일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- node.js
- Observable
- Reactive
- MediaSession
- Android
- 인앱결제
- mvvm
- PagingLib
- paging
- Koin
- node
- junit
- google play
- mysql
- GCP
- RxKotlin
- rx
- Kotlin
- SWIFTUI
- list
- android13
- SwiftUI Tutorial
- MediaPlayer
- Animation
- 테스트 자동화
- 동영상
- Android 13
- MotionLayout
- php
- databinding
- Today
- Total
봄날은 갔다. 이제 그 정신으로 공부하자
MVVM + Koin 최소 샘플 앱 개발 - part 2 본문
해당 글은 koin, DataBinding, Rxjava, retrofit, Coroutine, PagingLibrary을 사용하여. MVVM 구조로 된 github 사용자 검색 앱을 개발하는 과정을 기술합니다.
해당 글을 작성하는 이유는 위 기술이 적용된 최소 샘플 앱을 만들어 놓고 필요 시 참고하기 위합입니다.
해당 글은 최소 샘플앱 개발 두번째 글로 첫번째 글을 읽지 않은 분들은 이전 글 읽어보시면 해당 글을 이해하는데 더 도움이 됩니다.
https://als2019.tistory.com/20
사용자 검색 API 분석
GitHub에서 제공하는 사용자 검색 API는 아래와 같은 규격을 가지고 있습니다.
https://api.github.com/search/users?q={query}{&page,per_page,sort,order}
URL 파라미터터는 아래와 같습니다.
- q: 검색할 사용자 이름
- page: 검색 결과 페이지 번호 (1번 부터 시작)
- per_page: 페이지당 item count
- sort & order: 정렬 방식
호출 결과 값은 아래와 같습니다.
{
"total_count": 13179,
"incomplete_results": false,
"items": [
{
"login": "Aaaaaaaty",
"id": 15126694,
"node_id": "MDQ6VXNlcjE1MTI2Njk0",
"avatar_url": "https://avatars0.githubusercontent.com/u/15126694?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/Aaaaaaaty",
"html_url": "https://github.com/Aaaaaaaty",
"followers_url": "https://api.github.com/users/Aaaaaaaty/followers",
"following_url": "https://api.github.com/users/Aaaaaaaty/following{/other_user}",
"gists_url": "https://api.github.com/users/Aaaaaaaty/gists{/gist_id}",
"starred_url": "https://api.github.com/users/Aaaaaaaty/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/Aaaaaaaty/subscriptions",
"organizations_url": "https://api.github.com/users/Aaaaaaaty/orgs",
"repos_url": "https://api.github.com/users/Aaaaaaaty/repos",
"events_url": "https://api.github.com/users/Aaaaaaaty/events{/privacy}",
"received_events_url": "https://api.github.com/users/Aaaaaaaty/received_events",
"type": "User",
"site_admin": false,
"score": 1.0
},
…
]
}
Netwok 통신 API 만들기
앱이 네트워크 통신이 가능하도록 manifest.xml 파일에 아래와 같은 permission을 추가해줍니다.
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
Model 폴더에 network 폴더를 만들고 아래와 같이 3개의 파일을 만들어줍니다.
- GithubApiInterface: 인터페이스 class
- GithubApiService: 서비스 class
- GithubApiModel: API 모델 class
우선 사용자가 직접 호출하는 GithubApiInterface class에 아래와 같이 사용자 검색 API를 만들어줍니다.
검색어(“q”)를 입력하고 검색할 페이지 번호(“page”)와 페이지당 아이템수(“per_page”)를 매개변수로 받습니다.
값이 제대로 오는지 확인하기 위해 우선 리턴 값은 String으로 받습니다.
interface GithubApiInterface {
fun getUserSearch(q: String, page: Int): Single<String>
}
그다음으로 GithubApiService class에 getUserSearch() 함수를 추가해줍니다.
interface GithubApiService {
@Headers("Content-Type: application/json",
"Connection: keep-alive")
@GET("/search/users")
fun getUserSearch(@Query("q") q: String, @Query("page") page: Int, @Query("per_page") per_page: Int): Single<String>
}
마지막으로 GithubApiModel class를 만들어줍니다. GithubApiModel class는 GithubApiInterface와 GithubApiService
class를 이어주는 class 입니다.
class GithubApiModel(private val github: GithubApiService) : GithubApiInterface {
companion object{
val DEFAULT_PER_PAGE = 20
}
override fun getUserSearch(q: String, page: Int): Single<String> {
return github.getUserSearch(q, page, DEFAULT_PER_PAGE)
}
}
이렇게 3개의 클래스를 만드는 이유는 사용자는 GithubApiInterface를 통해 앱에 최적화되게 API를 호출해주고 실제 API를 호출하기 전에 필요한 처리는 GithubApiModel에서 처리해 네트워크 처리로 인한 앱 처리 부분을 분리하기 위함입니다.
이건 최소 샘플이라 내부 코드가 별로 없는데 실제 상용 앱을 개발하면 GithubApiModel class의 함수에 많은 네트워크 처리 코드가 추가됩니다.
Network API 종속성 주입하기
이렇게 만들어진 Network API를 편하게 호출하기 위해 koin을 사용하여 종속성을 추가해줍니다.
val BASE_URL = "https://api.github.com"
var apiModelPart = module {
factory<GithubApiInterface> {
GithubApiModel(get())
}
}
var apiDevPart = module {
single<GithubApiService>{
Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(GithubApiService::class.java)
}
}
…
var myDiModule = listOf(
apiDevPart,
apiModelPart,
viewModelpart
)
* 주의
서버 상태에 따라 아래 GsonConverterFactory 추가 후, POST & PUT 호출시 서버에서 입력 파라미터 에러가 발생하는 경우가 있습니다. 이유는 GsonConverterFactory 에서 JSONObject를 서버에 보낼 때 해당 JSONObject의 포맷이 어떻게 구성되어있는지 설명하는 Value key fair완 관련된 내용을 사용자가 만든 JSONObject의 앞에 추가해서 보내기 때문입니다.
이문제를 해결하기 위해서는 서버와 협력하는게 가장 좋지만 현실적으로 그러기 곤란한 상황이 발생할 수도 있습니다.
그럴때는 아래와 같이 수정해주시면 됩니다.
// build.gradle(app) 추가
implementation 'org.ligboy.retrofit2:converter-fastjson-android:2.2.0'
// MyDi.kt 수정
.addConverterFactory(FastJsonConverterFactory.create())
사용자 검색 API 호출하기
사용자 검색을 위해 검색 버튼 클릭 시 호출되는 MainViewModel의 onClickSearch() 함수에 위에서 만든 API를 호출해 줍니다. API를 호출하기 위해서는 MainViewModel이 API를 획득하는 과정은 아래와 같이 처리해줍니다.
// 인스턴스 생성 시 GithubApiInterface를 매개변수로 받도록 수정
class MainViewModel(private val github: GithubApiInterface): BaseViewModel() {
…
}
// koin을 사용해 MainViewModel에서 생성 시 GithubApiInterface를 매개변수로 받도록 수정
var viewModelpart = module{
viewModel{
MainViewModel(get())
}
...
}
이렇게 간단히 종속성을 주입할 수 있는 것이 koin의 장점입니다.
검색 버튼 클릭 시 사용자를 검색하기 위해 MainViewModel의 onClickSearch() 함수에 API를 호출하는 코드를 아래와 같이 추가합니다.
fun onClickSearch(view: View){
addDisposable(
github.getUserSearch(keyword.value, 1)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.single())
.subscribe({
Log.d(TAG, "response: ${it}")
},{
Log.d(TAG, "error: ${it.message}")
})
)
}
Response 자료화 하기
현재만들어진 코드는 사용자 검색 결과를 JSON format의 String으로 전달 받고 있습니다.
전달받은 String을 자료화하기 위해서는 많은 리소스가 소모됩니다.
아예 전받을 때 String이 아닌 자료화된 데이터를 받을 수 있도록 처리하도록 하겠습니다.
우선 common 폴더 하위에 item이라는 폴더를 만들고 2개의 data class 파일을 만들어 줍니다.
// DataUser.kt
data class DataUser(var id: Int, var login: String, var avatar_url: String) {
constructor(): this(-1, "", "")
}
// DataUserSearchResult.kt
data class DataUserSearchResult(var total_count: Int, var items: List<DataUser>) {
constructor() : this(-1, listOf())
}
이렇게 만들어진 DataUserSearchResult data class를 Single<String>로 되어 있는 3개 파일을 모두 Single<DataUserSearchResult>로 변경해줍니다.
결과를 보면 기존 String으로 전달되던 response값이 DataUserSearchResult으로 자료화 되어 전달 되는 것을 확인할 수 있습니다. 전체 검색 결과를 확인하기 위해 아래와 같이 count를 추가합니다.
addDisposable(
github.getUserSearch(keyword.value, 1)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.single())
.subscribe({
Log.d(TAG, "response: ${it}")
count.postValue(it.total_count)
},{
Log.d(TAG, "error: ${it.message}")
count.postValue(0)
})
)
다음 글에서는 Paging Library를 사용하여 리스트에 검색 결과를 보여주는 부분과 더보기 처리를 해보도록 하겠습니다.
여기까지 정리된 소스는 아래 경로에 있습니다.
https://github.com/lee-kil-jae/MyGitSample/tree/dev_second
'Android jetpack' 카테고리의 다른 글
MultiDex 1편 - 64K 제한과 Multidex 설정 방법 (0) | 2021.01.25 |
---|---|
MVVM + koin 최소 샘플 앱 개발 - part3 (1) | 2020.12.07 |
MVVM + Koin 최소 샘플 앱 개발 - part 1 (0) | 2020.12.04 |
Paging Library - Display paged lists (0) | 2020.11.24 |
Paging Library - Gather Paged Data (0) | 2020.11.24 |