-
Notifications
You must be signed in to change notification settings - Fork 45
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
Step2 - GitHub(HTTP) #108
base: oyj7677
Are you sure you want to change the base?
Step2 - GitHub(HTTP) #108
Changes from 7 commits
5c378e9
678ced4
e0d1e86
1c2ac73
5ab40f8
8dc801b
daf059b
222dd0b
759b5b6
d262f9b
e515420
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 이용 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package camp.nextstep.edu.github | ||
|
||
import android.util.Log | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import com.example.github_domain.GitHubRepository | ||
import kotlinx.coroutines.launch | ||
|
||
class GitHubViewModel(private val gitHubRepository: GitHubRepository) : ViewModel() { | ||
|
||
fun insertGitHubRepository() { | ||
gitHubRepository.insertGitHubRepository() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package camp.nextstep.edu.github | ||
|
||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.ViewModelProvider | ||
import com.example.github_data.room.GithubRepoDao | ||
import com.example.github_data.room.Injector | ||
|
||
class GithubViewModelFactory(private val githubRepoDao: GithubRepoDao) : ViewModelProvider.NewInstanceFactory() { | ||
override fun <T : ViewModel> create(modelClass: Class<T>): T { | ||
if (modelClass.isAssignableFrom(GitHubViewModel::class.java)) { | ||
return GitHubViewModel(Injector.providesGithubRepoRepository(githubRepoDao)) as T | ||
} | ||
throw IllegalArgumentException("Unknown ViewModel class") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,10 +2,42 @@ package camp.nextstep.edu.github | |
|
||
import androidx.appcompat.app.AppCompatActivity | ||
import android.os.Bundle | ||
import android.util.Log | ||
import androidx.activity.viewModels | ||
import androidx.lifecycle.ViewModelProvider | ||
import camp.nextstep.edu.github.databinding.ActivityMainBinding | ||
import com.example.github_data.room.GithubRepoDao | ||
import com.example.github_data.room.GithubRepoDatabase | ||
import kotlinx.coroutines.CoroutineExceptionHandler | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.withContext | ||
|
||
class MainActivity : AppCompatActivity() { | ||
|
||
private lateinit var binding: ActivityMainBinding | ||
private lateinit var viewModel: GitHubViewModel | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(R.layout.activity_main) | ||
|
||
initBinding() | ||
initViewModel() | ||
|
||
setContentView(binding.root) | ||
} | ||
|
||
private fun initViewModel() { | ||
val githubRepoDao = GithubRepoDatabase.getInstance(this)!!.GithubRepoDao() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !! 는 null일 경우, 에러가 발생하니 사용을 지양하는편이 좋아요 |
||
val githubViewModelFactory = GithubViewModelFactory(githubRepoDao) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dao라는 데이터 레이어의 구현체를 app모듈에서 알아야할까요? 추가로 고민하면 좋을거같아요! |
||
viewModel = ViewModelProvider(this, githubViewModelFactory)[GitHubViewModel::class.java] | ||
binding.viewModel = viewModel | ||
} | ||
|
||
private fun initBinding() { | ||
binding = ActivityMainBinding.inflate(layoutInflater) | ||
binding.lifecycleOwner = this | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,27 @@ | ||
<?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" | ||
android:onClick="@{() -> viewModel.insertGitHubRepository()}"/> | ||
|
||
</androidx.constraintlayout.widget.ConstraintLayout> | ||
</layout> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package camp.nextstep.edu.github | ||
|
||
import com.example.github_data.GitHubApi | ||
import com.example.github_data.room.GithubRepoEntity | ||
import com.google.common.truth.Truth.assertThat | ||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
import kotlinx.coroutines.test.UnconfinedTestDispatcher | ||
import kotlinx.coroutines.test.runTest | ||
import okhttp3.mockwebserver.MockResponse | ||
import okhttp3.mockwebserver.MockWebServer | ||
import org.junit.Before | ||
import org.junit.Test | ||
import retrofit2.Retrofit | ||
import retrofit2.converter.gson.GsonConverterFactory | ||
import retrofit2.create | ||
import java.io.File | ||
|
||
class GitHubViewModelTest { | ||
val server = MockWebServer() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. viewModel 테스트도 좋지만,
데이터모듈에서 테스트코드를 작성해보면 어떨까요? |
||
|
||
|
||
private lateinit var service: GitHubApi | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. api를 테스트하기 보단 GitHubRepositoryImpl를 테스트하는게 의미있지않을까요? |
||
|
||
@Before | ||
fun init() { | ||
val baseUrl = server.url("") | ||
|
||
service = Retrofit.Builder() | ||
.addConverterFactory(GsonConverterFactory.create()) | ||
.baseUrl(baseUrl) | ||
.build() | ||
.create() | ||
} | ||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
@Test | ||
fun `MockWebServer를 활용하여 getGitHubInfo() 요청을 검증한다`() = runTest(UnconfinedTestDispatcher()) { | ||
// given : MockResponse을 활용해 서버 응답을 세팅해둔다. | ||
val response = MockResponse() | ||
.setBody(File("src/test/resources/githubRepo.json").readText()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 정상케이스의 테스트도 좋지만, |
||
.setResponseCode(200) | ||
|
||
server.enqueue(response) | ||
|
||
// when : https://api.github.com/repositories에 HTTP통신 요청을 한다. | ||
val actual = service.getGitHubInfo().body() | ||
|
||
// then : 응답 받은 값과 비교한다. | ||
val expected = listOf(GithubRepoEntity("1", "mojombo/grit", "**Grit is no longer maintained. Check out libgit2/rugged.** Grit gives you object oriented read/write access to Git repositories via Ruby."), | ||
GithubRepoEntity("26", "wycats/merb-core", "Merb Core: All you need. None you don't."), | ||
GithubRepoEntity("27", "rubinius/rubinius", "The Rubinius Language Platform"), | ||
GithubRepoEntity("28", "mojombo/god", "Ruby process monitor"),) | ||
|
||
assertThat(actual).isEqualTo(expected) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UI의 경우는 Step3의 미션입니다 ㅠ,ㅠ! 관련 변경사항은 step3진행하면서 적용해주시면 좋을거같아요!