-
Notifications
You must be signed in to change notification settings - Fork 499
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Created a Hilt example with a view model scoped custom component (#503)
This PR is a heavily modified #495 MavericksViewModels no longer extend Jetpack ViewModels. However, we want to make sure that we have a story for how to use it with Hilt and its new @ViewModelScoped scope. I couldn't figure out how to integrate Mavericks with its corresponding ViewModelComponent. However, I was able to create the same setup with a custom component instead. It achieves the same thing and I can't think of any downsides.
- Loading branch information
Showing
42 changed files
with
669 additions
and
14 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,8 +11,8 @@ android { | |
versionName "1.0" | ||
} | ||
|
||
dataBinding { | ||
enabled = true | ||
buildFeatures { | ||
dataBinding true | ||
} | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# Dagger Usage Sample for MvRx | ||
|
||
This module contains a sample app demonstrating how to setup Hilt and AssistedInject in an app using MvRx. | ||
|
||
// build.gradle | ||
dependencies { | ||
def hiltVersion = "2.31.0" // or newer. | ||
kapt "com.google.dagger:hilt-android-compiler:${hiltVersion}" | ||
implementation "com.google.dagger:hilt-android:${hiltVersion}" | ||
} | ||
``` | ||
## Key Features | ||
* **Injecting state into ViewModels with AssistedInject** | ||
Since the `initialState` parameter is only available at runtime, we use the [AssistedInject](https://dagger.dev/dev-guide/assisted-injection). | ||
* **Multibinding setup for AssistedInject Factories** | ||
Every ViewModel using AssistedInject needs a Factory interface annotated with `@AssistedFactory`. 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. | ||
## Example | ||
* Create your ViewModel with an `@AssistedInject` constructor, an `@AssistedFactory` implementing `AssistedViewModelFactory`, and a companion object implementing `MavericksViewModelFactory`. | ||
```kotlin | ||
// NOTE: unlike using Jetpack ViewModels with Hilt, you do not need to annotate your ViewModel class with @HiltViewModel. | ||
class MyViewModel @AssistedInject constructor( | ||
@Assisted initialState: MyState, | ||
// and other dependencies | ||
) { | ||
@AssistedFactory | ||
interface Factory: AssistedViewModelFactory<MyViewModel, MyState> { | ||
override fun create(initialState: MyState): MyViewModel | ||
} | ||
companion object : MavericksViewModelFactory<MyViewModel, MyState> by hiltMavericksViewModelFactory() | ||
} | ||
``` | ||
|
||
* Tell Hilt to include your ViewModel's AssistedInject Factory in a Multibinding map. | ||
|
||
```kotlin | ||
@Module | ||
@InstallIn(MavericksViewModelComponent::class) | ||
interface ViewModelsModule { | ||
@Binds | ||
@IntoMap | ||
@ViewModelKey(HelloHiltViewModel::class) | ||
fun helloViewModelFactory(factory: HelloHiltViewModel.Factory): AssistedViewModelFactory<*, *> | ||
} | ||
|
||
``` | ||
|
||
* With this setup complete, request your ViewModel in a Fragment as usual, using any of MvRx's ViewModel delegates. | ||
|
||
```kotlin | ||
class MyFragment : Fragment(), MavericksView { | ||
val viewModel: MyViewModel by fragmentViewModel() | ||
} | ||
``` | ||
|
||
## How it works | ||
|
||
`HiltMavericksViewModelFactory` will create a custom ViewModelComponent that is a child of ActivityComponent and will create an instance of your ViewModel with it. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
apply plugin: "com.android.application" | ||
apply plugin: "kotlin-android" | ||
apply plugin: "kotlin-kapt" | ||
apply plugin: 'dagger.hilt.android.plugin' | ||
|
||
android { | ||
defaultConfig { | ||
applicationId "com.airbnb.mvrx.helloHilt" | ||
versionCode 1 | ||
versionName "0.0.1" | ||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||
} | ||
|
||
buildTypes { | ||
release { | ||
minifyEnabled true | ||
signingConfig signingConfigs.debug | ||
} | ||
} | ||
|
||
buildFeatures { | ||
viewBinding true | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation Libraries.appcompat | ||
implementation Libraries.constraintlayout | ||
implementation Libraries.fragmentKtx | ||
kapt AnnotationProcessors.hilt | ||
implementation Libraries.hilt | ||
implementation project(":mvrx-rxjava2") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
xmlns:tools="http://schemas.android.com/tools" | ||
package="com.airbnb.mvrx.hellohilt"> | ||
|
||
<application | ||
android:name="com.airbnb.mvrx.hellohilt.HelloHiltApplication" | ||
android:allowBackup="true" | ||
android:icon="@mipmap/ic_launcher" | ||
android:label="@string/app_name" | ||
android:roundIcon="@mipmap/ic_launcher_round" | ||
android:supportsRtl="true" | ||
android:theme="@style/AppTheme" | ||
tools:ignore="GoogleAppIndexingWarning"> | ||
<activity android:name="com.airbnb.mvrx.hellohilt.MainActivity"> | ||
<intent-filter> | ||
<action android:name="android.intent.action.MAIN" /> | ||
|
||
<category android:name="android.intent.category.LAUNCHER" /> | ||
</intent-filter> | ||
</activity> | ||
</application> | ||
|
||
</manifest> |
13 changes: 13 additions & 0 deletions
13
hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloHiltApplication.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.airbnb.mvrx.hellohilt | ||
|
||
import android.app.Application | ||
import com.airbnb.mvrx.Mavericks | ||
import dagger.hilt.android.HiltAndroidApp | ||
|
||
@HiltAndroidApp | ||
class HelloHiltApplication : Application() { | ||
override fun onCreate() { | ||
super.onCreate() | ||
Mavericks.initialize(this) | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloHiltFragment.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.airbnb.mvrx.hellohilt | ||
|
||
import android.os.Bundle | ||
import android.view.LayoutInflater | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import androidx.fragment.app.Fragment | ||
import com.airbnb.mvrx.MvRxView | ||
import com.airbnb.mvrx.fragmentViewModel | ||
import com.airbnb.mvrx.hellohilt.databinding.HelloHiltFragmentBinding | ||
import com.airbnb.mvrx.withState | ||
import dagger.hilt.android.AndroidEntryPoint | ||
|
||
@AndroidEntryPoint | ||
class HelloHiltFragment : Fragment(R.layout.hello_hilt_fragment), MvRxView { | ||
val viewModel1: HelloHiltViewModel by fragmentViewModel(keyFactory = { "a" }) | ||
val viewModel2: HelloHiltViewModel by fragmentViewModel(keyFactory = { "b" }) | ||
|
||
private var _binding: HelloHiltFragmentBinding? = null | ||
private val binding get() = _binding ?: error("Binding was null!") | ||
|
||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { | ||
_binding = HelloHiltFragmentBinding.inflate(inflater, container, false) | ||
return binding.root | ||
} | ||
|
||
override fun onDestroyView() { | ||
_binding = null | ||
super.onDestroyView() | ||
} | ||
|
||
override fun invalidate() = withState(viewModel1, viewModel2) { state1, state2 -> | ||
@Suppress("Detekt.MaxLineLength") | ||
binding.with.text = "@MavericksViewModelScoped: VM1: [${state1.viewModelScopedClassId1},${state1.viewModelScopedClassId2}] VM2: [${state2.viewModelScopedClassId1},${state2.viewModelScopedClassId2}]" | ||
@Suppress("Detekt.MaxLineLength") | ||
binding.without.text = "VM1: [${state1.notViewModelScopedClassId1},${state1.notViewModelScopedClassId2}] VM2: [${state2.notViewModelScopedClassId1},${state2.notViewModelScopedClassId2}]" | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloHiltViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.airbnb.mvrx.hellohilt | ||
|
||
import com.airbnb.mvrx.BaseMvRxViewModel | ||
import com.airbnb.mvrx.MavericksViewModelFactory | ||
import com.airbnb.mvrx.MvRxState | ||
import com.airbnb.mvrx.hellohilt.di.AssistedViewModelFactory | ||
import com.airbnb.mvrx.hellohilt.di.hiltMavericksViewModelFactory | ||
import dagger.assisted.Assisted | ||
import dagger.assisted.AssistedFactory | ||
import dagger.assisted.AssistedInject | ||
|
||
data class HelloHiltState( | ||
val viewModelScopedClassId1: Int? = null, | ||
val viewModelScopedClassId2: Int? = null, | ||
val notViewModelScopedClassId1: Int? = null, | ||
val notViewModelScopedClassId2: Int? = null, | ||
) : MvRxState | ||
|
||
class HelloHiltViewModel @AssistedInject constructor( | ||
@Assisted state: HelloHiltState, | ||
private val repo1: HelloRepository, | ||
private val repo2: HelloRepository, | ||
) : BaseMvRxViewModel<HelloHiltState>(state) { | ||
|
||
init { | ||
setState { | ||
copy( | ||
viewModelScopedClassId1 = repo1.viewModelScopedClass.id, | ||
viewModelScopedClassId2 = repo2.viewModelScopedClass.id, | ||
notViewModelScopedClassId1 = repo1.notViewModelScopedClass.id, | ||
notViewModelScopedClassId2 = repo2.notViewModelScopedClass.id, | ||
) | ||
} | ||
} | ||
|
||
@AssistedFactory | ||
interface Factory : AssistedViewModelFactory<HelloHiltViewModel, HelloHiltState> { | ||
override fun create(state: HelloHiltState): HelloHiltViewModel | ||
} | ||
|
||
companion object : MavericksViewModelFactory<HelloHiltViewModel, HelloHiltState> by hiltMavericksViewModelFactory() | ||
} |
8 changes: 8 additions & 0 deletions
8
hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/HelloRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.airbnb.mvrx.hellohilt | ||
|
||
import javax.inject.Inject | ||
|
||
class HelloRepository @Inject constructor( | ||
val viewModelScopedClass: ViewModelScopedClass, | ||
val notViewModelScopedClass: NotViewModelScopedClass, | ||
) |
7 changes: 7 additions & 0 deletions
7
hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/MainActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
12 changes: 12 additions & 0 deletions
12
hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/NotViewModelScopedClass.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.airbnb.mvrx.hellohilt | ||
|
||
import java.util.concurrent.atomic.AtomicInteger | ||
import javax.inject.Inject | ||
|
||
class NotViewModelScopedClass @Inject constructor() { | ||
val id = instanceId.incrementAndGet() | ||
|
||
companion object { | ||
private val instanceId = AtomicInteger(0) | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/ViewModelScopedClass.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.airbnb.mvrx.hellohilt | ||
|
||
import com.airbnb.mvrx.hellohilt.di.MavericksViewModelScoped | ||
import java.util.concurrent.atomic.AtomicInteger | ||
import javax.inject.Inject | ||
|
||
@MavericksViewModelScoped | ||
class ViewModelScopedClass @Inject constructor() { | ||
val id = instanceId.incrementAndGet() | ||
|
||
companion object { | ||
private val instanceId = AtomicInteger(0) | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/ViewModelsModule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.airbnb.mvrx.hellohilt | ||
|
||
import com.airbnb.mvrx.hellohilt.di.AssistedViewModelFactory | ||
import com.airbnb.mvrx.hellohilt.di.MavericksViewModelComponent | ||
import com.airbnb.mvrx.hellohilt.di.ViewModelKey | ||
import dagger.Binds | ||
import dagger.Module | ||
import dagger.hilt.InstallIn | ||
import dagger.multibindings.IntoMap | ||
|
||
@Module | ||
@InstallIn(MavericksViewModelComponent::class) | ||
interface ViewModelsModule { | ||
@Binds | ||
@IntoMap | ||
@ViewModelKey(HelloHiltViewModel::class) | ||
fun helloViewModelFactory(factory: HelloHiltViewModel.Factory): AssistedViewModelFactory<*, *> | ||
} |
24 changes: 24 additions & 0 deletions
24
hellohilt/src/main/java/com/airbnb/mvrx/hellohilt/di/AssistedViewModelFactory.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.airbnb.mvrx.hellohilt.di | ||
|
||
import com.airbnb.mvrx.MavericksState | ||
import com.airbnb.mvrx.MavericksViewModel | ||
|
||
/** | ||
* This factory allows Mavericks to supply the initial or restored [MavericksState] to Hilt. | ||
* | ||
* Add this interface inside of your [MavericksViewModel] class then create the following Hilt module: | ||
* | ||
* @Module | ||
* @InstallIn(MavericksViewModelComponent::class) | ||
* interface ViewModelsModule { | ||
* @Binds | ||
* @IntoMap | ||
* @ViewModelKey(MyViewModel::class) | ||
* fun myViewModelFactory(factory: MyViewModel.Factory): AssistedViewModelFactory<*, *> | ||
* } | ||
* | ||
* If you already have a ViewModelsModule then all you have to do is add the multibinding entry for your new [MavericksViewModel]. | ||
*/ | ||
interface AssistedViewModelFactory<VM : MavericksViewModel<S>, S : MavericksState> { | ||
fun create(state: S): VM | ||
} |
Oops, something went wrong.