코딩하는 일용직 노동자

안드로이드 MVVM 패턴으로 작업해보기 (2) 본문

안드로이드

안드로이드 MVVM 패턴으로 작업해보기 (2)

bacass 2020. 4. 30. 18:02

#1 인트로 화면과 메인화면 만들기
우선 앱이 실행되면 인트로화면을 몇초 보여준후 홈화면으로 이동하는 형태로 만들었습니다.
IntroActivity 와 MainActivity 로 만들고 홈화면은 Fragment 를 포함하도록 만들었습니다.
그리고 서브화면들을 만들고 각 화면마다 별도의 ViewModel 을 만들었습니다.
(일단 쓸지 안쓸지는 모르겠지만 다 만들어줬습니다.)

#2 Base 클래스, MyApplication 만들기.
각 액티비티와 뷰모델이 상속받아 쓰도록 BaseActivity, BaseViewModel 클래스를 만들었습니다. 


Retrofit2 와 Koin 을 셋팅하기 위해서 MyApplication 클래스를 만들고 ViewModel 셋팅도 해줍니다.

SharedPreference 를 편하게 처리해줄 Kotpref 와 Koin 을 onCreate 에서 설정해줬습니다.
이 프로젝트에서 Kotpref 를 쓸일이 있나 싶지만 우선 다 넣어보겠습니다.
Kotpref의 사용법은 https://bacassf.tistory.com/20?category=900902에 따로 포스팅을 했습니다.

class MyApplication: Application() {
    companion object {
        var mContext: Context? = null
    }

    private val appModules = module {
        single<NetworkService> {
            Retrofit.Builder().baseUrl(BuildConfig.SERVER_HTTP_URL).client(OkHttpClient.Builder().apply {
                connectTimeout(30, TimeUnit.SECONDS)
                writeTimeout(30, TimeUnit.SECONDS)
                readTimeout(30, TimeUnit.SECONDS)
                addInterceptor(AddCookieInterceptor())
                addInterceptor(ReceivedCookieInterceptor())
                
                addInterceptor(HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
                    override fun log(message: String) {
                        if (!message.startsWith("{") && !message.startsWith("[")) {
                            Timber.tag("OkHttp").d(message)
                            return
                        }
                        try {
                            Timber.tag("OkHttp").d(GsonBuilder().setPrettyPrinting().create().toJson(JsonParser().parse(message)))
                        } catch (m: JsonSyntaxException) {
                            Timber.tag("OkHttp").d(message)
                        }
                    }

                }).apply {
                    level = HttpLoggingInterceptor.Level.BODY
                })
            }.build()).addConverterFactory(ScalarsConverterFactory.create()).addConverterFactory(
                GsonConverterFactory.create()).build().create()
        }

        single {
            NetworkRepository(get())
        }

//        single { MainViewModel(get()) } // 싱글톤 뷰모델 생성.
        viewModel { MainViewModel(get()) }
        viewModel { HomeViewModel(get()) }
        viewModel { ShareViewModel(get()) }
        viewModel { GalleryViewModel(get()) }
    }

    override fun onCreate() {
        super.onCreate()

        mContext = this

        /**
         * 로그를 표시함.
         */
        if (BuildConfig.SHOW_LOG) {
            Timber.plant(Timber.DebugTree())
        }

        Kotpref.init(this)

        // start Koin
        startKoin {
            androidLogger()
            androidContext(this@MyApplication)
            modules(appModules)
        }
    }
}


#3 네트워크를 처리할 클래스 만들기
네트워크를 처리할 network 패키지를 만들고 Retrofit2 를 사용하기 위해 NetworkRepository, NetworkService 클래스를 만들었습니다.

 

NetworkRepository.kt

class NetworkRepository(private val service: NetworkService) {

    private fun parseErrorResult(body: ResponseBody): NetworkResult.Error {
        try {
            val element = JsonParser().parse(body.string()).asJsonObject
            val code = if (element.has("code")) element.get("code").asString else null
            val message = if (element.has("message")) element.get("message").asString else null
            return NetworkResult.Error(code, message)
        } catch (e: Exception) {
        }
        return NetworkResult.Error(null, null)
    }

    private suspend fun <T : Any> callResponse(call: suspend () -> Response<T>): NetworkResult<T> {
        val response = call.invoke()
        if (response.isSuccessful) {
            return NetworkResult.Success(response.body())
        } else {
            response.errorBody()?.let { error ->
                return parseErrorResult(error)
            } ?: run {
                return NetworkResult.Error(null, null)
            }
        }
    }

    private suspend fun <T : Any> callArray(call: suspend () -> Response<List<T>>): List<T> {
        val response = call.invoke()
        return response.body() ?: emptyList()
    }

    private suspend fun <T : Any> callList(call: suspend () -> Response<ListData<T>>): ListData<T> {
        val response = call.invoke()
        return response.body() ?: ListData()
    }




    // Search Image
    suspend fun searchImage(params: RequestImageParam): NetworkResult<ImageObj> = withContext(Dispatchers.IO) {
        callResponse { service.searchImage(params.key!!, params.q!!, params.image_type!!, params.page!!, params.per_page!!) }
    }

}

 

NetworkService.kt

interface NetworkService {

    // 이미지 검색
    @GET("api/")
    suspend fun searchImage(
        @Query("key") key: String,
        @Query("q") q: String,
        @Query("image_type") image_type: String,
        @Query("page") page: Int,
        @Query("per_page") per_page: Int
    ): Response<Void>
}