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は以下ページを参照。
// 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を指定できるので、層によって分けるとかしたほうがいいと思う。
class MyApplication: Application() {
override fun onCreate() {
super.create()
startKoin(this, listOf(KoinModule.module()))
}
}
注入するクラス
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()
}
}
interface Repository {
fun getItem(): Single<Item>
}
class RepositoryImpl(): Repository {
override fun getItem(): Single<Item> = Single.just(Item())
}
class Navigator(private val activity: Activity) {
fun navigateToNext() {
val intent = Intent(activity, NextActivity::class.java)
activity.startActivity(intent)
}
}
注入されるクラス
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)
}
}
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とかと比べると、機能的には物足りない。
依存関係を動的に解決するので、解決に失敗しているとコンパイル時にわからず、実行時エラーになる。
コードは雑に書いてるのでコンパイルできないかも・・・
間違ってたりおかしいところあれば連絡ください。