Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Step1 - GitHub(데이터 레이어) #36

Open
wants to merge 1 commit into
base: yibeomseok
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
# android-github-compose
# android-github-compose

## Step1

- NEXTSTEP 조직의 저장소 목록을 가져오는 Client를 구현한다
- `full_name`, `description` 필드만 가져온다
- 네트워크 요청으로 저장소 목록을 가져오는 기능은 `data` 패키지에 구현되어야 한다
- 힌트 코드를 참고하여 수동 DI를 구현한다
- 실제 서버 데이터가 잘 로드되는지 Log로 확인한다

4 changes: 4 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.kotlinSerialization)
}

android {
Expand Down Expand Up @@ -68,4 +69,7 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)

implementation(libs.kotlinx.serialization.json)
implementation(libs.retrofit2.kotlinx.serialization.converter)
}
5 changes: 4 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".NextStepApp"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -13,7 +16,7 @@
android:theme="@style/Theme.Github"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name=".ui.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Github">
Expand Down
46 changes: 0 additions & 46 deletions app/src/main/java/nextstep/github/MainActivity.kt

This file was deleted.

8 changes: 8 additions & 0 deletions app/src/main/java/nextstep/github/NextStepApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package nextstep.github

import android.app.Application
import nextstep.github.di.AppContainer

internal class NextStepApp : Application() {
val appContainer = AppContainer()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package nextstep.github.data.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
internal data class RemoteGitHubRepoInfo(
@SerialName("full_name")
val fullName: String?,
@SerialName("description")
val description: String?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package nextstep.github.data.repository

import nextstep.github.data.model.RemoteGitHubRepoInfo
import nextstep.github.data.service.GithubService

internal interface GitHubRepository {
suspend fun getRepositories(organization: String): List<RemoteGitHubRepoInfo>
}

internal class GitHubRepositoryImpl(
private val githubService: GithubService
) : GitHubRepository {
override suspend fun getRepositories(organization: String): List<RemoteGitHubRepoInfo> {
return githubService.getRepositories(organization)
}
}
12 changes: 12 additions & 0 deletions app/src/main/java/nextstep/github/data/service/GithubService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package nextstep.github.data.service

import nextstep.github.data.model.RemoteGitHubRepoInfo
import retrofit2.http.GET
import retrofit2.http.Path

internal interface GithubService {
@GET("orgs/{organization}/repos")
suspend fun getRepositories(
@Path("organization") organization: String
): List<RemoteGitHubRepoInfo>
}
33 changes: 33 additions & 0 deletions app/src/main/java/nextstep/github/di/AppContainer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package nextstep.github.di

import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import nextstep.github.data.repository.GitHubRepository
import nextstep.github.data.repository.GitHubRepositoryImpl
import nextstep.github.data.service.GithubService
import nextstep.github.ui.usecase.GetGitHubRepositoryUseCase
import nextstep.github.ui.usecase.GetGitHubRepositoryUseCaseImpl
import okhttp3.MediaType
import okhttp3.OkHttpClient
import retrofit2.Retrofit

internal class AppContainer {
private val serialization = Json { ignoreUnknownKeys = true }
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(OkHttpClient.Builder().build())
.addConverterFactory(serialization.asConverterFactory(MediaType.get(CONTENT_TYPE)))
.build()

private val gitHubService = retrofit.create(GithubService::class.java)

private val gitHubRepository: GitHubRepository = GitHubRepositoryImpl(gitHubService)

val getGitHubRepositoryUseCase: GetGitHubRepositoryUseCase =
GetGitHubRepositoryUseCaseImpl(gitHubRepository)

companion object {
private const val CONTENT_TYPE = "application/json"
private const val BASE_URL = "https://api.github.com/"
}
}
52 changes: 52 additions & 0 deletions app/src/main/java/nextstep/github/ui/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package nextstep.github.ui

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import nextstep.github.NextStepApp
import nextstep.github.ui.model.UiGitHubRepoInfo
import nextstep.github.ui.theme.GithubTheme

internal class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val appContainer = (application as NextStepApp).appContainer
val getGitHubRepositoryUseCase = appContainer.getGitHubRepositoryUseCase
val repos: Flow<List<UiGitHubRepoInfo>> =
flow { emit(getGitHubRepositoryUseCase("next-step")) }

setContent {

val repo by repos.collectAsStateWithLifecycle(emptyList())

GithubTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
LazyColumn {
items(repo) { r ->
Column {
Text(text = r.fullName)
Text(text = r.description)
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package nextstep.github.ui.model

data class UiGitHubRepoInfo(
val fullName: String,
val description: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package nextstep.github.ui.usecase

import nextstep.github.data.model.RemoteGitHubRepoInfo
import nextstep.github.data.repository.GitHubRepository
import nextstep.github.ui.model.UiGitHubRepoInfo

internal interface GetGitHubRepositoryUseCase {
suspend operator fun invoke(organization: String): List<UiGitHubRepoInfo>
}

internal class GetGitHubRepositoryUseCaseImpl(
private val gitHubRepository: GitHubRepository
) : GetGitHubRepositoryUseCase {
override suspend fun invoke(organization: String): List<UiGitHubRepoInfo> {
return gitHubRepository.getRepositories(organization).map(RemoteGitHubRepoInfo::toUi)
}
}

private fun RemoteGitHubRepoInfo.toUi(): UiGitHubRepoInfo {
return UiGitHubRepoInfo(
fullName = fullName ?: "",
description = description ?: ""
)
}
13 changes: 13 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ lifecycleRuntimeKtx = "2.8.4"
activityCompose = "1.9.1"
composeBom = "2024.06.00"

# kotlinx serialization
kotlinxSerializationJson = "1.6.3"

# retrofit2
retrofit2KotlinxSerializationConverter = "1.0.0"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
Expand All @@ -27,6 +33,13 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }

# kotlinx serialization
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }

# retrofit2
retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }