diff --git a/build.gradle b/build.gradle
index 778045728..ddb8a9eb5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,6 +7,7 @@ buildscript {
}
dependencies {
classpath libs.androidGradle
+ classpath libs.anvilGradle
classpath libs.kotlinGradle
classpath libs.hiltGradle
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index fbcef8f04..8e58e39a1 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -10,6 +10,7 @@ ksp = { id = "com.google.devtools.ksp", version = "1.7.10-1.0.6" }
[libraries]
androidGradle = "com.android.tools.build:gradle:_"
+anvilGradle = "com.squareup.anvil:gradle-plugin:_"
autoValue = "com.google.auto.value:auto-value:_"
autoValueAnnotations = "com.google.auto.value:auto-value-annotations:_"
dagger = "com.google.dagger:dagger:_"
diff --git a/sample-anvil/.gitignore b/sample-anvil/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/sample-anvil/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/sample-anvil/README.md b/sample-anvil/README.md
new file mode 100644
index 000000000..7f31483fb
--- /dev/null
+++ b/sample-anvil/README.md
@@ -0,0 +1,13 @@
+# Anvil Sample
+
+The Anvil sample consists of 3 modules:
+* :sample-anvil: The actual application that demonstrates an example features.
+* :sample-anvilcodegen: The [anvil-compiler code generator](https://github.com/square/anvil/blob/main/compiler-api/README.md) that allows `@ContributesViewModel` to setup all of the wiring necessary for constructor dependency injection.
+* :sample-anvilannotations: Shared annotations between the sample and codegen.
+
+The Anvil sample demonstrates how to setup a `@ContributesViewModel` annotation that allows you to do constructor injection from Dagger without any additional wiring such as the creation of a Dagger module. The setup requires a bit of infrastructure which is all included in these three modules. However, once it is set up, it dramatically simplifies the usage of ViewModels and Dagger.
+
+A good starting point is ExampleFeatureFragment. ExampleFeatureFragment and the other classes in that folder demonstrate:
+* How to use `@ContributesViewModel` to do constructor injection.
+* How to create feature/Fragment scoped Dagger Components that are supported by `@ContributesViewModel`.
+* How to set up App/User/Feature Dagger component hierarchies that are commonly used in real world apps.
diff --git a/sample-anvil/build.gradle b/sample-anvil/build.gradle
new file mode 100644
index 000000000..11bc5a871
--- /dev/null
+++ b/sample-anvil/build.gradle
@@ -0,0 +1,53 @@
+apply plugin: "com.android.application"
+apply plugin: "com.squareup.anvil"
+apply plugin: "kotlin-android"
+apply plugin: "kotlin-kapt"
+
+android {
+
+ defaultConfig {
+ applicationId "com.airbnb.mvrx.sample.anvil"
+ versionCode 1
+ versionName "0.0.1"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ multiDexEnabled true
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ signingConfig signingConfigs.debug
+ }
+ }
+
+ signingConfigs {
+ debug {
+ storeFile file("debug.keystore")
+ storePassword "testing"
+ keyAlias "helloDagger"
+ keyPassword "testing"
+ }
+ }
+
+ buildFeatures {
+ viewBinding true
+ }
+}
+
+dependencies {
+ implementation project(":mvrx")
+ implementation project(":utils-view-binding")
+ implementation project(":sample-anvilannotations")
+
+ anvil project(':sample-anvilcodegen')
+
+ kapt libs.daggerCompiler
+
+ implementation libs.appcompat
+ implementation libs.constraintlayout
+ implementation libs.coreKtx
+ implementation libs.dagger
+ implementation libs.fragmentKtx
+ implementation libs.viewModelKtx
+ implementation libs.multidex
+}
diff --git a/sample-anvil/src/main/AndroidManifest.xml b/sample-anvil/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..8554bdb5d
--- /dev/null
+++ b/sample-anvil/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/AnvilSampleApplication.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/AnvilSampleApplication.kt
new file mode 100644
index 000000000..476dd415f
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/AnvilSampleApplication.kt
@@ -0,0 +1,23 @@
+package com.airbnb.mvrx.sample.anvil
+
+import android.app.Application
+import com.airbnb.mvrx.Mavericks
+import com.airbnb.mvrx.sample.anvil.di.DaggerComponentOwner
+import com.airbnb.mvrx.sample.anvil.di.bindings
+
+class AnvilSampleApplication : Application(), DaggerComponentOwner {
+
+ lateinit var appComponent: AppComponent
+ // This can be set or unset as users log in and out.
+ var userComponent: UserComponent? = null
+
+ override val daggerComponent get() = listOfNotNull(appComponent, userComponent)
+
+ override fun onCreate() {
+ super.onCreate()
+ appComponent = DaggerAppComponent.create()
+ // Simulate a logged in user
+ userComponent = bindings().userComponentBuilder().user(User("Gabriel Peal")).build()
+ Mavericks.initialize(this)
+ }
+}
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/AppComponent.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/AppComponent.kt
new file mode 100644
index 000000000..de79ba966
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/AppComponent.kt
@@ -0,0 +1,10 @@
+package com.airbnb.mvrx.sample.anvil
+
+import com.airbnb.mvrx.sample.anvil.di.SingleIn
+import com.squareup.anvil.annotations.MergeComponent
+
+interface AppScope
+
+@SingleIn(AppScope::class)
+@MergeComponent(AppScope::class)
+interface AppComponent
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/MainActivity.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/MainActivity.kt
new file mode 100644
index 000000000..795743c23
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.airbnb.mvrx.sample.anvil
+
+import androidx.appcompat.app.AppCompatActivity
+
+class MainActivity : AppCompatActivity(R.layout.activity_main)
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/UserComponent.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/UserComponent.kt
new file mode 100644
index 000000000..2e6ed2fab
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/UserComponent.kt
@@ -0,0 +1,31 @@
+package com.airbnb.mvrx.sample.anvil
+
+import com.airbnb.mvrx.sample.anvil.di.SingleIn
+import com.squareup.anvil.annotations.ContributesTo
+import com.squareup.anvil.annotations.MergeSubcomponent
+import dagger.BindsInstance
+import dagger.Subcomponent
+
+data class User(val name: String)
+
+interface UserScope
+
+@SingleIn(UserScope::class)
+@MergeSubcomponent(UserScope::class)
+interface UserComponent {
+ @Subcomponent.Builder
+ interface Builder {
+ @BindsInstance
+ fun user(user: User): Builder
+ fun build(): UserComponent
+ }
+
+ /**
+ * This is a subcomponent of [AppComponent]. This tells [AppComponent] that it needs to be able to
+ * provide the builder for [UserComponent].
+ */
+ @ContributesTo(AppScope::class)
+ interface ParentBindings {
+ fun userComponentBuilder(): Builder
+ }
+}
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/UserScopedRepository.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/UserScopedRepository.kt
new file mode 100644
index 000000000..438c675a2
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/UserScopedRepository.kt
@@ -0,0 +1,20 @@
+package com.airbnb.mvrx.sample.anvil
+
+import com.airbnb.mvrx.sample.anvil.di.SingleIn
+import kotlinx.coroutines.delay
+import javax.inject.Inject
+
+/**
+ * This doesn't need to be a singleton in the user component but is done just to demonstrate
+ * how to create singletons with [SingleIn].
+ */
+@SingleIn(UserScope::class)
+class UserScopedRepository @Inject constructor(
+ private val user: User,
+) {
+
+ suspend operator fun invoke(): String {
+ delay(2000)
+ return "Hello World, ${user.name}!"
+ }
+}
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/AssistedViewModelFactory.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/AssistedViewModelFactory.kt
new file mode 100644
index 000000000..748d51d85
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/AssistedViewModelFactory.kt
@@ -0,0 +1,55 @@
+package com.airbnb.mvrx.sample.anvil.di
+
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.MavericksViewModel
+
+/**
+ * You do not need to use this type directly. It is used by DaggerMavericksViewModelFactory.
+ *
+ * Serves as a supertype for AssistedInject factories in ViewModels.
+ *
+ * We use this interface as a marker in a Multibinding Dagger setup to populate a Map
+ * of ViewModel classes with their AssistedInject factories.
+ *
+ * First we define our ViewModel with an @AssistedInject annotated constructor, and a Factory interface
+ * implementing AssistedViewModelFactory.
+ *
+ * class MyViewModel @AssistedInject constructor(
+ * @Assisted initialState: MyState,
+ * …
+ * ): MavericksViewModel(...) {
+ * @AssistedFactory
+ * 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.
+ *
+ * @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 : MavericksState> {
+ fun create(state: S): VM
+}
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/Bindings.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/Bindings.kt
new file mode 100644
index 000000000..f6c7374fd
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/Bindings.kt
@@ -0,0 +1,53 @@
+package com.airbnb.mvrx.sample.anvil.di
+
+import android.content.Context
+import android.content.ContextWrapper
+import androidx.fragment.app.Fragment
+
+/**
+ * Use this to get the Dagger "Bindings" for your module. Bindings are used if you need to directly interact with a dagger component such as:
+ * * an inject function: `inject(MyFragment frag)`
+ * * an explicit getter: `fun myClass(): MyClass`
+ *
+ * Anvil will make your Dagger component implement these bindings so that you can call any of these functions on an instance of your component.
+ *
+ * [bindings] will walk up the Fragment/Activity hierarchy and check for [DaggerComponentOwner] to see if any of its components implement the
+ * specified bindings. Most of the time this will "just work" and you don't have to think about it.
+ *
+ * For example, if your class has @Inject properties:
+ * 1) Create an bindings interface such as `YourModuleBindings`
+ * 1) Add an inject function like `fun inject(yourClass: YourClass)`
+ * 2) Contribute your interface to the correct component via `@ContributesTo(AppScope::class)`.
+ * 3) Call bindings().inject(this).
+ */
+inline fun Context.bindings() = bindings(T::class.java)
+
+/**
+ * @see bindings
+ */
+inline fun Fragment.bindings() = bindings(T::class.java)
+
+/** Use no-arg extension function instead: [Context.bindings] */
+fun Context.bindings(klass: Class): T {
+ // search dagger components in the context hierarchy
+ return generateSequence(this) { (it as? ContextWrapper)?.baseContext }
+ .plus(applicationContext)
+ .filterIsInstance()
+ .map { it.daggerComponent }
+ .flatMap { if (it is Collection<*>) it else listOf(it) }
+ .filterIsInstance(klass)
+ .firstOrNull()
+ ?: error("Unable to find bindings for ${klass.name}")
+}
+
+/** Use no-arg extension function instead: [Fragment.bindings] */
+fun Fragment.bindings(klass: Class): T {
+ // search dagger components in fragment hierarchy, then fallback to activity and application
+ return generateSequence(this, Fragment::getParentFragment)
+ .filterIsInstance()
+ .map { it.daggerComponent }
+ .flatMap { if (it is Collection<*>) it else listOf(it) }
+ .filterIsInstance(klass)
+ .firstOrNull()
+ ?: requireActivity().bindings(klass)
+}
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/DaggerComponentOwner.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/DaggerComponentOwner.kt
new file mode 100644
index 000000000..95dfa3a39
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/DaggerComponentOwner.kt
@@ -0,0 +1,19 @@
+package com.airbnb.mvrx.sample.anvil.di
+
+/**
+ * A [DaggerComponentOwner] is anything that "owns" a Dagger Component. The owner can be a Application, Activity, or Fragment.
+ *
+ * When using [bindings] or when creating a ViewModel, the infrastructure provided will walk up the Fragment tree, then check
+ * the Activity, then the Application for any [DaggerComponentOwner] that can provide ViewModels.
+ *
+ * In this sample:
+ * * [com.airbnb.mvrx.sample.anvil.AppComponent] is owned by the Application class.
+ * * [com.airbnb.mvrx.sample.anvil.UserComponent] is subcomponent of AppComponent and injected with the current logged in user.
+ * It is set/cleared in the Application class.
+ * * [com.airbnb.mvrx.sample.anvil.ExampleFeatureComponent] is a subcomponent of UserComponent and
+ * owned by [com.airbnb.mvrx.sample.anvil.ExampleFeatureFragment].
+ */
+interface DaggerComponentOwner {
+ /** This is either a component, or a list of components. */
+ val daggerComponent: Any
+}
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/DaggerMavericksViewModelFactory.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/DaggerMavericksViewModelFactory.kt
new file mode 100644
index 000000000..229f89066
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/DaggerMavericksViewModelFactory.kt
@@ -0,0 +1,68 @@
+package com.airbnb.mvrx.sample.anvil.di
+
+import com.airbnb.mvrx.FragmentViewModelContext
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.MavericksViewModel
+import com.airbnb.mvrx.MavericksViewModelFactory
+import com.airbnb.mvrx.ViewModelContext
+
+/**
+ * To connect Mavericks ViewModel creation with Anvil's dependency injection, add the following to your MavericksViewModel.
+ *
+ * Example:
+ *
+ * @ContributesViewModel(YourScope::class)
+ * class MyViewModel @AssistedInject constructor(
+ * @Assisted initialState: MyState,
+ * …,
+ * ): MavericksViewModel(...) {
+ * …
+ *
+ * companion object : MavericksViewModelFactory by daggerMavericksViewModelFactory()
+ * }
+ */
+
+inline fun , S : MavericksState> daggerMavericksViewModelFactory() = DaggerMavericksViewModelFactory(VM::class.java)
+
+/**
+ * A [MavericksViewModelFactory] 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 via [daggerMavericksViewModelFactory].
+ *
+ * @param viewModelClass The [Class] of the ViewModel being requested for creation
+ *
+ * This class accesses the map of ViewModel class to [AssistedViewModelFactory]s from the nearest [DaggerComponentOwner] 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.
+ * @see daggerMavericksViewModelFactory
+ */
+class DaggerMavericksViewModelFactory, S : MavericksState>(
+ private val viewModelClass: Class
+) : MavericksViewModelFactory {
+
+ override fun create(viewModelContext: ViewModelContext, state: S): VM {
+ val bindings: DaggerMavericksBindings = when (viewModelContext) {
+ is FragmentViewModelContext -> viewModelContext.fragment.bindings()
+ else -> viewModelContext.activity.bindings()
+ }
+ val viewModelFactoryMap = bindings.viewModelFactories()
+ val viewModelFactory = viewModelFactoryMap[viewModelClass] ?: error("Cannot find ViewModelFactory for ${viewModelClass.name}.")
+
+ @Suppress("UNCHECKED_CAST")
+ val castedViewModelFactory = viewModelFactory as? AssistedViewModelFactory
+ val viewModel = castedViewModelFactory?.create(state)
+ return viewModel as VM
+ }
+}
+
+/**
+ * These Anvil/Dagger bindings are used by [DaggerMavericksViewModelFactory]. The factory will find the nearest [DaggerComponentOwner]
+ * that implements these bindings. It will then attempt to retrieve the [AssistedViewModelFactory] for the given ViewModel class.
+ *
+ * In this example, this bindings class is implemented by [com.airbnb.mvrx.sample.anvil.feature.ExampleFeatureComponent] because
+ * it provides the [com.airbnb.mvrx.sample.anvil.feature.ExampleFeatureViewModel]. Any component that will generate ViewModels should
+ * either implement this directly or have this added via `@ContributesTo(YourScope::class)`.
+ */
+interface DaggerMavericksBindings {
+ fun viewModelFactories(): Map>, AssistedViewModelFactory<*, *>>
+}
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/FragmentComponent.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/FragmentComponent.kt
new file mode 100644
index 000000000..05275afae
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/FragmentComponent.kt
@@ -0,0 +1,43 @@
+package com.airbnb.mvrx.sample.anvil.di
+
+import android.app.Application
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.airbnb.mvrx.sample.anvil.AnvilSampleApplication
+import kotlinx.coroutines.CoroutineScope
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * Use this property delegate to create a DaggerComponent scoped to a Fragment.
+ *
+ * The factory lambda will be given Application as well as a CoroutineScope that will have the same lifecycle as this component.
+ *
+ * In the factory, return the instance of the Dagger component. Most likely, it will look something like:
+ * ```
+ * app.bindings().createMyComponent()
+ * ```
+ *
+ * The returned component will be stored inside of a backing Jetpack ViewModel and will have the equivalent lifecycle as it.
+ * That means that during configuration changes or while on the back stack, your Dagger component will continue to operate.
+ * When the Fragment is destroyed for the last time (equivalent to ViewModel.onCleared()), the provided CoroutineScope will be canceled.
+ *
+ * It may be convenient to bind the CoroutineScope as an instance in your Dagger component so it can be injected into singleton objects.
+ */
+inline fun Fragment.fragmentComponent(
+ crossinline factory: (CoroutineScope, AnvilSampleApplication) -> T
+) = lazy {
+ ViewModelProvider(this)[DaggerComponentHolderViewModel::class.java].get(factory)
+}
+
+/**
+ * @see fragmentComponent
+ */
+class DaggerComponentHolderViewModel(app: Application) : AndroidViewModel(app) {
+ val map = ConcurrentHashMap, Any>()
+
+ inline fun get(factory: (CoroutineScope, AnvilSampleApplication) -> T): T {
+ return map.getOrPut(T::class.java) { factory(viewModelScope, getApplication()) } as T
+ }
+}
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/SingleIn.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/SingleIn.kt
new file mode 100644
index 000000000..cc99b9a52
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/SingleIn.kt
@@ -0,0 +1,34 @@
+package com.airbnb.mvrx.sample.anvil.di
+
+import javax.inject.Scope
+import kotlin.reflect.KClass
+
+/**
+ * This annotation lets you use the same annotation to represent which scope you want to contribute an Anvil object to
+ * and also as `@SingleIn(YourScope::class)`.
+ *
+ * Without `@SingleIn`, an AppComponent contribution might look like this:
+ * ```
+ * @Singleton
+ * @ContributesBinding(AppScope::class)
+ * class YourClassImpl : YourClass
+ * ```
+ * Singleton is a well defined pattern for AppScope but the scope naming becomes more confusing once you start defining your
+ * own components.
+ * `@SingleIn` prevents you from memorizing two names per component. The above example becomes:
+ * ```
+ * @SingleIn(AppScope::class)
+ * @ContributesBinding(AppScope::class)
+ * class YourClassImpl : YourClass
+ * ```
+ *
+ * And custom components would look like:
+ * ```
+ * @SingleIn(YourScope::class)
+ * @ContributesBinding(YourScope::class)
+ * class YourClassImpl : YourClass
+ * ```
+ */
+@Scope
+@Retention(AnnotationRetention.RUNTIME)
+annotation class SingleIn(val clazz: KClass<*>)
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/ViewModelKey.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/ViewModelKey.kt
new file mode 100644
index 000000000..8eff73db4
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/di/ViewModelKey.kt
@@ -0,0 +1,13 @@
+package com.airbnb.mvrx.sample.anvil.di
+
+import com.airbnb.mvrx.MavericksViewModel
+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>)
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/feature/ExampleFeatureComponent.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/feature/ExampleFeatureComponent.kt
new file mode 100644
index 000000000..c110fcde2
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/feature/ExampleFeatureComponent.kt
@@ -0,0 +1,51 @@
+package com.airbnb.mvrx.sample.anvil.feature
+
+import com.airbnb.mvrx.sample.anvil.UserComponent
+import com.airbnb.mvrx.sample.anvil.UserScope
+import com.airbnb.mvrx.sample.anvil.di.DaggerMavericksBindings
+import com.airbnb.mvrx.sample.anvil.di.SingleIn
+import com.squareup.anvil.annotations.ContributesTo
+import com.squareup.anvil.annotations.MergeSubcomponent
+import dagger.BindsInstance
+import dagger.Subcomponent
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * This should be used as the scope for any `@SingleIn(ExampleFeatureScope::class)` objects.
+ *
+ * The reason this is a named class rather than just binding `CoroutineScope` directly is that there will likely
+ * be a CoroutineScope associated with each Dagger component in the hierarchy (AppComponent, UserComponent, ExampleFeatureComponent).
+ * Using a named class is the best way to be explicit and ensure that there aren't duplicate bindings and that the correct
+ * scope is always used.
+ */
+class ExampleFeatureCoroutineScope(private val parentScope: CoroutineScope) : CoroutineScope by parentScope
+
+interface ExampleFeatureScope
+
+/**
+ * Any component that provides ViewModels via [com.airbnb.mvrx.sample.anvil.annotation.ContributesViewModel] should
+ * implement [DaggerMavericksBindings].
+ */
+@SingleIn(ExampleFeatureScope::class)
+@MergeSubcomponent(ExampleFeatureScope::class)
+interface ExampleFeatureComponent : DaggerMavericksBindings {
+ @Subcomponent.Builder
+ interface Builder {
+ /**
+ * This CoroutineScope will have the same lifecycle as this component. Any objects annotated with
+ * `@SingleIn(ExampleFeatureScope::class)` that need a CoroutineScope should use this.
+ */
+ @BindsInstance
+ fun coroutineScope(coroutineScope: ExampleFeatureCoroutineScope): Builder
+ fun build(): ExampleFeatureComponent
+ }
+
+ /**
+ * This is a subcomponent of [UserComponent]. This tells [UserComponent] that it needs to be able to
+ * provide the builder for [ExampleFeatureCoroutineScope].
+ */
+ @ContributesTo(UserScope::class)
+ interface ParentBindings {
+ fun exampleFeatureComponentBuilder(): Builder
+ }
+}
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/feature/ExampleFeatureFragment.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/feature/ExampleFeatureFragment.kt
new file mode 100644
index 000000000..8d11341cb
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/feature/ExampleFeatureFragment.kt
@@ -0,0 +1,90 @@
+package com.airbnb.mvrx.sample.anvil.feature
+
+import androidx.fragment.app.Fragment
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.MavericksView
+import com.airbnb.mvrx.MavericksViewModel
+import com.airbnb.mvrx.MavericksViewModelFactory
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.sample.anvil.R
+import com.airbnb.mvrx.sample.anvil.UserScopedRepository
+import com.airbnb.mvrx.sample.anvil.annotation.ContributesViewModel
+import com.airbnb.mvrx.sample.anvil.databinding.HelloFragmentBinding
+import com.airbnb.mvrx.sample.anvil.di.DaggerComponentOwner
+import com.airbnb.mvrx.sample.anvil.di.bindings
+import com.airbnb.mvrx.sample.anvil.di.daggerMavericksViewModelFactory
+import com.airbnb.mvrx.sample.anvil.di.fragmentComponent
+import com.airbnb.mvrx.viewbinding.viewBinding
+import com.airbnb.mvrx.withState
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+
+data class ExampleFeatureState(
+ val title: Async = Uninitialized,
+ val description: Async = Uninitialized,
+) : MavericksState
+
+/**
+ * The following code and the companion object contain everything needed to wire up constructor injection with Anvil.
+ *
+ * The `sample-anvilcodegen` module contains the code generation that happens for [ContributesViewModel].
+ *
+ * Note that this ViewModel is created in [ExampleFeatureFragment] which is a [DaggerComponentOwner] for [ExampleFeatureComponent] which means
+ * that this can inject anything from [ExampleFeatureComponent] as well. And because [ExampleFeatureComponent] is a subcomponent of
+ * [com.airbnb.mvrx.sample.anvil.UserComponent] and [com.airbnb.mvrx.sample.anvil.AppComponent], it can inject anything from those components as well.
+ */
+@ContributesViewModel(ExampleFeatureScope::class)
+class ExampleFeatureViewModel @AssistedInject constructor(
+ @Assisted initialState: ExampleFeatureState,
+ userScopedRepo: UserScopedRepository,
+ featureScopedRepo: ExampleFeatureScopedRepository,
+) : MavericksViewModel(initialState) {
+
+ init {
+ suspend {
+ userScopedRepo()
+ }.execute { copy(title = it) }
+ suspend {
+ featureScopedRepo()
+ }.execute { copy(description = it) }
+ }
+
+ companion object : MavericksViewModelFactory by daggerMavericksViewModelFactory()
+}
+
+class ExampleFeatureFragment : Fragment(R.layout.hello_fragment), MavericksView, DaggerComponentOwner {
+ private val binding: HelloFragmentBinding by viewBinding()
+ private val viewModel: ExampleFeatureViewModel by fragmentViewModel()
+
+ /**
+ * We are using this Fragment as the owner of a Dagger Component. In a real world example, this Fragment
+ * could have child fragments and/or be a container for an entire flow or large feature.
+ * With the [bindings] methods, any ViewModels for this fragment or any child fragments can inject objects
+ * from this component.
+ *
+ * If you don't need a custom dagger component for your Fragment, you can omit this and the [DaggerComponentOwner] interface entirely.
+ * In that case, you could contribute the ViewModel to the user or app component depending on what is available from
+ * parent fragments/Activity/Application.
+ */
+ override val daggerComponent by fragmentComponent { scope, _ ->
+ // Note: use `requireActivity().bindings` not `bindings` here or else you will wind up with a StackOverflow in which this
+ // Fragment which is a DaggerComponentOwner will keep searching itself over and over.
+ requireActivity().bindings().exampleFeatureComponentBuilder()
+ .coroutineScope(ExampleFeatureCoroutineScope(scope))
+ .build()
+ }
+
+ override fun invalidate() = withState(viewModel) { state ->
+ requireActivity().title = state.description()
+ binding.title.text = when (state.title) {
+ is Uninitialized, is Loading -> getString(R.string.hello_fragment_loading_text)
+ is Success -> state.title()
+ is Fail -> getString(R.string.hello_fragment_failure_text)
+ }
+ }
+}
diff --git a/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/feature/ExampleFeatureScopedRepository.kt b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/feature/ExampleFeatureScopedRepository.kt
new file mode 100644
index 000000000..602f807a3
--- /dev/null
+++ b/sample-anvil/src/main/java/com/airbnb/mvrx/sample/anvil/feature/ExampleFeatureScopedRepository.kt
@@ -0,0 +1,10 @@
+package com.airbnb.mvrx.sample.anvil.feature
+
+import com.airbnb.mvrx.sample.anvil.di.SingleIn
+import javax.inject.Inject
+
+@SingleIn(ExampleFeatureScope::class)
+class ExampleFeatureScopedRepository @Inject constructor() {
+ @Suppress("FunctionOnlyReturningConstant")
+ operator fun invoke() = "Example Feature"
+}
diff --git a/sample-anvil/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample-anvil/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 000000000..1f6bb2906
--- /dev/null
+++ b/sample-anvil/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-anvil/src/main/res/drawable/ic_launcher_background.xml b/sample-anvil/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000..0d025f9bf
--- /dev/null
+++ b/sample-anvil/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-anvil/src/main/res/layout/activity_main.xml b/sample-anvil/src/main/res/layout/activity_main.xml
new file mode 100644
index 000000000..8b461458f
--- /dev/null
+++ b/sample-anvil/src/main/res/layout/activity_main.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/sample-anvil/src/main/res/layout/hello_fragment.xml b/sample-anvil/src/main/res/layout/hello_fragment.xml
new file mode 100644
index 000000000..9b7a15f90
--- /dev/null
+++ b/sample-anvil/src/main/res/layout/hello_fragment.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/sample-anvil/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample-anvil/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..eca70cfe5
--- /dev/null
+++ b/sample-anvil/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/sample-anvil/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample-anvil/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..eca70cfe5
--- /dev/null
+++ b/sample-anvil/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/sample-anvil/src/main/res/mipmap-hdpi/ic_launcher.png b/sample-anvil/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..898f3ed59
Binary files /dev/null and b/sample-anvil/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/sample-anvil/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample-anvil/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 000000000..dffca3601
Binary files /dev/null and b/sample-anvil/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/sample-anvil/src/main/res/mipmap-mdpi/ic_launcher.png b/sample-anvil/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..64ba76f75
Binary files /dev/null and b/sample-anvil/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/sample-anvil/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample-anvil/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 000000000..dae5e0823
Binary files /dev/null and b/sample-anvil/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/sample-anvil/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample-anvil/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..e5ed46597
Binary files /dev/null and b/sample-anvil/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/sample-anvil/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample-anvil/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..14ed0af35
Binary files /dev/null and b/sample-anvil/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/sample-anvil/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample-anvil/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..b0907cac3
Binary files /dev/null and b/sample-anvil/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/sample-anvil/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample-anvil/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..d8ae03154
Binary files /dev/null and b/sample-anvil/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/sample-anvil/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample-anvil/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..2c18de9e6
Binary files /dev/null and b/sample-anvil/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/sample-anvil/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample-anvil/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..beed3cdd2
Binary files /dev/null and b/sample-anvil/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/sample-anvil/src/main/res/values/colors.xml b/sample-anvil/src/main/res/values/colors.xml
new file mode 100644
index 000000000..69b22338c
--- /dev/null
+++ b/sample-anvil/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #008577
+ #00574B
+ #D81B60
+
diff --git a/sample-anvil/src/main/res/values/strings.xml b/sample-anvil/src/main/res/values/strings.xml
new file mode 100644
index 000000000..e230fa2c4
--- /dev/null
+++ b/sample-anvil/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+ Hello Anvil
+ Say Hello
+ Loading…
+ Loading failed
+
diff --git a/sample-anvil/src/main/res/values/styles.xml b/sample-anvil/src/main/res/values/styles.xml
new file mode 100644
index 000000000..5885930df
--- /dev/null
+++ b/sample-anvil/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/sample-anvilannotations/build.gradle b/sample-anvilannotations/build.gradle
new file mode 100644
index 000000000..2bb0a22ba
--- /dev/null
+++ b/sample-anvilannotations/build.gradle
@@ -0,0 +1,5 @@
+apply plugin: 'org.jetbrains.kotlin.jvm'
+
+dependencies {
+ api 'javax.inject:javax.inject:1'
+}
\ No newline at end of file
diff --git a/sample-anvilannotations/src/main/java/com/airbnb/mvrx/sample/anvil/annotation/ContributesViewModel.kt b/sample-anvilannotations/src/main/java/com/airbnb/mvrx/sample/anvil/annotation/ContributesViewModel.kt
new file mode 100644
index 000000000..0880004c1
--- /dev/null
+++ b/sample-anvilannotations/src/main/java/com/airbnb/mvrx/sample/anvil/annotation/ContributesViewModel.kt
@@ -0,0 +1,17 @@
+package com.airbnb.mvrx.sample.anvil.annotation
+
+import kotlin.reflect.KClass
+
+/**
+ * Adds view model to the specified component graph.
+ * Equivalent to the following declaration in a dagger module:
+ *
+ * @Binds
+ * @IntoMap
+ * @ViewModelKey(YourViewModel::class)
+ * public abstract fun bindYourViewModelFactory(factory: YourViewModel.Factory): AssistedViewModelFactory<*, *>
+ */
+@Target(AnnotationTarget.CLASS)
+annotation class ContributesViewModel(
+ val scope: KClass<*>,
+)
diff --git a/sample-anvilcodegen/build.gradle b/sample-anvilcodegen/build.gradle
new file mode 100644
index 000000000..88373336c
--- /dev/null
+++ b/sample-anvilcodegen/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'org.jetbrains.kotlin.jvm'
+apply plugin: 'org.jetbrains.kotlin.kapt'
+
+tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+ kotlinOptions {
+ freeCompilerArgs += [
+ "-opt-in=com.squareup.anvil.annotations.ExperimentalAnvilApi"]
+ }
+}
+
+dependencies {
+ api "com.squareup.anvil:compiler-api:2.4.0"
+ implementation project(':sample-anvilannotations')
+ implementation "com.squareup.anvil:compiler-utils:2.4.0"
+ implementation "com.squareup:kotlinpoet:1.10.2"
+ implementation 'com.google.dagger:dagger:2.42'
+
+ compileOnly "com.google.auto.service:auto-service-annotations:1.0.1"
+ kapt "com.google.auto.service:auto-service:1.0.1"
+}
diff --git a/sample-anvilcodegen/src/main/java/com/airbnb/mvrx/sample/anvil/codegen/ContributesViewModelCodeGenerator.kt b/sample-anvilcodegen/src/main/java/com/airbnb/mvrx/sample/anvil/codegen/ContributesViewModelCodeGenerator.kt
new file mode 100644
index 000000000..f7978b308
--- /dev/null
+++ b/sample-anvilcodegen/src/main/java/com/airbnb/mvrx/sample/anvil/codegen/ContributesViewModelCodeGenerator.kt
@@ -0,0 +1,119 @@
+package com.airbnb.mvrx.sample.anvil.codegen
+
+import com.airbnb.mvrx.sample.anvil.annotation.ContributesViewModel
+import com.google.auto.service.AutoService
+import com.squareup.anvil.annotations.ContributesTo
+import com.squareup.anvil.compiler.api.AnvilCompilationException
+import com.squareup.anvil.compiler.api.AnvilContext
+import com.squareup.anvil.compiler.api.CodeGenerator
+import com.squareup.anvil.compiler.api.GeneratedFile
+import com.squareup.anvil.compiler.api.createGeneratedFile
+import com.squareup.anvil.compiler.internal.asClassName
+import com.squareup.anvil.compiler.internal.buildFile
+import com.squareup.anvil.compiler.internal.fqName
+import com.squareup.anvil.compiler.internal.reference.ClassReference
+import com.squareup.anvil.compiler.internal.reference.asClassName
+import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.STAR
+import com.squareup.kotlinpoet.TypeSpec
+import dagger.Binds
+import dagger.Module
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import dagger.multibindings.IntoMap
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.KtFile
+import java.io.File
+
+/**
+ * This is an anvil plugin that allows ViewModels to use [ContributesViewModel] alone and let this plugin automatically
+ * handle the rest of the Dagger wiring required for constructor injection.
+ */
+@AutoService(CodeGenerator::class)
+class ContributesViewModelCodeGenerator : CodeGenerator {
+
+ override fun isApplicable(context: AnvilContext): Boolean = true
+
+ override fun generateCode(codeGenDir: File, module: ModuleDescriptor, projectFiles: Collection): Collection {
+ return projectFiles.classAndInnerClassReferences(module)
+ .filter { it.isAnnotatedWith(ContributesViewModel::class.fqName) }
+ .flatMap { listOf(generateModule(it, codeGenDir, module), generateAssistedFactory(it, codeGenDir, module)) }
+ .toList()
+ }
+
+ private fun generateModule(vmClass: ClassReference.Psi, codeGenDir: File, module: ModuleDescriptor): GeneratedFile {
+ val generatedPackage = vmClass.packageFqName.toString()
+ val moduleClassName = "${vmClass.shortName}_Module"
+ val scope = vmClass.annotations.single { it.fqName == ContributesViewModel::class.fqName }.scope()
+ val content = FileSpec.buildFile(generatedPackage, moduleClassName) {
+ addType(
+ TypeSpec.classBuilder(moduleClassName)
+ .addModifiers(KModifier.ABSTRACT)
+ .addAnnotation(Module::class)
+ .addAnnotation(AnnotationSpec.builder(ContributesTo::class).addMember("%T::class", scope.asClassName()).build())
+ .addFunction(
+ FunSpec.builder("bind${vmClass.shortName}Factory")
+ .addModifiers(KModifier.ABSTRACT)
+ .addParameter("factory", ClassName(generatedPackage, "${vmClass.shortName}_AssistedFactory"))
+ .returns(assistedViewModelFactoryFqName.asClassName(module).parameterizedBy(STAR, STAR))
+ .addAnnotation(Binds::class)
+ .addAnnotation(IntoMap::class)
+ .addAnnotation(AnnotationSpec.Companion.builder(viewModelKeyFqName.asClassName(module)).addMember("%T::class", vmClass.asClassName()).build())
+ .build(),
+ )
+ .build(),
+ )
+ }
+ return createGeneratedFile(codeGenDir, generatedPackage, moduleClassName, content)
+ }
+
+ private fun generateAssistedFactory(vmClass: ClassReference.Psi, codeGenDir: File, module: ModuleDescriptor): GeneratedFile {
+ val generatedPackage = vmClass.packageFqName.toString()
+ val assistedFactoryClassName = "${vmClass.shortName}_AssistedFactory"
+ val constructor = vmClass.constructors.singleOrNull { it.isAnnotatedWith(AssistedInject::class.fqName) }
+ val assistedParameter = constructor?.parameters?.singleOrNull { it.isAnnotatedWith(Assisted::class.fqName) }
+ if (constructor == null || assistedParameter == null) {
+ throw AnvilCompilationException(
+ "${vmClass.fqName} must have an @AssistedInject constructor with @Assisted initialState: S parameter",
+ element = vmClass.clazz,
+ )
+ }
+ if (assistedParameter.name != "initialState") {
+ throw AnvilCompilationException(
+ "${vmClass.fqName} @Assisted parameter must be named initialState",
+ element = assistedParameter.parameter,
+ )
+ }
+ val vmClassName = vmClass.asClassName()
+ val stateClassName = assistedParameter.type().asTypeName()
+ val content = FileSpec.buildFile(generatedPackage, assistedFactoryClassName) {
+ addType(
+ TypeSpec.interfaceBuilder(assistedFactoryClassName)
+ .addSuperinterface(assistedViewModelFactoryFqName.asClassName(module).parameterizedBy(vmClassName, stateClassName))
+ .addAnnotation(AssistedFactory::class)
+ .addFunction(
+ FunSpec.builder("create")
+ .addModifiers(KModifier.OVERRIDE, KModifier.ABSTRACT)
+ .addParameter("initialState", stateClassName)
+ .returns(vmClassName)
+ .build(),
+ )
+ .build(),
+ )
+ }
+ return createGeneratedFile(codeGenDir, generatedPackage, assistedFactoryClassName, content)
+ }
+
+ companion object {
+ private val assistedViewModelFactoryFqName = FqName("com.airbnb.mvrx.sample.anvil.di.AssistedViewModelFactory")
+ private val viewModelKeyFqName = FqName("com.airbnb.mvrx.sample.anvil.di.ViewModelKey")
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 120e0e672..0a1c3d903 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -20,6 +20,9 @@ include ':utils-view-binding'
include ':sample-counter'
include ':sample-dogs'
include ':sample-dagger'
+include ':sample-anvil'
+include ':sample-anvilcodegen'
+include ':sample-anvilannotations'
include ':sample-hilt'
include ':sample'
include ':sample-compose'
diff --git a/versions.properties b/versions.properties
index 943225431..8febd5c52 100644
--- a/versions.properties
+++ b/versions.properties
@@ -184,6 +184,8 @@ version.androidx.activity=1.5.1
plugin.io.gitlab.arturbosch.detekt=1.21.0
+version.anvil=2.4.2
+
plugin.android=7.2.2
## # available=7.3.0-alpha01
## # available=7.3.0-alpha02