프로그래밍/Android

[Android] 경기지역화폐 지도 만들기 - 4

흔한티벳여우 2020. 9. 2. 17:10
반응형

이번 포스트

지난 포스트에서는 Retrofit을 이용해서 실제로 지역화폐 가맹점 데이터를 가져와봤다. 이번 포스트에서는 Dependency Injection(의존성 주입)을 사용하기 위해 Koin이라는 라이브러리를 적용하겠다.

 

의존성 주입의 이점은 대표적으로 3가지가 있다.

  1. 의존 관계 설정이 컴파일시가 아닌 실행시에 이루어져 모듈간의 결합도를 낮출 수 있다.
  2. 코드 재사용을 높혀서 작성된 모듈을 여러 곳에서 소스코드의 수정 없이 사용할 수 있다.
  3. 모의 객체 등을 이용한 단위 테스트의 편의성을 높여준다.

위와 같은 이유로 사용되어지는데, 사실상 개발을 하다보면 확실히 느끼게 되는 점 중 하나는 결합도의 문제이다. 

객체 간의 결합도가 높게 되면 A 객체의 수정 시, B 객체도 수정해야하는 불상사가 생기게 되고 이러한 문제는 생산성과도 직결된다. 따라서 간단한 소스가 아닌 이상 의존성 주입을 사용하는 연습을 해두는게 좋다. 

그리고 요즘엔 거의 모든 언어에서 DI Framework를 자체 지원하거나 지원하는 라이브러리가 있으니 직접 구현보다는 해당 기능을 사용하는 것을 추천한다.

 

일반적으로 Java나 Kotlin을 사용할때 이용하는 대표적인 DI 라이브러리는 두 가지가 있다.

 

첫 번째는 Dagger

https://github.com/google/dagger

 

google/dagger

A fast dependency injector for Android and Java. Contribute to google/dagger development by creating an account on GitHub.

github.com

두 번째로는 Koin

https://github.com/InsertKoinIO/koin

 

InsertKoinIO/koin

Koin - a pragmatic lightweight dependency injection framework for Kotlin - InsertKoinIO/koin

github.com

 

dagger의 경우 java언어로 되어있지만 kotlin에서 java코드를 사용할 수 있기에 충분히 사용이 가능하고, 기능 또한 상당히 많다. 하지만 문제는 라이트하게 쓰기에는 라이브러리를 사용하기 위한 습득 장벽이 너무 높아서 사용하기가 힘들다.

 

그에 비해 Koin은 github에 대놓고 표기하고 있지만 경량화된 DI Framework라고 표현하고 있다. 심지어 kotlin 베이스라 Java 코드를 kotlin으로 변경할 필요 없이 바로 적용 가능하고, 손쉽게 적용이 가능하다.

그리고 차후에 적용할 MVVM 패턴에서 ViewModel을 의존성 주입할 수 있도록 쉽게 구현이 되어있으며, JUnit을 통한 테스트도 쉽다. 

 

자. 이제 Koin 라이브러리를 이용하기 위한 셋팅을 해보자.

app/build.gradle의 dependencies에 Koin 라이브러리를 추가하자.

// Koin
implementation "org.koin:koin-androidx-scope:2.1.5"

 

그리고 우리가 이전 포스트에서 만들었던 OpenDataService를 주입받는 클래스를 하나 만들어보자.

model -> service에서 LocalPlaceRepository 클래스를 생성한다.

 

import com.antoine.easylocalpaymap.model.data.LocalPayPlace
import retrofit2.Call

class LocalPlaceRepository(private val service: OpenDataService) {
    fun getOlaceList(index: String, size: String, sigun: String, newAddr: String, oldAddr: String) : Call<LocalPayPlace> {
        return service.getPlaceList(AuthKey.KEY, "json", index, size, sigun, newAddr, oldAddr)
    }
}

 

Package에 di를 만들고 그 밑에 AppModules.kt 라는 파일을 생성한다.

 

import com.antoine.easylocalpaymap.model.service.LocalPlaceRepository
import com.antoine.easylocalpaymap.model.service.OpenDataService
import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
import org.koin.dsl.module
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

val apiModule = module {
    single<OpenDataService> {
        Retrofit.Builder()
            .baseUrl("https://openapi.gg.go.kr/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(OpenDataService::class.java)
    }
    factory { LocalPlaceRepository(get()) }
}

val appModules = listOf(apiModule)

위의 코드를 보면, 몇가지 새로운 키워드가 보인다. 그에 대한 설명은 아래와 같다.

  • module - Koin module을 생성할때 사용
  • factory - 요청시 마다 새로운 인스턴스를 생성하여 제공
  • single - 인스턴스를 싱글톤으로 생성하여 제공
  • get - 인스턴스가 필요한 곳에 알맞게 의존성 주입

우리가 기존에 MainActivity에 선언했던 retrofit 객체를 싱글톤으로 선언하고 LocalPlaceRepository를 통해 주입하여 사용한다.

 

이제 최상위 Package에서 MyApplication라는 클래스를 생성하고 아래의 코드를 집어넣는다.

import android.app.Application
import com.antoine.easylocalpaymap.di.appModules
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        startKoin {
            androidContext(this@MyApplication)
            modules(appModules)
        }
    }
}

MyApplication 클래스는 Application을 상속 받음으로써 App이 실행될때 최초에 onCreate를 호출한다. 

이 때, startKoin을 통해 생성한 모듈들을 추가해준다.

 

이제 MainActivity에서 이전에 생성했던 LocalPlaceRepository를 DI를 통해 생성해보자.

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.antoine.easylocalpaymap.model.data.LocalPayPlace
import com.antoine.easylocalpaymap.model.service.LocalPlaceRepository
import kotlinx.android.synthetic.main.activity_main.*
import org.koin.android.ext.android.inject
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MainActivity : AppCompatActivity(), Callback<LocalPayPlace> {

    private val model: LocalPlaceRepository by inject()

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

        btnTest.setOnClickListener {
            model.getOlaceList("1","1","용인시","기흥구 죽전로","").enqueue(this)
        }
    }

    override fun onFailure(call: Call<LocalPayPlace>, t: Throwable) {
        TODO("Not yet implemented")
    }

    override fun onResponse(call: Call<LocalPayPlace>, response: Response<LocalPayPlace>) {
        TODO("Not yet implemented")
    }
}

이제 간단한 키워드를 통해 DI가 자동으로 처리되는 것을 확인할 수 있다.

반응형