From f157868010c39f4f076007468458cdeb9b6f82ae Mon Sep 17 00:00:00 2001 From: Carlos M Date: Thu, 18 Jun 2020 00:47:56 -0700 Subject: [PATCH 1/7] Adds dagger hilt example. --- build.gradle | 1 + buildSrc/src/main/java/dependencies.kt | 3 + hellohilt/.gitignore | 1 + hellohilt/README.md | 67 +++++++ hellohilt/build.gradle | 57 ++++++ hellohilt/proguard-rules.pro | 21 +++ hellohilt/src/main/AndroidManifest.xml | 24 +++ .../mvrx/hellohilt/HelloDaggerApplication.kt | 7 + .../airbnb/mvrx/hellohilt/HelloFragment.kt | 33 ++++ .../airbnb/mvrx/hellohilt/HelloRepository.kt | 14 ++ .../airbnb/mvrx/hellohilt/HelloViewModel.kt | 33 ++++ .../com/airbnb/mvrx/hellohilt/MainActivity.kt | 7 + .../mvrx/hellohilt/base/BaseViewModel.kt | 13 ++ .../airbnb/mvrx/hellohilt/di/AppModule.java | 20 +++ .../hellohilt/di/AssistedViewModelFactory.kt | 58 ++++++ .../di/DaggerMvRxViewModelFactory.kt | 62 +++++++ .../airbnb/mvrx/hellohilt/di/ViewModelKey.kt | 13 ++ .../drawable-v24/ic_launcher_foreground.xml | 34 ++++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++++ .../src/main/res/layout/activity_main.xml | 6 + .../src/main/res/layout/fragment_hello.xml | 41 +++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2963 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4905 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6895 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10413 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15132 bytes hellohilt/src/main/res/values/colors.xml | 6 + hellohilt/src/main/res/values/strings.xml | 6 + hellohilt/src/main/res/values/styles.xml | 11 ++ .../mvrx/hellohilt/HelloViewModelTest.kt | 29 +++ settings.gradle | 1 + 38 files changed, 748 insertions(+) create mode 100644 hellohilt/.gitignore create mode 100644 hellohilt/README.md create mode 100644 hellohilt/build.gradle create mode 100644 hellohilt/proguard-rules.pro create mode 100644 hellohilt/src/main/AndroidManifest.xml create mode 100644 hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloDaggerApplication.kt create mode 100644 hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloFragment.kt create mode 100644 hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloRepository.kt create mode 100644 hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloViewModel.kt create mode 100644 hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/MainActivity.kt create mode 100644 hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/base/BaseViewModel.kt create mode 100644 hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/AppModule.java create mode 100644 hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/AssistedViewModelFactory.kt create mode 100644 hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/DaggerMvRxViewModelFactory.kt create mode 100644 hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/ViewModelKey.kt create mode 100644 hellohilt/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 hellohilt/src/main/res/drawable/ic_launcher_background.xml create mode 100644 hellohilt/src/main/res/layout/activity_main.xml create mode 100644 hellohilt/src/main/res/layout/fragment_hello.xml create mode 100644 hellohilt/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 hellohilt/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 hellohilt/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 hellohilt/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 hellohilt/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 hellohilt/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 hellohilt/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 hellohilt/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 hellohilt/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 hellohilt/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 hellohilt/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 hellohilt/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 hellohilt/src/main/res/values/colors.xml create mode 100644 hellohilt/src/main/res/values/strings.xml create mode 100644 hellohilt/src/main/res/values/styles.xml create mode 100644 hellohilt/src/test/java/com/airbnb/mvrx/hellohilt/HelloViewModelTest.kt diff --git a/build.gradle b/build.gradle index 41cb6e1a1..53e46b62a 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,7 @@ buildscript { classpath "com.android.tools.build:gradle:${Versions.gradlePlugin}" classpath "org.jetbrains.kotlin:kotlin-android-extensions:${Versions.kotlin}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" + classpath "com.google.dagger:hilt-android-gradle-plugin:${Versions.hilt}" // Upload with: // ./gradlew clean assemble uploadArchives --no-daemon --no-parallel diff --git a/buildSrc/src/main/java/dependencies.kt b/buildSrc/src/main/java/dependencies.kt index 79d39abb0..fffeaaa85 100644 --- a/buildSrc/src/main/java/dependencies.kt +++ b/buildSrc/src/main/java/dependencies.kt @@ -25,6 +25,7 @@ object Versions { const val dagger = "2.27" const val daggerAssisted = "0.5.2" const val epoxy = "4.0.0" + const val hilt = "2.28-alpha" const val koin = "2.0.1" const val kotlinCoroutines = "1.4.1" const val lottie = "3.4.0" @@ -54,6 +55,7 @@ object AnnotationProcessors { const val dagger = "com.google.dagger:dagger-compiler:${Versions.dagger}" const val daggerAssisted = "com.squareup.inject:assisted-inject-processor-dagger2:${Versions.daggerAssisted}" const val epoxy = "com.airbnb.android:epoxy-processor:${Versions.epoxy}" + const val hilt = "com.google.dagger:hilt-android-compiler:${Versions.hilt}" const val lifecycle = "androidx.lifecycle:lifecycle-compiler:${Versions.lifecycle}" const val moshi = "com.squareup.moshi:moshi-kotlin-codegen:${Versions.moshi}" const val room = "androidx.room:room-compiler:${Versions.room}" @@ -73,6 +75,7 @@ object Libraries { const val fragment = "androidx.fragment:fragment:${Versions.fragment}" const val fragmentKtx = "androidx.fragment:fragment-ktx:${Versions.fragment}" const val fragmentTesting = "androidx.fragment:fragment-testing:${Versions.fragment}" + const val hilt = "com.google.dagger:hilt-android:${Versions.hilt}" const val junit = "junit:junit:${Versions.junit}" const val koin = "org.koin:koin-android:${Versions.koin}" const val kotlin = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlin}" diff --git a/hellohilt/.gitignore b/hellohilt/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/hellohilt/.gitignore @@ -0,0 +1 @@ +/build diff --git a/hellohilt/README.md b/hellohilt/README.md new file mode 100644 index 000000000..d2ffa1015 --- /dev/null +++ b/hellohilt/README.md @@ -0,0 +1,67 @@ +# Dagger Usage Sample for MvRx + +This module contains a sample app to demonstrate how to ease the usage of Dagger and AssistedInject in apps using MvRx. + +## Key Features + +* **Injecting state into ViewModels with AssistedInject** + + Since the `initialState` parameter is only available at runtime, Dagger can not provide this dependency for us. We need the [AssistedInject](https://github.com/square/AssistedInject) library for this purpose. + +* **Multibinding setup for AssistedInject Factories** + + Every ViewModel using AssistedInject needs a Factory interface annotated with `@AssistedInject.Factory`. These factories are grouped together under a common parent type [AssistedViewModelFactory](src/main/java/com/airbnb/mvrx/hellohilt/di/AssistedViewModelFactory.kt) to enable a Multibinding Dagger setup. + +* **Removing boilerplate from a MvRxViewModelFactory** + + An `MvRxViewModelFactory` is different than an AssistedInject Factory, and is still needed. Using this AssistedInject multibinding setup, most ViewModels will share the same boilerplate logic in their `MvRxViewModelFactory`'s. A [DaggerMvRxViewModelFactory](src/main/java/com/airbnb/mvrx/hellohilt/di/DaggerMvRxViewModelFactory.kt) has been added to eliminate this boilerplate. + +## Example + +* Create your ViewModel with an @AssistedInject constructor, an AssistedInject Factory implementing `AssistedViewModelFactory`, and a companion object implementing `DaggerMvRxViewModelFactory`. + +```kotlin +class MyViewModel @AssistedInject constructor( + @Assisted initialState: MyState, + // and other dependencies +) { + + @AssistedInject.Factory + interface Factory: AssistedViewModelFactory { + override fun create(initialState: MyState): MyViewModel + } + + companion object: DaggerMvRxViewModelFactory(MyViewModel::class.java) +} +``` + +* Tell Dagger to include your ViewModel's AssistedInject Factory in a Multibinding map. + +```kotlin +interface AppModule { + + @Binds + @IntoMap + @ViewModelKey(MyViewModel::class) + fun myViewModelFactory(factory: MyViewModel.Factory): AssistedViewModelFactory<*, *> + +} +``` + +* Add a provision for the Multibinding map in your Dagger component: + +```kotlin + fun viewModelFactories(): Map>, AssistedViewModelFactory<*, *>> +``` + +* With this setup complete, request your ViewModel in a Fragment as usual, using any of MvRx's ViewModel delegates. + +```kotlin +class MyFragment : BaseMvRxFragment() { + val viewModel: MyViewModel by fragmentViewModel() +} +``` + +## How it works + +The `DaggerMvRxViewModelFactory` is used by MvRx to create the requested ViewModel. This factory uses the map of AssistedInject factories provided in the `AppComponent`, retrieves the one for the requested ViewModel and delegates the creation to it. diff --git a/hellohilt/build.gradle b/hellohilt/build.gradle new file mode 100644 index 000000000..f894b6eaf --- /dev/null +++ b/hellohilt/build.gradle @@ -0,0 +1,57 @@ +apply plugin: "com.android.application" +apply plugin: "kotlin-android" +apply plugin: "kotlin-android-extensions" +apply plugin: 'dagger.hilt.android.plugin' +apply plugin: "kotlin-kapt" + +android { + + defaultConfig { + applicationId "com.airbnb.mvrx.helloHilt" + versionCode 1 + versionName "0.0.1" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" + signingConfig signingConfigs.debug + } + } + + signingConfigs { + debug { + storeFile file("debug.keystore") + storePassword "testing" + keyAlias "hellodagger" + keyPassword "testing" + } + } +} + +dependencies { + kapt AnnotationProcessors.daggerAssisted + kapt AnnotationProcessors.hilt + + implementation Libraries.appcompat + implementation Libraries.constraintlayout + implementation Libraries.coreKtx + implementation Libraries.daggerAssisted + implementation Libraries.fragmentKtx + implementation Libraries.hilt + implementation Libraries.rxJava + implementation Libraries.viewModelKtx + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation project(":mvrx") + + debugImplementation Libraries.fragmentTesting + + androidTestImplementation InstrumentedTestLibraries.core + androidTestImplementation InstrumentedTestLibraries.espresso + androidTestImplementation InstrumentedTestLibraries.junit + + testImplementation TestLibraries.junit + testImplementation TestLibraries.mockk +} diff --git a/hellohilt/proguard-rules.pro b/hellohilt/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/hellohilt/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/hellohilt/src/main/AndroidManifest.xml b/hellohilt/src/main/AndroidManifest.xml new file mode 100644 index 000000000..7d035c684 --- /dev/null +++ b/hellohilt/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloDaggerApplication.kt b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloDaggerApplication.kt new file mode 100644 index 000000000..ba57b6d29 --- /dev/null +++ b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloDaggerApplication.kt @@ -0,0 +1,7 @@ +package com.airbnb.mvrx.hellohilt + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class HelloHiltApplication : Application() diff --git a/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloFragment.kt b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloFragment.kt new file mode 100644 index 000000000..0042b578b --- /dev/null +++ b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloFragment.kt @@ -0,0 +1,33 @@ +package com.airbnb.mvrx.hellohilt + +import android.os.Bundle +import android.view.View +import com.airbnb.mvrx.BaseMvRxFragment +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.android.synthetic.main.fragment_hello.helloButton +import kotlinx.android.synthetic.main.fragment_hello.messageTextView + +@AndroidEntryPoint +class HelloFragment : BaseMvRxFragment(R.layout.fragment_hello) { + + val viewModel: HelloViewModel by fragmentViewModel() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + helloButton.setOnClickListener { viewModel.sayHello() } + } + + override fun invalidate() = withState(viewModel) { state -> + helloButton.isEnabled = state.message !is Loading + messageTextView.text = when (state.message) { + is Uninitialized, is Loading -> getString(R.string.hello_fragment_loading_text) + is Success -> state.message() + is Fail -> getString(R.string.hello_fragment_failure_text) + } + } +} \ No newline at end of file diff --git a/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloRepository.kt b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloRepository.kt new file mode 100644 index 000000000..bf56a0402 --- /dev/null +++ b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloRepository.kt @@ -0,0 +1,14 @@ +package com.airbnb.mvrx.hellohilt + +import io.reactivex.Observable +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class HelloRepository @Inject constructor() { + + fun sayHello(): Observable { + return Observable + .just("Hello, world!") + .delay(2, TimeUnit.SECONDS) + } +} \ No newline at end of file diff --git a/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloViewModel.kt b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloViewModel.kt new file mode 100644 index 000000000..b2ec3428a --- /dev/null +++ b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloViewModel.kt @@ -0,0 +1,33 @@ +package com.airbnb.mvrx.hellohilt + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.hellohilt.base.BaseViewModel +import com.airbnb.mvrx.hellohilt.di.AssistedViewModelFactory +import com.airbnb.mvrx.hellohilt.di.DaggerMvRxViewModelFactory +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject + +data class HelloState(val message: Async = Uninitialized) : MvRxState + +class HelloViewModel @AssistedInject constructor( + @Assisted state: HelloState, + private val repo: HelloRepository +) : BaseViewModel(state) { + + init { + sayHello() + } + + fun sayHello() { + repo.sayHello().execute { copy(message = it) } + } + + @AssistedInject.Factory + interface Factory : AssistedViewModelFactory { + override fun create(state: HelloState): HelloViewModel + } + + companion object : DaggerMvRxViewModelFactory(HelloViewModel::class.java) +} \ No newline at end of file diff --git a/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/MainActivity.kt b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/MainActivity.kt new file mode 100644 index 000000000..60d76a3e3 --- /dev/null +++ b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/MainActivity.kt @@ -0,0 +1,7 @@ +package com.airbnb.mvrx.hellohilt + +import androidx.appcompat.app.AppCompatActivity +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : AppCompatActivity(R.layout.activity_main) diff --git a/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/base/BaseViewModel.kt b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/base/BaseViewModel.kt new file mode 100644 index 000000000..5067054f6 --- /dev/null +++ b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/base/BaseViewModel.kt @@ -0,0 +1,13 @@ +package com.airbnb.mvrx.hellohilt.base + +import com.airbnb.mvrx.BaseMvRxViewModel +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.hellohilt.BuildConfig + +/** + * Base class for ViewModels. + * + * This class sets the 'Debug' mode in a [BaseMvRxViewModel] to the corresponding parameter + * in the [BuildConfig] class. + */ +abstract class BaseViewModel(initialState: S) : BaseMvRxViewModel(initialState, BuildConfig.DEBUG) \ No newline at end of file diff --git a/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/AppModule.java b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/AppModule.java new file mode 100644 index 000000000..30e1b5bb8 --- /dev/null +++ b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/AppModule.java @@ -0,0 +1,20 @@ +package com.airbnb.mvrx.hellohilt.di; + +import com.airbnb.mvrx.hellohilt.HelloViewModel; +import com.airbnb.mvrx.hellohilt.HelloViewModel_AssistedFactory; + +import dagger.Binds; +import dagger.Module; +import dagger.hilt.InstallIn; +import dagger.hilt.android.components.ApplicationComponent; +import dagger.multibindings.IntoMap; + +@Module +@InstallIn(ApplicationComponent.class) +public abstract class AppModule { + + @Binds + @IntoMap + @ViewModelKey(HelloViewModel.class) + abstract AssistedViewModelFactory helloViewModelFactory(HelloViewModel_AssistedFactory factory); +} diff --git a/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/AssistedViewModelFactory.kt b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/AssistedViewModelFactory.kt new file mode 100644 index 000000000..d040a15ab --- /dev/null +++ b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/AssistedViewModelFactory.kt @@ -0,0 +1,58 @@ +package com.airbnb.mvrx.hellohilt.di + +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.hellohilt.base.BaseViewModel + +/* + * Serves as a supertype for AssistedInject factories in ViewModels. + * + * We can use this interface as a marker in a Multibinding Dagger setup to populate a Map + * of ViewModel classes with their AssistedInject factories. + * + * This setup is needed because AssistedInject factories are isolated from each other, so this + * interface provides a common parent type to facilitate grouping these factories into a collection. + * When we use it correctly with Dagger, a Map of ViewModels and their factories can be created for us + * automatically. This Map can then be used to retrieve for a ViewModel's factory using its class. + * + * Here's an example for such a setup: + * + * First we define our ViewModel with an @AssistedInject annotated constructor, and a Factory interface + * implementing AssistedViewModelFactory. + * + * class MyViewModel @AssistedInject constructor(...): BaseMvRxViewModel(...) { + * @AssistedInject.Factory + * interface Factory : AssistedViewModelFactory { + * override fun create(state: MyState): MyViewModel + * } + * } + * + * Then we need to create a Dagger Module, which contains methods that @Binds @IntoMap all of our + * AssistedViewModelFactories using a [ViewModelKey]. Notice that the input to these methods is + * the exact type of our AssistedInject factory, but the return type is an AssistedViewModelFactory. + * + * @AssistedModule(includes = [AssistedInject_MyAppModule::class]) + * @Module + * interface MyAppModule { + * @Binds + * @IntoMap + * @ViewModelKey(MyViewModel::class) + * fun myViewModelFactory(factory: MyViewModel.Factory): AssistedViewModelFactory<*, *> + * } + * + * This Module tells Dagger to include MyViewModel.Factory class in the Multibinding map using + * MyViewModel::class as the key. Such a method should be added for **every ViewModel Factory** + * so that they can be identified by Dagger and used for populating the Map. + * + * The generated map can then be injected wherever it is required. + * + * interface AppComponent { + * fun viewModelFactories(): Map>, AssistedViewModelFactory<*, *>> + * } + * + * class SomeClass @Inject constructor( + * val viewModelFactories: Map>, AssistedViewModelFactory<*, *>> + * ) + */ +interface AssistedViewModelFactory, S : MvRxState> { + fun create(state: S): VM +} \ No newline at end of file diff --git a/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/DaggerMvRxViewModelFactory.kt b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/DaggerMvRxViewModelFactory.kt new file mode 100644 index 000000000..025e4db14 --- /dev/null +++ b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/DaggerMvRxViewModelFactory.kt @@ -0,0 +1,62 @@ +package com.airbnb.mvrx.hellohilt.di + +import androidx.fragment.app.FragmentActivity +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import com.airbnb.mvrx.hellohilt.base.BaseViewModel +import dagger.hilt.EntryPoint +import dagger.hilt.EntryPoints +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ApplicationComponent + + +/** + * A [MvRxViewModelFactory] which makes it easy to create instances of a ViewModel + * using its AssistedInject Factory. This class should be implemented by the companion object + * of every ViewModel which uses AssistedInject. + * + * @param viewModelClass The [Class] of the ViewModel being requested for creation + * + * This class accesses the map of [AssistedViewModelFactory]s from [AppComponent] and uses it to + * retrieve the requested ViewModel's factory class. It then creates an instance of this ViewModel + * using the retrieved factory and returns it. + * + * Example: + * + * class MyViewModel @AssistedInject constructor(...): BaseViewModel(...) { + * + * @AssistedInject.Factory + * interface Factory : AssistedViewModelFactory { + * ... + * } + * + * companion object : DaggerMvRxViewModelFactory(MyViewModel::class.java) + * + * } + */ +abstract class DaggerMvRxViewModelFactory, S : MvRxState>( + private val viewModelClass: Class> +) : MvRxViewModelFactory { + + override fun create(viewModelContext: ViewModelContext, state: S): VM? { + return createViewModel(viewModelContext.activity, state) + } + + private fun , S : MvRxState> createViewModel(fragmentActivity: FragmentActivity, state: S): VM { + val viewModelFactoryMap = EntryPoints.get( + fragmentActivity.applicationContext, DaggerMvrxViewModelFactoryEntryPoint::class.java + ).viewModelFactories + val viewModelFactory = viewModelFactoryMap[viewModelClass] + @Suppress("UNCHECKED_CAST") + val castedViewModelFactory = viewModelFactory as? AssistedViewModelFactory + val viewModel = castedViewModelFactory?.create(state) + return viewModel as VM + } +} + +@EntryPoint +@InstallIn(ApplicationComponent::class) +interface DaggerMvrxViewModelFactoryEntryPoint { + val viewModelFactories: Map>, AssistedViewModelFactory<*, *>> +} \ No newline at end of file diff --git a/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/ViewModelKey.kt b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/ViewModelKey.kt new file mode 100644 index 000000000..017e2c78f --- /dev/null +++ b/hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/ViewModelKey.kt @@ -0,0 +1,13 @@ +package com.airbnb.mvrx.hellohilt.di + +import com.airbnb.mvrx.hellohilt.base.BaseViewModel +import dagger.MapKey +import kotlin.reflect.KClass + +/** + * A [MapKey] for populating a map of ViewModels and their factories. + */ +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION) +@MapKey +annotation class ViewModelKey(val value: KClass>) \ No newline at end of file diff --git a/hellohilt/src/main/res/drawable-v24/ic_launcher_foreground.xml b/hellohilt/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..1f6bb2906 --- /dev/null +++ b/hellohilt/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/hellohilt/src/main/res/drawable/ic_launcher_background.xml b/hellohilt/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..0d025f9bf --- /dev/null +++ b/hellohilt/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hellohilt/src/main/res/layout/activity_main.xml b/hellohilt/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..19f6693dd --- /dev/null +++ b/hellohilt/src/main/res/layout/activity_main.xml @@ -0,0 +1,6 @@ + + diff --git a/hellohilt/src/main/res/layout/fragment_hello.xml b/hellohilt/src/main/res/layout/fragment_hello.xml new file mode 100644 index 000000000..5e5c229ca --- /dev/null +++ b/hellohilt/src/main/res/layout/fragment_hello.xml @@ -0,0 +1,41 @@ + + + +