Skip to content

Step2 - GitHub(HTTP) #108

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

Open
wants to merge 11 commits into
base: oyj7677
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# android-github

## 1 단계 - GitHub(모듈 분리)
### 프로그래밍 요구 사항
- 순수 코틀린 모듈인 domain 모듈을 만든다.
- 순수 코틀린 모듈인 data 모듈을 만든다.
- data 모듈은 domain 모듈에 의존해야 한다.
- app 모듈은 domain 모듈에 의존해야 한다.

## 2단계 - GitHub(HTTP)
###기능 요구사항
- GitHub Repository 목록을 가져올 수 있어야 한다.
- GET, https://api.github.com/repositories
- full_name, description 필드만

###프로그래밍 요구사항
- Repository 목록을 가져오는 기능은 data 모듈에 구현되어야 한다.
- data 모듈의 구현체는 모두 internal class로 선언한다.
- HTTP 요청을 통해 가져오는 구현체에 대한 테스트 코드를 작성한다.
- OkHttp MockWebServer 이용
23 changes: 15 additions & 8 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@file:Suppress("UnstableApiUsage")

plugins {
id("com.android.application")
id("kotlin-android")
Expand All @@ -10,7 +8,7 @@ android {
compileSdk = Version.compileSdk

defaultConfig {
applicationId = "edu.nextstep.camp.calculator"
applicationId = "camp.nextstep.edu.github"
minSdk = Version.minSdk
targetSdk = Version.targetSdk
versionCode = 1
Expand Down Expand Up @@ -42,17 +40,26 @@ android {
}

dependencies {
api(project(":github-domain"))
implementation(project(":github-data"))
implementation(project(":github-domain"))

implementation("org.jetbrains.kotlin:kotlin-stdlib:${Version.kotlin}")
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.0")
implementation("com.google.android.material:material:1.7.0")
implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.9.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.fragment:fragment-ktx:1.5.5")
implementation("androidx.fragment:fragment-ktx:1.6.1")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation ("com.squareup.retrofit2:converter-gson:2.9.0")
implementation ("com.google.code.gson:gson:2.10.1")
implementation("androidx.room:room-runtime:2.5.2")
annotationProcessor("androidx.room:room-compiler:2.5.2")

testImplementation("junit:junit:4.13.2")
testImplementation("com.google.truth:truth:1.1.3")
testImplementation ("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
implementation("com.squareup.okhttp3:mockwebserver:4.11.0")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="camp.nextstep.edu.github">

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

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/camp/nextstep/edu/github/GitHubViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package camp.nextstep.edu.github

import androidx.lifecycle.ViewModel
import com.example.githubdomain.GitHubRepository

class GitHubViewModel : ViewModel() {

}
22 changes: 21 additions & 1 deletion app/src/main/java/camp/nextstep/edu/github/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,30 @@ package camp.nextstep.edu.github

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import camp.nextstep.edu.github.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI의 경우는 Step3의 미션입니다 ㅠ,ㅠ! 관련 변경사항은 step3진행하면서 적용해주시면 좋을거같아요!


private lateinit var binding: ActivityMainBinding
private val viewModel: GitHubViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

initBinding()
initViewModel()

setContentView(binding.root)
}

private fun initViewModel() {
binding.viewModel = viewModel
}

private fun initBinding() {
binding = ActivityMainBinding.inflate(layoutInflater)
binding.lifecycleOwner = this
}
}
}
41 changes: 23 additions & 18 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
>
<layout>
<data>
<variable
name="viewModel"
type="camp.nextstep.edu.github.GitHubViewModel" />
</data>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

</androidx.constraintlayout.widget.ConstraintLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="github/repositories 저장"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
61 changes: 54 additions & 7 deletions github-data/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,13 +1,60 @@
plugins {
id("java-library")
id("org.jetbrains.kotlin.jvm")
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt")
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
android {
namespace = "camp.nextstep.edu.github.github_data"
compileSdk = Version.compileSdk

defaultConfig {
minSdk = Version.minSdk

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}

testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
}

dependencies {
api(project(":github-domain"))
dependencies{
implementation(project(":github-domain"))

implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation ("com.squareup.retrofit2:converter-gson:2.9.0")
implementation ("com.google.code.gson:gson:2.10.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
implementation("androidx.room:room-runtime:2.5.2")
implementation("com.squareup.okhttp3:mockwebserver:4.11.0")
implementation("androidx.test:core-ktx:1.5.0")

kapt ("androidx.room:room-compiler:2.5.2")

testImplementation("junit:junit:4.13.2")
testImplementation ("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
testImplementation("com.google.truth:truth:1.1.3")
testImplementation ("org.robolectric:robolectric:4.9")

}
10 changes: 10 additions & 0 deletions github-data/src/main/java/com/example/github/GitHubApi.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.github

import com.example.github.data.repository.remote.data.RemoteGithubData
import retrofit2.Response
import retrofit2.http.GET

interface GitHubApi {
@GET("/repositories")
suspend fun getGitHubRepo(): Response<List<RemoteGithubData>>
}
16 changes: 16 additions & 0 deletions github-data/src/main/java/com/example/github/GitHubService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.github

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

internal object GitHubService {
private val BASE_URL = "https://api.github.com"

fun getGitHubService(): GitHubApi {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(GitHubApi::class.java)
}
}
35 changes: 35 additions & 0 deletions github-data/src/main/java/com/example/github/data/mapper/Mapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.example.github.data.mapper

import com.example.github.data.repository.remote.data.RemoteGithubData
import com.example.github.data.room.GithubRepoEntity
import com.example.githubdomain.GithubRepoData

fun List<GithubRepoEntity>.githubRepoEntityToGithubRepoData(): List<GithubRepoData> {
return this.toList().map {
GithubRepoData(
id = it.id,
fullName = it.fullName,
description = it.description ?: ""
)
}
}

fun List<RemoteGithubData>.remoteGithubDataToGithubRepoData(): List<GithubRepoData> {
return this.toList().map {
GithubRepoData(
id = it.id,
fullName = it.fullName,
description = it.description
)
}
}

fun List<GithubRepoData>.githubRepoDataToGithubRepoEntity(): List<GithubRepoEntity> {
return this.toList().map {
GithubRepoEntity(
id = it.id,
fullName = it.fullName,
description = it.description
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example.github.data.repository
import com.example.github.data.mapper.githubRepoEntityToGithubRepoData
import com.example.github.data.mapper.remoteGithubDataToGithubRepoData
import com.example.github.data.repository.local.LocalGithubDataSource
import com.example.github.data.repository.remote.RemoteGithubDataSource
import com.example.githubdomain.GitHubRepository
import com.example.githubdomain.GithubRepoData

internal class GithubRepositoryImpl(
private val localGithubDataSource: LocalGithubDataSource,
private val remoteGithubDataSource: RemoteGithubDataSource
): GitHubRepository {

override suspend fun getGitHubRepoData(): List<GithubRepoData> {
val localData = localGithubDataSource.getGitHubRepo()
return if(localData.isNullOrEmpty()) {
try {
remoteGithubDataSource.getGitHubRepo().remoteGithubDataToGithubRepoData().also {
localGithubDataSource.insertGitHubRepo(it)
}
} catch (e: Exception) {
e.printStackTrace()
emptyList()
}

} else {
localData.githubRepoEntityToGithubRepoData()
}
}

suspend fun isEmptyLocalData() = localGithubDataSource.getGitHubRepo().isNullOrEmpty()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.github.data.repository.local

import com.example.github.data.mapper.githubRepoDataToGithubRepoEntity
import com.example.github.data.room.GithubRepoEntity
import com.example.githubdomain.GithubRepoData

internal class FakeLocalGithubDataSourceImpl: LocalGithubDataSource {
private val gitHubRepoEntityList = mutableListOf<GithubRepoEntity>()
override suspend fun getGitHubRepo() = gitHubRepoEntityList

override fun insertGitHubRepo(gitHubRepoDataList: List<GithubRepoData>) {
gitHubRepoEntityList.addAll(gitHubRepoDataList.githubRepoDataToGithubRepoEntity())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.github.data.repository.local

import com.example.github.data.room.GithubRepoEntity
import com.example.githubdomain.GithubRepoData

interface LocalGithubDataSource {
suspend fun getGitHubRepo(): List<GithubRepoEntity>

fun insertGitHubRepo(gitHubRepoDataList: List<GithubRepoData>)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example.github.data.repository.local

import com.example.github.data.mapper.githubRepoDataToGithubRepoEntity
import com.example.github.data.room.GithubRepoDao
import com.example.github.data.room.GithubRepoEntity
import com.example.githubdomain.GithubRepoData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async

internal class LocalGithubDataSourceImpl(private val githubDao: GithubRepoDao): LocalGithubDataSource {
override suspend fun getGitHubRepo(): List<GithubRepoEntity> {
return CoroutineScope(Dispatchers.IO).async {
githubDao.getGitHubRepo()
}.await()
}

override fun insertGitHubRepo(gitHubRepoDataList: List<GithubRepoData>) {
githubDao.insertGithubRepoEntityList(gitHubRepoDataList.githubRepoDataToGithubRepoEntity())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.github.data.repository.remote

import com.example.github.data.repository.remote.data.RemoteGithubData

interface RemoteGithubDataSource {
suspend fun getGitHubRepo(): List<RemoteGithubData>
}
Loading