diff --git a/build.gradle.kts b/build.gradle.kts index 70363769..f55d68ee 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -31,6 +31,6 @@ subprojects { } tasks.dokkaHtmlMultiModule { - moduleVersion.set("1.2.0-beta01") + moduleVersion.set("1.2.0") outputDirectory.set(rootDir.resolve("docs/static/api")) } diff --git a/docs/blog-jp/2023-03-18-android-components.mdx b/docs/blog-jp/2023-03-18-android-components.mdx new file mode 100644 index 00000000..7898303a --- /dev/null +++ b/docs/blog-jp/2023-03-18-android-components.mdx @@ -0,0 +1,306 @@ +--- +slug: android-components +title: Koject v1.2.0 - Androidコンポーネントの追加 +authors: atsushi +image: /blog/2023-03-18/ogp.png +--- + +import { + Provides, + KojectStart, + AndroidApplication, + ViewModelComponent, + ViewModelCoroutineScope, + ActivityLazyViewModels, + ActivityComponent, + ActivityInject, + ActivityLazyInject, + FragmentComponent, + ComposeComponent, +} from '@site/src/components/CodeLink'; + +# Koject v1.2.0 - Androidコンポーネントの追加 + +![](/blog/2023-03-18/banner.png) + +Koject 1.1.0で[Androidのサポートが強化](/blog/jp/android-support)されたばかりですが、Koject 1.2.0ではさらに複数の機能が追加されています。 +この記事では、v1.2.0の利用方法と、追加された主な機能について紹介します。 + + + +[**Read in English →**](/blog/android-components) + +## v1.1.0から移行する +v1.1.0からv1.2.0に向け、一部のAPIが変更されました。対応が必要になる可能性があります。 + + +
+ [core] The inject() API for Named has changed (#148) + +```kotlin +// Until v1.1.0 +val db1 = inject("db1") +val db2 = inject("db2") + +// Since v1.2.0 +val db1 = inject(Named("db1")) +val db2 = inject(Named("db2")) +``` + +
+ + +
+ [core] The ComponentExtras API has changed. (#157) + +```kotlin +// Until v1.1.0 +@ExperimentalKojectApi +@ComponentExtras(CustomComponent::class) +class CustomComponentExtras( + val extra: ExtraClass +) + +// Since v1.2.0 +@ExperimentalKojectApi +class CustomComponentExtras( + val extra: ExtraClass +): ComponentExtras +``` + +
+ + +
+ [android] injectViewModels() has been renamed to lazyViewModels() (#149) + + +```kotlin +// 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`パッケージを追加することで利用できます。 + +```kotlin +dependencies { + implementation("com.moriatsushi.koject:koject-android-core:1.2.0") +} +``` + +利用するには、を呼び出す際にメソッドでApplicationを渡す必要があります。 + +```kotlin +class MyApplication : Application() { + override fun onCreate() { + super.onCreate() + + Koject.start { + application(this@MyApplication) + } + } +} +``` + +これにより、 `Application`クラス及びApplication scopeの`Context`クラスが配布されるようになります。 +配布されたクラスは以下のように利用できます。 + +```kotlin +@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の一部の処理を以下のように別クラスに切り出すことが考えられます。 + +```kotlin +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`関数を呼び忘れる可能性があり、あまり安全ではありません。 + +```kotlin +@Provides +class ViewModelHelper { + private lateinit var coroutineScope: CoroutineScope + + fun setup(coroutineScope: CoroutineScope) { + this.coroutineScope = coroutineScope + } + + fun update() { + coroutineScope.launch { + /* ... */ + } + } +} +``` +```kotlin +@Provides +class MyViewModel( + private val helper: TopViewModelHelper +): ViewModel() { + init { + helper.setup(viewModelScope) + } + + fun update() { + helper.update() + } +} +``` + +他にも、Factoryクラスを配布する方法も考えられます。 + +```kotlin +class ViewModelHelper( + private val coroutineScope: CoroutineScope +) { + fun update() { + coroutineScope.launch { + /* ... */ + } + } + + @Provides + class Factory { + fun create(coroutineScope: CoroutineScope): ViewModelHelper { + return ViewModelHelper(coroutineScope) + } + } +} +``` +```kotlin +@Provides +class MyViewModel( + helperFactory: TopViewModelHelper.Factory +): ViewModel() { + private val helper = helperFactory.create(viewMdoelScope) + + fun update() { + helper.update() + } +} +``` + +どちらの方法も動作はしますが、冗長さが残ります。 + +Kojectでは、`ViewModelComponent`を使うことでシンプルに解決することができます。 +`@Provides`アノテーションをつける際にをつけると、アノテーションを使用してViewModel Scopeの`CoroutineScope`をinjectできるようになります。 + + +```kotlin +@ViewModelComponent +@Provides +class MyViewModel( + private val helper: ViewModelHelper +): ViewModel() { + fun update() { + helper.update() + } +} +``` +```kotlin +@ViewModelComponent +@Provides +class ViewModelHelper( + @ViewModelCoroutineScope + private val coroutineScope: CoroutineScope // same as ViewModel.viewModelScope +) { + fun update() { + coroutineScope.launch { + /* ... */ + } + } +} +``` + +`@ViewModelComponent` がついたタイプは、同じ`@ViewModelComponent` がついたタイプにのみinjectが可能な点に注意してください。 +`MyViewModel`クラスにも`@ViewModelComponent`アノテーションを付ける必要があります。 + + +ViewModelを利用する際は関数を使用します。 + +```kotlin +class TopActivity : ComponentActivity() { + private val viewModel: TopViewModel by lazyViewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + /* ... */ + } +} +``` + +この機能は、処理を移譲するだけでなく、ViewModelの処理の一部を共通化したいときにも役に立ちます。 + +ViewModelのより詳しい説明は[Inject ViewModels ドキュメント](/docs/android/viewmodel)を確認してください。 + +## Androidコンポーネントが追加されました +Koject 1.2.0では`ViewModelComponent`に加えて, , が使えるようになりました。 + +`@ActivityComponent`アノテーションが付与されているタイプは、`Activity`もしくは同じ`@ActivityComponent`がついたタイプにのみinjectができます。 +`@ActivityComponent`がついたアノテーションでは、`ComponentActivity`やActivity scopeの`Context`、`CoroutineScope`等が利用できます。 + +```kotlin +@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で利用する際はもしくはを使用してください。 + +```kotlin +class MyActivity: ComponentActivity { + val helper: ActivityHelper by lazyInject() // can be injected +} +``` + +この機能はViewModelと同様に、クラス分割等のリファクタリングに役立てることができます。 + +全てのコンポーネントと、それらが利用できるタイプについては、[Android components ドキュメント](/docs/android/components)で確認できます。 + +## フィードバックをお待ちしています。 +Kojectは基本的な機能はシンプルに保ちつつ、様々な機能追加を行っています。 +足りない機能があれば、ぜひ[Issue](https://github.com/mori-atsushi/koject/issues)から教えて下さい。 diff --git a/docs/blog/2023-03-18-android-components.mdx b/docs/blog/2023-03-18-android-components.mdx new file mode 100644 index 00000000..12026343 --- /dev/null +++ b/docs/blog/2023-03-18-android-components.mdx @@ -0,0 +1,303 @@ +--- +slug: android-components +title: Koject v1.2.0 - What are Android components? +authors: atsushi +image: /blog/2023-03-18/ogp.png +--- + +import { + Provides, + KojectStart, + AndroidApplication, + ViewModelComponent, + ViewModelCoroutineScope, + ActivityLazyViewModels, + ActivityComponent, + ActivityInject, + ActivityLazyInject, + FragmentComponent, + ComposeComponent, +} from '@site/src/components/CodeLink'; + +# Koject v1.2.0 - What are Android components? + +![](/blog/2023-03-18/banner.png) + +While [Android support was recently strengthened in Koject 1.1.0](/blog/android-support), several more features have been added in Koject 1.2.0. +In this article, I'll introduce how to use v1.2.0 and the main features that have been added. + + + +[**日本語で読む →**](/blog/jp/android-components) + +## Migrating from v1.1.0 +Some APIs have been changed from v1.1.0 to v1.2.0, which may require your attention. + +
+ [core] The inject() API for Named has changed (#148) + +```kotlin +// Until v1.1.0 +val db1 = inject("db1") +val db2 = inject("db2") + +// Since v1.2.0 +val db1 = inject(Named("db1")) +val db2 = inject(Named("db2")) +``` + +
+ + +
+ [core] The ComponentExtras API has changed. (#157) + +```kotlin +// Until v1.1.0 +@ExperimentalKojectApi +@ComponentExtras(CustomComponent::class) +class CustomComponentExtras( + val extra: ExtraClass +) + +// Since v1.2.0 +@ExperimentalKojectApi +class CustomComponentExtras( + val extra: ExtraClass +): ComponentExtras +``` + +
+ + +
+ [android] injectViewModels() has been renamed to lazyViewModels() (#149) + + +```kotlin +// Until v1.1.0 +private val viewModel: TopViewModel by injectViewModels() + +// Since v1.2.0 +private val viewModel: TopViewModel by lazyViewModels() +``` + +
+ +## Providing Android Applications +With Koject 1.2.0, it's now possible to provide the Android `Application` class. +This can be done by adding the `koject-android-core` package. + +```kotlin +dependencies { + implementation("com.moriatsushi.koject:koject-android-core:1.2.0") +} +``` + +To use it, you need to pass an `Application` using the method when calling . + +```kotlin +class MyApplication : Application() { + override fun onCreate() { + super.onCreate() + + Koject.start { + application(this@MyApplication) + } + } +} +``` + +This allows the `Application` class and the `Context` class in the Application scope to be provided. +The provided classes can be used like this: + +```kotlin +@Provides +@Singleton +fun provideAppDatabase( + applicationContext: Context // can be injected +): AppDatabase { + return Room.databaseBuilder( + applicationContext, + AppDatabase::class.java, + "database-name" + ).build() +} +``` + +## Inject ViewModel's CoroutineScope +In the Android architecture using ViewModels, it is common for the ViewModel class to become bloated with code. One solution is to extract some of the code into a separate class, as shown below: + +```kotlin +class MyViewModel: ViewModel() { + private val helper = TopViewModelHelper(viewModelScope) + + fun update() { + helper.update() + } +} +``` +```kotlin +class ViewModelHelper( + private val coroutineScope: CoroutineScope +) { + fun update() { + coroutineScope.launch { + /* ... */ + } + } +} +``` + +When attempting to provide this `ViewModelHelper` using a DI container, it becomes difficult to pass the `CoroutineScope` of the `ViewModel` to the `ViewModelHelper`. + +One possible solution is to pass it through a `setup` function, as shown below. +However, there is a risk of forgetting to call the `setup` function, making this approach less safe. + +```kotlin +@Provides +class ViewModelHelper { + private lateinit var coroutineScope: CoroutineScope + + fun setup(coroutineScope: CoroutineScope) { + this.coroutineScope = coroutineScope + } + + fun update() { + coroutineScope.launch { + /* ... */ + } + } +} +``` +```kotlin +@Provides +class MyViewModel( + private val helper: TopViewModelHelper +): ViewModel() { + init { + helper.setup(viewModelScope) + } + + fun update() { + helper.update() + } +} +``` + +Another possible solution is to provide a `Factory` class. + +```kotlin +class ViewModelHelper( + private val coroutineScope: CoroutineScope +) { + fun update() { + coroutineScope.launch { + /* ... */ + } + } + + @Provides + class Factory { + fun create(coroutineScope: CoroutineScope): ViewModelHelper { + return ViewModelHelper(coroutineScope) + } + } +} +``` +```kotlin +@Provides +class MyViewModel( + helperFactory: TopViewModelHelper.Factory +): ViewModel() { + private val helper = helperFactory.create(viewMdoelScope) + + fun update() { + helper.update() + } +} +``` + +Both methods work, but there is still redundancy. + +With Koject, this can be solved simply by using the `ViewModelComponent`. +When the `@Provides` annotation is accompanied by the annotation, the `CoroutineScope` of the ViewModel Scope can be injected using the annotation. + +```kotlin +@ViewModelComponent +@Provides +class MyViewModel( + private val helper: ViewModelHelper +): ViewModel() { + fun update() { + helper.update() + } +} +``` +```kotlin +@ViewModelComponent +@Provides +class ViewModelHelper( + @ViewModelCoroutineScope + private val coroutineScope: CoroutineScope // same as ViewModel.viewModelScope +) { + fun update() { + coroutineScope.launch { + /* ... */ + } + } +} +``` + +Note that types with the `@ViewModelComponent` annotation can only be injected into other types with the same annotation. +The `MyViewModel` class must also have the `@ViewModelComponent` annotation. + +To use ViewModel, use the function. + +```kotlin +class TopActivity : ComponentActivity() { + private val viewModel: TopViewModel by lazyViewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + /* ... */ + } +} +``` + +This functionality is useful not only for delegating work to other classes, but also for consolidating parts of ViewModel code. + +For more information about ViewModels, please refer to the [Inject ViewModels documentation](/docs/android/viewmodel). + +## Added Android components +Android components have been added to Koject 1.2.0, which now includes , , and , in addition to `ViewModelComponent`. + +Types annotated with `@ActivityComponent` can only be injected into an `Activity` or a type with the same annotation. +`@ActivityComponent` allows you to use `ComponentActivity`, activity scope `Context`, `CoroutineScope`, and more. + +```kotlin +@ActivityComponent +class ActivityHelper( + val activity: ComponentActivity, // can be injected + @ActivityContext + val context: Context, // activity's context + @ActivityCoroutineScope + val coroutineScope: CoroutineScope // same as ComponentActivity.lifecycleScope +) +``` + +When using it in an activity, please use or . + +```kotlin +class MyActivity: ComponentActivity { + val helper: ActivityHelper by lazyInject() // can be injected +} +``` + +Similar to ViewModel, this function can be useful for refactoring such as class division. + +You can check all components and the types they can be used with in the [Android components documentation](/docs/android/components). + +## What more do you need? +Koject aims to keep its basic functions simple while adding various features. +If you find any missing functionality, please let us know via [Issues](https://github.com/mori-atsushi/koject/issues). diff --git a/docs/docs/android/activity.mdx b/docs/docs/android/activity.mdx index c2c8967e..04db9661 100644 --- a/docs/docs/android/activity.mdx +++ b/docs/docs/android/activity.mdx @@ -15,7 +15,7 @@ The following dependency is required to take advantage of the additional support ```kotlin dependencies { - implementation("com.moriatsushi.koject:koject-android-activity:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-android-activity:1.2.0") } ``` diff --git a/docs/docs/android/application.mdx b/docs/docs/android/application.mdx index 650c0c72..79a9ac25 100644 --- a/docs/docs/android/application.mdx +++ b/docs/docs/android/application.mdx @@ -17,7 +17,7 @@ To get started, add the following dependencies for Android: ```kotlin dependencies { - implementation("com.moriatsushi.koject:koject-android-core:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-android-core:1.2.0") } ``` diff --git a/docs/docs/android/fragment.mdx b/docs/docs/android/fragment.mdx index 3f64e814..f3175343 100644 --- a/docs/docs/android/fragment.mdx +++ b/docs/docs/android/fragment.mdx @@ -15,7 +15,7 @@ The following dependency is required to take advantage of the additional support ```kotlin dependencies { - implementation("com.moriatsushi.koject:koject-android-fragment:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-android-fragment:1.2.0") } ``` diff --git a/docs/docs/android/viewmodel.mdx b/docs/docs/android/viewmodel.mdx index cf289092..2fb3f43f 100644 --- a/docs/docs/android/viewmodel.mdx +++ b/docs/docs/android/viewmodel.mdx @@ -15,11 +15,11 @@ Add the appropriate dependencies for the injection target: ```kotlin dependencies { // Inject ViewModel into Activity - implementation("com.moriatsushi.koject:koject-android-activity:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-android-activity:1.2.0") // Inject ViewModel into Fragment - implementation("com.moriatsushi.koject:koject-android-fragment:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-android-fragment:1.2.0") // ViewModelFactory only - implementation("com.moriatsushi.koject:koject-android-viewmodel:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-android-viewmodel:1.2.0") } ``` diff --git a/docs/docs/compose/core.mdx b/docs/docs/compose/core.mdx index 187c5975..d8475c69 100644 --- a/docs/docs/compose/core.mdx +++ b/docs/docs/compose/core.mdx @@ -14,7 +14,7 @@ To use Koject for injection into Composable functions, you need to add the follo ```kotlin dependencies { - implementation("com.moriatsushi.koject:koject-compose-core:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-compose-core:1.2.0") } ``` diff --git a/docs/docs/compose/viewmodel.mdx b/docs/docs/compose/viewmodel.mdx index 872a55bc..7331ac00 100644 --- a/docs/docs/compose/viewmodel.mdx +++ b/docs/docs/compose/viewmodel.mdx @@ -18,7 +18,7 @@ To inject ViewModels into Composable, add the following dependencies: ```kotlin dependencies { - implementation("com.moriatsushi.koject:koject-compose-viewmodel:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-compose-viewmodel:1.2.0") } ``` diff --git a/docs/docs/setup.mdx b/docs/docs/setup.mdx index 7949275b..a898fdc2 100644 --- a/docs/docs/setup.mdx +++ b/docs/docs/setup.mdx @@ -27,7 +27,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { -+ implementation("com.moriatsushi.koject:koject-core:1.2.0-beta01") ++ implementation("com.moriatsushi.koject:koject-core:1.2.0") } } } @@ -35,7 +35,7 @@ kotlin { dependencies { // Add it according to your targets. -+ val processor = "com.moriatsushi.koject:koject-processor-app:1.2.0-beta01" ++ val processor = "com.moriatsushi.koject:koject-processor-app:1.2.0" + add("kspAndroid", processor) + add("kspJvm", processor) + add("kspJs", processor) @@ -55,8 +55,8 @@ plugins { } dependencies { -+ implementation("com.moriatsushi.koject:koject-core:1.2.0-beta01") -+ ksp("com.moriatsushi.koject:koject-processor-app1.2.0-beta01") ++ implementation("com.moriatsushi.koject:koject-core:1.2.0") ++ ksp("com.moriatsushi.koject:koject-processor-app1.2.0") } ``` @@ -69,8 +69,8 @@ This prevents the container from being generated in the library module. ```diff title="build.gradle.kts" dependencies { // Add it according to your targets. -- val processor = "com.moriatsushi.koject:koject-processor-app:1.2.0-beta01" -+ val processor = "com.moriatsushi.koject:koject-processor-lib:1.2.0-beta01" +- val processor = "com.moriatsushi.koject:koject-processor-app:1.2.0" ++ val processor = "com.moriatsushi.koject:koject-processor-lib:1.2.0" add("kspAndroid", processor) add("kspJvm", processor) add("kspJs", processor) @@ -83,9 +83,9 @@ dependencies { ```diff title="build.gradle.kts" dependencies { - implementation("com.moriatsushi.koject:koject-core:1.2.0-beta01") -- ksp("com.moriatsushi.koject:koject-processor-app:1.2.0-beta01") -+ ksp("com.moriatsushi.koject:koject-processor-lib:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-core:1.2.0") +- ksp("com.moriatsushi.koject:koject-processor-app:1.2.0") ++ ksp("com.moriatsushi.koject:koject-processor-lib:1.2.0") } ``` @@ -95,13 +95,13 @@ For Android applications, additional functionality is available by adding the fo ```kotlin dependencies { // Inject Application / Context (Recommended) - implementation("com.moriatsushi.koject:koject-android-core:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-android-core:1.2.0") // Activity support - implementation("com.moriatsushi.koject:koject-android-activity:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-android-activity:1.2.0") // Fragment support - implementation("com.moriatsushi.koject:koject-android-fragment:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-android-fragment:1.2.0") // ViewModelFactory only - implementation("com.moriatsushi.koject:koject-android-viewmodel:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-android-viewmodel:1.2.0") } ``` @@ -110,7 +110,7 @@ If you are using [Jetpack Compose](https://developer.android.com/jetpack/compose ```kotlin dependencies { - implementation("com.moriatsushi.koject:koject-compose-core:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-compose-core:1.2.0") } ``` @@ -119,7 +119,7 @@ To inject Android ViewModel into Composable, use the following package. ```kotlin dependencies { // Inject ViewModel into Composable (Android only) - implementation("com.moriatsushi.koject:koject-compose-viewmodel:1.2.0-beta01") + implementation("com.moriatsushi.koject:koject-compose-viewmodel:1.2.0") } ``` @@ -128,7 +128,7 @@ Copy the following snippets if you are using [gradle verion catalog](https://doc ```xml title="libs.versions.toml" [versions] -koject = "1.2.0-beta01" +koject = "1.2.0" [libraries] koject-core = { group = "com.moriatsushi.koject", name = "koject-core", version.ref = "koject" } diff --git a/docs/static/blog/2023-03-18/banner.png b/docs/static/blog/2023-03-18/banner.png new file mode 100644 index 00000000..84a79f5a Binary files /dev/null and b/docs/static/blog/2023-03-18/banner.png differ diff --git a/docs/static/blog/2023-03-18/ogp.png b/docs/static/blog/2023-03-18/ogp.png new file mode 100644 index 00000000..263143f6 Binary files /dev/null and b/docs/static/blog/2023-03-18/ogp.png differ diff --git a/gradle.properties b/gradle.properties index 771a21b0..2ec247c5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ android.useAndroidX=true SONATYPE_HOST=S01 RELEASE_SIGNING_ENABLED=true GROUP=com.moriatsushi.koject -VERSION_NAME=1.2.0-beta01 +VERSION_NAME=1.2.0 POM_NAME=Koject POM_DESCRIPTION=DI Container library for Kotlin Multiplatform