LoginSignup
3
5

More than 5 years have passed since last update.

Koinつかってみた(Koin 1.0.1)

Last updated at Posted at 2018-10-23

Koinとは

Koin is a DSL, a lightweight container and a pragmatic API.

See : insert-koin.io

※以下のコードは、Koin v1.0.1の場合です。今後変更されることがあります。
ViewModel内のNavigatorがActivityに依存しているので、画面回転後の遷移でNPE起こします。ご注意ください。

導入する

See : Getting started with Android & Java

repositories {
  jcenter()    
}
dependencies {
    // Koin for Android
    compile 'org.koin:koin-android:1.0.1'
    // or Koin for Lifecycle scoping
    compile 'org.koin:koin-androidx-scope:1.0.1'
    // or Koin for Android Architecture ViewModel
    compile 'org.koin:koin-androidx-viewmodel:1.0.1'
}

DIする

今回は、ActivityとFragmentにAACのViewModelをinjectする。
ViewModelには、Repositoryと画面遷移用のNavigatorをコンストラクタで受け取る。

Koinを用いて、ActivityにはViewModelを生成して注入し、
Fragmentには親Activityと同じViewModelを注入する。

登場人物

クラス名 説明
MyApplication -
KoinModule クラス間の依存解決を行う。
MainViewModel AACのViewModelを継承するViewModel。RepositoryとNavigatorに依存する。
Navigator 画面遷移を担当する。Activityに依存する。
Repository リポジトリインターフェース。特に依存関係はなし。
MainActivity 親Activity。MainFragmentを持つ。レイアウトファイルは省略。
MainFragment Fragment。ボタンを一つ持つ。レイアウトファイルは省略。

使用するKoinメソッド

依存解決(Module)する:single{}factory{}viewModel{}
注入する:by viewModel()by sharedViewModel()

登場人物定義

依存解決クラス

KoinModule.kt では、依存関係の解決を行う。
使用できるBeanDefinitionは以下ページを参照。

Defining a singleton

Defining a factory

Scope definition

ViewModel DSL

KoinModule.kt
// Module用のobjectを作成していると管理が楽かも
object KoinModule {
    fun module() = module {
        // Singletonとなり、同じインスタンスが返される
        // Interfaceを返す場合は、型に指定する
        single<Repository> {RepositoryImpl()}

        // factoryは呼び出される毎に生成される
        // 生成に引数が必要な場合は、ラムダ式に引数を追加する。
        // この引数にはgetやinjectでparametersOfを使用して指定する。
        factory<Navigator> {(activity: Activity) -> Navigator(activity)}

        // AACのViewModelの生成を行う
        // 内部で ViewModelProviders.of(~) が呼ばれている。
        // 1つ目のgetは、Navigatorを解決しているので、引数が必要。
        // 2つ目のgetは、Repositoryを解決しているので、引数が不要。
        viewModel {(app: AppCompatActivity) -> MainViewModel(get{ parametersOf(app) }, get())}
    }
}

Application

Koinの初期化を行う。
listOf()には、複数のmoduleを指定できるので、層によって分けるとかしたほうがいいと思う。

MyApplication.kt
class MyApplication: Application() {
    override fun onCreate() {
        super.create()
        startKoin(this, listOf(KoinModule.module()))
    }
}

注入するクラス

MainViewModel.kt
class MainViewModel(private val repository: Repository,
                    private val navigator: Navigator): ViewModel() {

    // Applicationは暗黙的に解決されるため、Moduleには定義不要
    private val application: Application by inject()

    val item = MutableLiveData<String>()

    init {
        item.observeForever {
            repository.getItem().subscribe {
                // use item
            }
        }
    }

    fun init(itemResId: Int) {
        item.postValue(application.getString(itemResId))
    }

    fun next() {
        navigator.navigateNext()
    }
}
Repository.kt
interface Repository {
    fun getItem(): Single<Item>
}
class RepositoryImpl(): Repository {
    override fun getItem(): Single<Item> = Single.just(Item())
}
Navigator.kt
class Navigator(private val activity: Activity) {
    fun navigateToNext() {
        val intent = Intent(activity, NextActivity::class.java)
        activity.startActivity(intent)
    }
}

注入されるクラス

MainActivity.kt
class MainActivity: AppCompatActivity() {

    // ViewModelを生成する際に、自身(AppCompatActivity)を引数として渡す
    private val vm: MainViewModel by viewModel{ parametersOf(this) }

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        vm.init(R.string.text)
    }
}

MainFragment.kt
class MainFragment: Fragment() {

    // sharedViewModel()で親Activityと同一インスタンスのViewModelが取得できる
    private val vm: MainViewModel by sharedViewModel()

    private lateinit var binding: MainFragmentBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate(inflater,R.layout.fragment_main, container, false)
        binding.button.setOnClickListener {v ->
            vm.next()
        }
        return binding.root
    }
}

v1.0より前と比べて

DSLの書き方が変わっているので、参考にする際はバージョンを確認するようにしたほうがいい。

所感

すごくシンプルにDIできて楽だった。
AACのViewModelに対応しているので、AcitvityとFragmentでのViewModel共有が簡単にできる。
コード生成がないので消耗が少ない。なんでビルドできないんや・・・的なのがない。

Daggerとかと比べると、機能的には物足りない。
依存関係を動的に解決するので、解決に失敗しているとコンパイル時にわからず、実行時エラーになる。

コードは雑に書いてるのでコンパイルできないかも・・・
間違ってたりおかしいところあれば連絡ください。

3
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5