본문 바로가기

Android/이론

[안드로이드 Kotlin] Room Database, Repository, ViewModel을 Koin으로 의존성 주입(DI)

728x90
반응형

 

 

  • Koin

DI(Dependecy Injection, 의존성 주입)는 구성요소들의 의존관계가 소스 내부가 아닌 외부에서 설정되도록 하는 디자인 패턴입니다.

public class PetOwner{
    private AnimalType animal;

    public PetOwner() {
        this.animal = new Dog();
    }
}

위와 같은 예제 코드 처럼 PetOwner()클래스는 Dog()클래스에 의존적인 상황이 되겠죠.

따라서 하나의 클래스만 변경하여도 의존한 다른 모듈까지 변경되는 상황이 됩니다.

DI 디자인 패턴을 사용하면 객체의 생성과 사용을 분리시켜 재사용이 유연해집니다.

 

Koin은 다른 의존성프레임워크인 Dagger2와 달리 러닝커브가 높지 않습니다.

기존 Repository는 singletone으로 만들어주어야 해서 따로 싱글톤 코드를 작성했었는데요,

Koin을 사용하면 바로 싱글톤으로 만들어 주입해줄 수가 있습니다.

또한 다른 인터페이스 들도 편리하게 주입해줄 수가 있습니다.

Room Database를 포함한 MVVM 구조에서의 사용을 정리해보겠습니다. 

 

 

 

  • Dependency
buildscript {
    ext.kotlin_version = "1.4.21"
    ext.koin_version = "2.2.1"
    repositories {
        google()
        jcenter() // jcenter 추가
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.2"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.koin:koin-gradle-plugin:$koin_version" // koin 추가
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
...

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'koin' // koin 추가
}

 


// Koin
def koin_version = "2.2.1"
implementation "org.koin:koin-core:$koin_version"
implementation "org.koin:koin-core-ext:$koin_version"
implementation "org.koin:koin-android:$koin_version"
implementation "org.koin:koin-androidx-scope:$koin_version"
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
implementation "org.koin:koin-androidx-fragment:$koin_version"
implementation "org.koin:koin-androidx-ext:$koin_version"
testImplementation "org.koin:koin-test:$koin_version"

 // Room components
def roomVersion = '2.2.6'
implementation "androidx.room:room-runtime:$roomVersion"
kapt "androidx.room:room-compiler:$roomVersion"
implementation "androidx.room:room-ktx:$roomVersion"
androidTestImplementation "androidx.room:room-testing:$roomVersion"

 

  • Room Database
@Database(entities = [MeasureData::class, UserSetting::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun measureDataDao(): MeasureDataDao
    abstract fun userSettingDao(): UserSettingDao
}

Entity와 Dao 두개를 포함한 Database입니다.

 

  • ViewModel, Repository
class MainViewModel(val repository: Repository): ViewModel() {
...
}

class Repository(val userSettingDao: UserSettingDao) {
...
}

viewModel은 Repository 를 인자로, Repository는 dao를 인자로 가지도록 만들었습니다.

 

  • KoinModules.kt
val roomDBModule = module{
    single{
        Room.databaseBuilder(get(),
            AppDatabase::class.java,"database")
            .fallbackToDestructiveMigration()
            .addCallback(object: RoomDatabase.Callback(){
                override fun onCreate(db: SupportSQLiteDatabase) {
                    super.onCreate(db)
                    CoroutineScope(IO).launch{
                        get<AppDatabase>().userSettingDao().insertUserSetting(UserSetting(0,true))
                    }
                }
            })
            .build()
    }
    single{
        get<AppDatabase>().measureDataDao()
    }
    single{
        get<AppDatabase>().userSettingDao()
    }
}
val viewModelModule = module {
    viewModel { MainViewModel(get()) }
}

val repositoryModule = module{
    single{
        Repository(get())
    }
}

get()을 사용하여 인자를 자동으로 주입해줍니다.

repository와 database는 singletone으로 만들어야 하기 때문에 

single { }을 사용하면 됩니다.

factory{ }를 사용하면 injcect 하는 시점에 해당 객체가 계속 생성합니다.

.addCallback()RoomDatabase.Callback()의 onCreate override를 사용하여 db에 맨 처음 들어가야 할 값을 populate해줄 수 있습니다.

 

  • MyApplication.kt
class MyApplication : Application() {

    init{
        instance = this
    }

    companion object {
        lateinit var instance: MyApplication
        fun applicationContext() : Context {
            return instance.applicationContext
        }
    }

    override fun onCreate() {
        super.onCreate()

        startKoin {
            // 로그를 찍어볼 수 있다.
            // 에러확인 - androidLogger(Level.ERROR)
            androidLogger()
            // Android Content를 넘겨준다.
            androidContext(this@MyApplication)
            // assets/koin.properties 파일에서 프로퍼티를 가져옴
            androidFileProperties()
            //module list
            modules(listOf(roomDBModule, repositoryModule, viewModelModule))
        }

    }

}

startKoin을 통해 어플리케이션이 시작될때 Koin을 시작합니다.

 

Manifest에 MyApplication을 추가해 주어야 합니다.

<application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        ...

 

 

  • Repository 주입

factory{} 나 single{}로 만든 모듈을 사용하기 위해서,  by inject()를 사용하여 다음과 같이 간단히 주입하여 사용할 수 있습니다.

private val repository: Repository by inject()

 

  • viewModel 사용
class MainActivity : AppCompatActivity() {

    private val viewModel by viewModel<MainViewModel>()
    ...
    
}

 

위와 같이 한 줄로 viewModel을 주입하여 사용할 수 있습니다.

viewModel에서 사용하는 Repository도 위에서 자동으로 주입하였습니다.

 

  • ViewModel을 공유할 경우

2개 이상의 View가 ViewModel을 공유할 경우, shareViewModel을 사용하면 됩니다.각각 by viewModel() 대신, by sharedViewModel()로 바꾸어 주입해줍니다.

주의할것은, sharedViewModel은 Fragment에서만 쓸 수 있고, 부모 Activity는 by viewModel()로 주입하면 됩니다.

class Fragment1 :Fragment(){

    val viewModel by sharedViewModel<MainViewModel>()
    ...
}

class Fragment2 :Fragment(){

    val viewModel by sharedViewModel<MainViewModel>()
    ...
}

 

 

728x90
반응형