Skip to content

Commit a526fa5

Browse files
authored
🚀 2단계 - GitHub(UI 레이어) (#47)
* refactor: GithubResponse > RepositoryResponse로 클래스명 수정 * refactor: remote.api.mapper > remote.mapper 로 이동 및 파일명 수정 * refactor: getRepositories 첫 조회 ViewModel init에서 처리 * refactor: 화면 기준 패키지 재구성 * docs: Step1 - 개선 사항 정의 * docs: Step2 - 요구 사항 정의 * refactor: ViewModel Repository 의존성 주입 로직 수정 및 앱 패키지 구조 변경 * refactor: GithubModel > RepositoryModel 로 클래스명 변경 * feat: GithubViewModel _repositoryList 값 세팅 * feat: GithubScreen 화면 구성 및 repositoryList 데이터 연동 * refactor: appbar title stringResource 적용 * refactor: GithubScreen private 접근제어자 제거 * refactor: Text style 중복 코드 제거 * refactor: Color MaterialTheme.colorScheme 활용 * refactor: RepositoryResponse, RepositoryModel에 id 속성 추가 * refactor: GithubScreen LazyColumn key 속성 추가 * refactor: viewModelScope Dispatchers.IO 제거 * refactor: GithubRepoListContainer, GithubRepoItem 컴포넌트 분리 * refactor: GithubViewModel getRepositories > fetchRepositories 함수명 수정 * feat: GithubScreen GithubScreenEmptyPreview 추가 * test: GithubScreen 화면 테스트 코드 작성
1 parent e18d945 commit a526fa5

File tree

18 files changed

+343
-79
lines changed

18 files changed

+343
-79
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
# android-github-compose
22

3-
### 🚀 1단계 - GitHub(데이터 레이어)
3+
### 🚀 1단계 - GitHub(데이터 레이어) 요구 사항
44
- NEXTSTEP 조직의 저장소 목록을 가져오는 Client를 구현한다.
55
- GET https://api.github.com/orgs/next-step/repos
66
- full_name, description 필드만 가져온다.
77
- 네트워크 요청으로 저장소 목록을 가져오는 기능은 data 패키지에 구현되어야 한다.
88
- 힌트 코드를 참고하여 수동 DI를 구현한다. (Hilt와 같은 별도의 DI 라이브러리를 활용하지 않는다)
99
- 실제로 서버 데이터가 잘 로드되는지 Log로 확인한다.
1010

11+
#### 🚀 1단계 - GitHub(데이터 레이어) 개선 사항
12+
- GithubResponse 클래스 명 api 호출 의도에 맞게 변경
13+
- remote.api.mapper > remote.mapper 패키지 이동
14+
- getRepositories 첫 조회 viewModel init에서 호출하도록 수정
15+
- 화면 기준 패키지 재구성
16+
17+
### 🚀 2단계 - GitHub(UI 레이어) 요구 사항
18+
- NEXTSTEP 조직의 저장소 목록을 선형 리스트로 노출한다
19+
- Material3의 Theme을 활용하여 Typography와 Color를 부여한다.
20+
- 힌트 코드를 참고하여 ViewModel Factory를 구현한다. (Hilt와 같은 별도의 DI 라이브러리를 활용하지 않는다)
21+
- 저장소 목록을 노출하는 UI와 관련된 기능은 ui 패키지에 구현되어야 한다.
22+
- UI 레이어는 데이터 레이어를 의존하지만, 데이터 레이어는 UI 레이어를 의존해선 안 된다.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import androidx.compose.ui.test.junit4.createComposeRule
2+
import androidx.compose.ui.test.onNodeWithText
3+
import nextstep.github.data.model.RepositoryModel
4+
import nextstep.github.ui.screen.github.GithubScreen
5+
import org.junit.Rule
6+
import org.junit.Test
7+
8+
class GithubScreenTest {
9+
10+
@get:Rule
11+
val composeTestRule = createComposeRule()
12+
13+
val repositoryList = listOf(
14+
RepositoryModel(
15+
id = 1,
16+
fullName = "next-step/nextstep-docs",
17+
description = "nextstep 매뉴얼 및 문서를 관리하는 저장소",
18+
),
19+
RepositoryModel(
20+
id = 2,
21+
fullName = "next-step/holy-moly",
22+
description = "nextstep 홀리몰리한 저장소",
23+
),
24+
RepositoryModel(
25+
id = 3,
26+
fullName = "next-step/haly-galy",
27+
description = "nextstep 할리갈리한 저장소",
28+
),
29+
)
30+
31+
@Test
32+
fun Github_레포지토리_데이터가_정상적으로_화면에_출력된다() {
33+
composeTestRule.setContent {
34+
GithubScreen(
35+
repositoryList = repositoryList,
36+
)
37+
}
38+
39+
composeTestRule.onNodeWithText("next-step/nextstep-docs")
40+
.assertExists()
41+
42+
composeTestRule.onNodeWithText("next-step/holy-moly")
43+
.assertExists()
44+
45+
composeTestRule.onNodeWithText("next-step/haly-galy")
46+
.assertExists()
47+
}
48+
}

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
android:theme="@style/Theme.Github"
1717
tools:targetApi="31">
1818
<activity
19-
android:name=".MainActivity"
19+
android:name=".ui.screen.MainActivity"
2020
android:exported="true"
2121
android:label="@string/app_name"
2222
android:theme="@style/Theme.Github">
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package nextstep.github.data.model
22

3-
data class GithubModel(
3+
data class RepositoryModel(
4+
val id: Int,
45
val fullName: String,
56
val description: String
67
)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package nextstep.github.data.remote.api
22

3-
import nextstep.github.data.remote.response.GithubResponse
3+
import nextstep.github.data.remote.response.RepositoryResponse
44
import retrofit2.http.GET
55
import retrofit2.http.Path
66

77

88
interface GithubService {
99

1010
@GET("orgs/{organization}/repos")
11-
suspend fun getRepositories(@Path("organization") organization: String): List<GithubResponse>
11+
suspend fun getRepositories(@Path("organization") organization: String): List<RepositoryResponse>
1212
}
1313

app/src/main/java/nextstep/github/data/remote/api/mapper/GithubResponseToDataMapper.kt

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package nextstep.github.data.remote.mapper
2+
3+
import nextstep.github.data.model.RepositoryModel
4+
import nextstep.github.data.remote.response.RepositoryResponse
5+
6+
fun List<RepositoryResponse>.toDataList(): List<RepositoryModel> = map { it.toData() }
7+
8+
fun RepositoryResponse.toData() = RepositoryModel(
9+
id = id,
10+
fullName = fullName ?: "",
11+
description = description ?: ""
12+
)

app/src/main/java/nextstep/github/data/remote/response/GithubResponse.kt renamed to app/src/main/java/nextstep/github/data/remote/response/RepositoryResponse.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import kotlinx.serialization.SerialName
44
import kotlinx.serialization.Serializable
55

66
@Serializable
7-
data class GithubResponse(
7+
data class RepositoryResponse(
8+
@SerialName("id") val id: Int,
89
@SerialName("full_name") val fullName: String?,
910
@SerialName("description") val description: String?,
1011
)

app/src/main/java/nextstep/github/data/repository/GithubRepository.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
package nextstep.github.data.repository
22

3-
import nextstep.github.data.model.GithubModel
3+
import nextstep.github.data.model.RepositoryModel
44
import nextstep.github.data.remote.api.GithubService
5-
import nextstep.github.data.remote.api.mapper.toDataList
5+
import nextstep.github.data.remote.mapper.toDataList
66

77
interface GithubRepository {
8-
suspend fun getRepositories(organization: String): Result<List<GithubModel>>
8+
suspend fun getRepositories(organization: String): Result<List<RepositoryModel>>
99
}
1010

1111
class GithubRepositoryImpl(
1212
private val githubService: GithubService,
1313
) : GithubRepository {
1414

15-
override suspend fun getRepositories(organization: String): Result<List<GithubModel>> {
15+
override suspend fun getRepositories(organization: String): Result<List<RepositoryModel>> {
1616
return runCatching {
1717
githubService
1818
.getRepositories(organization = organization)
Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
11
package nextstep.github.ui.screen
22

33
import androidx.compose.runtime.Composable
4-
import androidx.compose.runtime.LaunchedEffect
54
import androidx.compose.ui.Modifier
6-
import androidx.lifecycle.viewmodel.compose.viewModel
7-
import nextstep.github.di.AppContainer
8-
import nextstep.github.ui.viewmodel.GithubViewModel
5+
import nextstep.github.ui.screen.github.GithubScreen
96

107
@Composable
118
fun GithubApp(
12-
appContainer: AppContainer,
13-
modifier: Modifier = Modifier
9+
modifier: Modifier = Modifier,
1410
) {
15-
val viewModel: GithubViewModel = viewModel(
16-
factory = GithubViewModel.provideFactory(appContainer.githubRepository)
11+
GithubScreen(
12+
modifier = modifier,
1713
)
18-
19-
LaunchedEffect(Unit) {
20-
viewModel.getRepositories("next-step")
21-
}
2214
}

0 commit comments

Comments
 (0)