Koject 1.1.0でAndroidのサポートが強化されたばかりですが、Koject 1.2.0ではさらに複数の機能が追加されています。 この記事では、v1.2.0の利用方法と、追加された主な機能について紹介します。
v1.1.0から移行する
v1.1.0からv1.2.0に向け、一部のAPIが変更されました。対応が必要になる可能性があります。
[core] The inject() API for Named has changed (#148)
// Until v1.1.0
val db1 = inject<DB>("db1")
val db2 = inject<DB>("db2")
// Since v1.2.0
val db1 = inject<DB>(Named("db1"))
val db2 = inject<DB>(Named("db2"))
[core] The ComponentExtras API has changed. (#157)
// Until v1.1.0
@ExperimentalKojectApi
@ComponentExtras(CustomComponent::class)
class CustomComponentExtras(
val extra: ExtraClass
)
// Since v1.2.0
@ExperimentalKojectApi
class CustomComponentExtras(
val extra: ExtraClass
): ComponentExtras<CustomComponent>
[android] injectViewModels() has been renamed to lazyViewModels() (#149)
// Until v1.1.0
private val viewModel: TopViewModel by injectViewModels()
// Since v1.2.0
private val viewModel: TopViewModel by lazyViewModels()
Android Applicationを配布する
Koject 1.2.0から、Androidアプリケーションの配布が可能になりました。
koject-android-core
パッケージを追加することで利用できます。
dependencies {
implementation("com.moriatsushi.koject:koject-android-core:1.2.0")
}
利用するには、Koject.start()
を呼び出す際にapplication()
メソッドでApplicationを渡す必要があります。
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Koject.start {
application(this@MyApplication)
}
}
}
これにより、 Application
クラス及びApplication scopeのContext
クラスが配布されるようになります。
配布されたクラスは以下のように利用できます。
@Provides
@Singleton
fun provideAppDatabase(
applicationContext: Context // can be injected
): AppDatabase {
return Room.databaseBuilder(
applicationContext,
AppDatabase::class.java,
"database-name"
).build()
}
ViewModelのCoroutineScopeを配布する
AndroidのViewModelを使ったアーキテクチャでは、ViewModelが肥大化する傾向になります。 その対策としてViewModelの一部の処理を以下のように別クラスに切り出すことが考えられます。
class MyViewModel: ViewModel() {
private val helper = TopViewModelHelper(viewModelScope)
fun update() {
helper.update()
}
}
class ViewModelHelper(
private val coroutineScope: CoroutineScope
) {
fun update() {
coroutineScope.launch {
/* ... */
}
}
}
DIコンテナを使用して、このViewModelHelper
を配布しようとすると、ViewModel
のCoroutineScope
をViewModelHelper
に渡すのが難しくなります。
例えばこのようにsetup
関数を通じて渡す方法が考えられますが、setup
関数を呼び忘れる可能性があり、あまり安全ではありません。
@Provides
class ViewModelHelper {
private lateinit var coroutineScope: CoroutineScope
fun setup(coroutineScope: CoroutineScope) {
this.coroutineScope = coroutineScope
}
fun update() {
coroutineScope.launch {
/* ... */
}
}
}
@Provides
class MyViewModel(
private val helper: TopViewModelHelper
): ViewModel() {
init {
helper.setup(viewModelScope)
}
fun update() {
helper.update()
}
}
他にも、Factoryクラスを配布する方法も考えられます。
class ViewModelHelper(
private val coroutineScope: CoroutineScope
) {
fun update() {
coroutineScope.launch {
/* ... */
}
}
@Provides
class Factory {
fun create(coroutineScope: CoroutineScope): ViewModelHelper {
return ViewModelHelper(coroutineScope)
}
}
}
@Provides
class MyViewModel(
helperFactory: TopViewModelHelper.Factory
): ViewModel() {
private val helper = helperFactory.create(viewMdoelScope)
fun update() {
helper.update()
}
}
どちらの方法も動作はしますが、冗長さが残ります。
Kojectでは、ViewModelComponent
を使うことでシンプルに解決することができます。
@Provides
アノテーションをつける際に@ViewModelComponent
をつけると、@ViewModelCoroutineScope
アノテーションを使用してViewModel ScopeのCoroutineScope
をinjectできるようになります。
@ViewModelComponent
@Provides
class MyViewModel(
private val helper: ViewModelHelper
): ViewModel() {
fun update() {
helper.update()
}
}
@ViewModelComponent
@Provides
class ViewModelHelper(
@ViewModelCoroutineScope
private val coroutineScope: CoroutineScope // same as ViewModel.viewModelScope
) {
fun update() {
coroutineScope.launch {
/* ... */
}
}
}
@ViewModelComponent
がついたタイプは、同じ@ViewModelComponent
がついたタイプにのみinjectが可能な点に注意してください。
MyViewModel
クラスにも@ViewModelComponent
アノテーションを付ける必要があります。
ViewModelを利用する際はComponentActivity.lazyViewModels()
関数を使用します。
class TopActivity : ComponentActivity() {
private val viewModel: TopViewModel by lazyViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/* ... */
}
}
この機能は、処理を移譲するだけでなく、ViewModelの処理の一部を共通化したいときにも役に立ちます。
ViewModelのより詳しい説明はInject ViewModels ドキュメントを確認してください。
Androidコンポーネントが追加されました
Koject 1.2.0ではViewModelComponent
に加えて@ActivityComponent
, @FragmentComponent
, @ComposeComponent
が使えるようになりました。
@ActivityComponent
アノテーションが付与されているタイプは、Activity
もしくは同じ@ActivityComponent
がついたタイプにのみinjectができます。
@ActivityComponent
がついたアノテーションでは、ComponentActivity
やActivity scopeのContext
、CoroutineScope
等が利用できます。
@ActivityComponent
class ActivityHelper(
val activity: ComponentActivity, // can be injected
@ActivityContext
val context: Context, // activity's context
@ActivityCoroutineScope
val coroutineScope: CoroutineScope // same as ComponentActivity.lifecycleScope
)
Activityで利用する際はComponentActivity.inject()
もしくはComponentActivity.lazyInject()
を使用してください。
class MyActivity: ComponentActivity {
val helper: ActivityHelper by lazyInject() // can be injected
}
この機能はViewModelと同様に、クラス分割等のリファクタリングに役立てることができます。
全てのコンポーネントと、それらが利用できるタイプについては、Android components ドキュメントで確認できます。
フィードバックをお待ちしています。
Kojectは基本的な機能はシンプルに保ちつつ、様々な機能追加を行っています。 足りない機能があれば、ぜひIssueから教えて下さい。