관리 메뉴

나만을 위한 블로그

[Android] Koin이란? Koin 사용법 본문

Android

[Android] Koin이란? Koin 사용법

참깨빵위에참깨빵 2022. 4. 29. 12:50
728x90
반응형

Koin은 안드로이드에서 사용할 수 있는 코틀린 DI 라이브러리다.

아래는 공식 홈페이지에서 설명하는 Koin이다. 메인 화면과 공식문서에 적힌 내용이 조금 다르다.

 

https://insert-koin.io/

 

Koin - The Kotlin Injection Framework | Koin

Description will go into a meta tag in <head />

insert-koin.io

도구가 아닌 앱에 집중할 수 있는 코틀린 주입 라이브러리

 

https://insert-koin.io/docs/reference/introduction/

 

What is Koin? | Koin

Koin is a pragmatic and lightweight dependency injection framework for Kotlin developers.

insert-koin.io

Koin은 코틀린 개발자를 위한 실용적이고 가벼운 종속성 주입 프레임워크다

 

자바로 사용할 수 있는지 찾아보니 코틀린+자바 혼용 프로젝트에서는 사용할 수 있지만 미디엄의 어떤 글에선 순수 자바 프로젝트에선 사용할 수 없다고 한다. 애초에 공식문서에서 "코틀린 개발자를 위한 라이브러리"라고 못박아놨으니 코틀린으로 쓰자.

 

https://proandroiddev.com/exploring-dependency-injection-in-android-dagger-koin-and-kodein-e219a764be52

 

Exploring Dependency Injection in Android — Dagger, Koin, and Kodein

Dagger has been the reigning dependency injection library in Android for a long time, but a few alternatives have appeared recently. I’ve…

proandroiddev.com

 

 

https://medium.com/android-news/koin-simple-android-di-a47827a707ce

 

Koin - Simple Android DI

Dealing with dependency injection is one complex concept that I managed to understand before coming into the JVM space. I say this because…

medium.com

...(중략) Koin은 코틀린의 DSL을 써서 런타임에 종속성 그래프를 느리게 해결하는 가벼운 종속성 주입 프레임워크다

 

나머지 글들도 비슷하게 설명하기 때문에 그만 찾아보고 DSL이 뭔지 확인해봤다.

 

https://en.wikipedia.org/wiki/Domain-specific_language

 

Domain-specific language - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Computer language specialized to a particular set of requirements or functionality A domain-specific language (DSL) is a computer language specialized to a particular application domai

en.wikipedia.org

DSL(Domain specific language)은 특정 응용 프로그램 도메인에 특화된 컴퓨터 언어다. 도메인 전반에 걸쳐 적용할 수 있는 범용 언어(GPL)와 대조적이다. 웹 페이지용 HTML 같이 공통 도메인에 널리 쓰이는 언어에서 MUSH 소프트 코드와 같이 하나 또는 소수의 소프트웨어에서만 쓰이는 언어에 이르기까지 다양한 DSL이 있다
DSL은 언어 종류에 따라 더 세분화될 수 있으며, 도메인 별 마크업 언어, 도메인 별 모델링 언어(일반적으로 사양 언어), 도메인 별 프로그래밍 언어가 포함된다...(중략)...DSL은 특정 문제 영역, 특정 문제 표현 기법, 특정 솔루션 기법 또는 영역의 다른 측면에 특화될 수 있다

 

https://www.raywenderlich.com/2780058-domain-specific-languages-in-kotlin-getting-started

 

Domain-Specific Languages In Kotlin: Getting Started

In this Kotlin tutorial, learn how to create a DSL using Kotlin lambdas with receivers, builder pattern and extension functions!

www.raywenderlich.com

DSL은 앱의 특정 부분에 특화된 언어다. 코드 일부를 추출해서 재사용 가능하고 이해하기 쉽게 만드는 데 쓰인다. 함수나 메서드와 달리 DSL은 코드를 사용하고 작성하는 방식도 바꾼다. 일반적으로 DSL은 거의 구어체처럼 코드를 읽기 쉽게 만든다. 즉 코드 이면의 아키텍처를 이해하지 못하는 사람들도 코드의 의미를 이해할 수 있다
DSL을 만든단 것은 코드의 특정 부분의 구문을 바꾸는 걸 의미한다. 코틀린에선 람다와 확장 함수, 표현식을 써서 많은 상용구 코드를 제거하고 사용자에게 내부 구현을 숨김으로써 이를 달성한다. 가장 좋은 예시 중 하나는 안드로이드 개발의 중요한 부분인 gradle이다

 

DSL은 대충 이렇다고 한다. 그럼 Koin은 어떻게 쓰는지 확인해보자.

 

먼저 앱 수준 gradle에 Koin 관련 의존성들을 추가해줘야 한다. 모두 오늘 날짜인 22.04.29 기준으로 최신 버전들이다.

 

// Koin
implementation "io.insert-koin:koin-core:3.1.6"
// Koin main features for Android
implementation "io.insert-koin:koin-android:3.1.6"
// No more koin-android-viewmodel, koin-android-scope, koin-android-fragment

// Java Compatibility
implementation "io.insert-koin:koin-android-compat:3.1.6"
// Jetpack WorkManager
implementation "io.insert-koin:koin-androidx-workmanager:3.1.6"
// Navigation Graph
implementation "io.insert-koin:koin-androidx-navigation:3.1.6"
// Jetpack Compose
implementation "io.insert-koin:koin-androidx-compose:3.1.6"

// Koin Unit Test
testImplementation "io.insert-koin:koin-test:3.1.6"
testImplementation "io.insert-koin:koin-test-junit4:3.1.6"

 

마지막 2줄은 주석에 써 있듯 유닛 테스트 관련된 의존성이다. 다음 포스팅에서 Koin을 사용한 유닛 테스트를 포스팅할 예정이라 미리 복붙해두면 좋다.

그리고 Application 클래스를 상속받는 클래스를 만들고 아래 코드를 복붙한다. 만들고 나서 매니페스트에 적어주는 걸 잊지 말자. 그리고 인터넷 권한도 꼭 설정한다.

 

import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin

class BaseApplication: Application() {

    override fun onCreate() {
        super.onCreate()

        /* startKoin {} : GlobalContext API를 쓰기 위해 KoinApplication 컨테이너를 구성하고 GlobalContext에 등록
        * koinApplication {} : KoinApplication 컨테이너 구성 생성 */
        startKoin {
            androidContext(this@BaseApplication)
            androidLogger()
            // 위의 메서드 2개는 없어도 작동하긴 한다
            /* modules() : 컨테이너에 로드할 Koin 모듈 목록 설정. listOf()로 appModule을 구성한 예제가 있었다 */
            modules(appModule)
        }
    }
}
<application
        android:name=".BaseApplication"

 

onCreate()를 여기서 재정의한 다음 startKoin {} 안에 필요한 내용들을 넣는다.

startKoin이 아니라 koinApplication을 써도 작동하는데 둘의 차이는 주석에 간단하게 써놨다. 궁금하다면 따로 찾아보자. 여기선 startKoin을 사용한다.

modules() 안에는 내가 사용할 Koin 모듈들을 넣는다. appModule은 AppModule.kt라는 파일 안에 만든 var 프로퍼티명이다.

 

import org.koin.dsl.module

val appModule = module {
    // single instance of HelloRepository
    /* single : 싱글톤 빈 정의를 제공. 즉 1번만 객체를 생성한다 */
    single<ApiInterface>() { ApiInterfaceImpl() }

    // Simple Presenter Factory
    /* factory : 호출될 때마다 객체 생성 */
    factory { ApiViewModel(get()) }
}

 

single, factory는 Koin과 관련된 키워드니 마찬가지로 궁금하다면 따로 검색해서 찾아보자.

ApiInterface는 레트로핏을 사용할 때 필요한 추상 메서드들을 정의해 놓은 인터페이스, ApiInterfaceImpl은 ApiInterface를 구현하고 이 안에 정의된 추상 메서드를 구현한 클래스다. ApiViewModel은 MVVM 패턴에 쓰이는 그 뷰모델이다.

아래에 각 파일 별 사용된 코드를 파일명 주석과 같이 쓴다. RxJava도 같이 썼는데 이에 관련된 의존성들은 생략한다.

ServerResponse는 서버에서 주는 응답을 프로퍼티 형태로 정의한 data class인데 안에는 boolean 프로퍼티만 존재한다. Koin 테스트를 먼저 하기 위해 프로퍼티 하나만 정의했다.

 

import com.google.gson.annotations.SerializedName

class ServerResponse(@SerializedName("data") val result: Boolean)
// ApiInterface.kt

import io.reactivex.Observable
import retrofit2.http.GET
import retrofit2.http.Query

interface ApiInterface {
    @GET("api/user/email/{userEmail}")
    fun isRegisteredUser(
        @Query("userEmail") email: String
    ): Observable<ServerResponse>
}
// ApiInterfaceImpl.kt

import io.reactivex.Observable

class ApiInterfaceImpl: ApiInterface {
    override fun isRegisteredUser(email: String): Observable<ServerResponse> =
        ApiClient.getApiInterface().isRegisteredUser(email)
}
// ApiClient.kt

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory

object ApiClient {
    private val gson: Gson = GsonBuilder()
        .setLenient()
        .create()

    private val client: OkHttpClient = OkHttpClient.Builder()
        .addInterceptor(setHttpLoggingInterceptor())
        .build()

    private val retrofit: Retrofit = Retrofit.Builder()
        .baseUrl("SERVER_URL")
        .client(client)
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(ScalarsConverterFactory.create())
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build()

    private fun setHttpLoggingInterceptor(): HttpLoggingInterceptor {
        val interceptor = HttpLoggingInterceptor { message: String ->
            Log.e(
                "HttpLoggingInterceptor",
                "message : $message"
            )
        }
        return interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
    }

    fun getApiInterface(): ApiInterface = retrofit.create(ApiInterface::class.java)
}
// ApiViewModel.kt

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.kointest.BaseViewModel
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers.io

class ApiViewModel(private val repo: ApiInterface) : BaseViewModel() {
    val TAG = this.javaClass.simpleName
    private val _isRegisteredUserData = MutableLiveData<ServerResponse>()
    val isRegisteredUserData: LiveData<ServerResponse>
        get() = _isRegisteredUserData

    fun test(email: String): MutableLiveData<ServerResponse> {
        addDisposable(repo.isRegisteredUser(email)
            .subscribeOn(io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({
                it.run {
                    if (result) {
                        Log.e(TAG, "test() 성공 : $result");
                    } else {
                        Log.e(TAG, "test() 실패 : $result");
                    }
                    _isRegisteredUserData.postValue(this)
                }
            }, {
                Log.e(TAG, "error : ${it.printStackTrace()}");
            })
        )
        return _isRegisteredUserData
    }
}

 

ApiViewModel은 BaseViewModel을 상속받는 걸 볼 수 있다.

BaseViewModel은 뷰모델 사용 시 공통적으로 쓰이는 메서드나 프로퍼티들을 모아둔 클래스로, RxJava를 썼기 때문에 CompositeDisposable을 사용하는 메서드 2개를 BaseViewModel 안에 구현해뒀다.

 

import androidx.lifecycle.ViewModel
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable

open class BaseViewModel: ViewModel() {
    private val compositeDisposable = CompositeDisposable()

    fun addDisposable(disposable: Disposable) {
        compositeDisposable.add(disposable)
    }

    override fun onCleared() {
        compositeDisposable.clear()
        super.onCleared()
    }
}

 

이제 메인 액티비티에서 위에서 만든 뷰모델을 통해 RxJava 함수를 호출하면 된다.

 

import org.koin.android.ext.android.inject

class MainActivity : BaseActivity<ActivityMainBinding>() {

    val TAG = this.javaClass.simpleName
    override val layoutResourceId: Int
        get() = R.layout.activity_main
    private val testPresenter: ApiViewModel by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        testPresenter.test("abcd@sdf.com").observe(this) {
            if (it.result) {
                Log.e(TAG, "액티비티에서 확인한 result 값이 true : ${it.result}");
            } else {
                Log.e(TAG, "액티비티에서 확인한 result 값이 false : ${it.result}");
            }
        }
    }
}

 

액티비티도 BaseActivity를 만들어 사용했는데 이 안에서 제네릭으로 데이터 바인딩 관련 처리들을 좀 넣었다.

Koin 관련 설명 및 코드를 참고한 사이트는 밑에 링크로 걸어뒀으니 그걸 참고하면 된다. 원래는 제네릭 안에 데이터 바인딩, 뷰모델을 넣는데 난 데이터 바인딩만 넣어서 사용했다.

 

 

참고한 사이트)

 

https://deque.tistory.com/108?category=984011 

 

Android Kotlin MVVM패턴으로 간단한 검색 앱 만들기 - 1. BaseView, BaseViewModel을 작성하여 MVVM의 토대 만

MVVM 패턴과 Kotlin으로 간단한 앱을 만드는 것에 대해 글을 써보려고 합니다. 저도 정리를 좀 하고, 다른 분들도 도움이 좀 되셨으면 하는 의미에서.. 일단 어떤 앱을 만들고자 하냐면, 간단하게 카

deque.tistory.com

 

반응형
Comments