Open blonsky95 opened 3 years ago
What to inject:
mainViewModel - inject a preferencesDataStore - or make application context injectable and inject it to mainViewModel at start so when it inits it creates PDS
see where it needs injecting when context
Dependencies
build.gradle project -
dependencies {
classpath "com.android.tools.build:gradle:4.0.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
build.gradle app -
implementation "com.google.dagger:hilt-android:2.28.3-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28.3-alpha"
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02"
kapt "androidx.hilt:hilt-compiler:1.0.0-alpha02"
implementation "androidx.activity:activity-ktx:1.1.0"
and
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
Create Application
@HiltAndroidApp
class MyApplication:Application()
and add to manifest
<application
android:name=".daggerhilt.MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher_app"
Now - for PreferenceDataStore
First in AppModule looks like this
@Module
@InstallIn(ApplicationComponent::class)
object AppModule {
@Singleton
@Provides
@Named("PreferencesDataStore")
fun providePreferenceDataStore(@ApplicationContext context: Context) = PreferencesDataStore(context)
}
PreferencesDataStore will look like this
class PreferencesDataStore @Inject constructor (@ApplicationContext context: Context)
MainViewModel like this
class MainViewModel @ViewModelInject constructor(application: Application,
@Named("PreferencesDataStore") var preferencesDataStore: PreferencesDataStore
) : AndroidViewModel(application)
PD - actually atm, PreferencesDataStore doesnt contain the @Inject annotation, because if its using the AppModule @Provides it is not neccesary, so the function that provides that injection is specified with @Named("PreferencesDataStore") , If it weren't a singleton I wouldn't include it in Module, but add the @Inject annotation to pds class, so the mainviewmodel constructor wouldnt have to specify the @inject, just by putting var preferencesDataStore: PreferencesDataStore it will check if that class is injectable in its constructor - explained in the next comments
and MainActivity will do
@AndroidEntryPoint
class MainActivity : AppCompatActivity(),
LifecycleOwner,
ActionButtonsInterface,
SpeedSliderInterface,
GuideInterface {
private val mainViewModel: MainViewModel by viewModels()
}
Bear in mind this is doing two things - its injecting the view model, and in its constructor it needs PDS and there it is injected so might be a bit more duh
Next, try getting PDS instance injected in MainActivity and see if it works there and if its just viewmodel thats weird
for resource material look - philip lackner own project source code https://medium.com/@robercoding/c%C3%B3mo-usar-dagger-hilt-en-android-con-ejemplos-kotlin-4122bdfc95dc
Ok so to summarise something rather basic:
source: https://developer.android.com/training/dependency-injection/hilt-android?hl=es-419
To do basic injection, you dont really need a module, but it does clear things up a little, plus, for your own classes I guess it makes more sense
So, if you want to inject a field property, you just add @Inject in front of it e.g.
@Inject
lateinit var myClass:MyClass
And then in the MyClass.kt you make sure you indicate how its injected, like this:
class MyClass @Inject constructor( myOtherClass: MyOtherClass)
Entonces Hilt knows how to create it, but in this last case, if the constructor has dependencies/parameters, these would have to be injectable too, so perhaps somewhere else you would need:
class MyOtherClass @Inject constructor ()
Here some stuff on how to use + if in a view model - you no longer have to use factories - you can use ktx delegate "by viewModels()" and it will search for viewModelInject and that the constructors params are all injectable
But if I want to use the module (which also helps if I want the class im injecting to be a singleton) I'll add it to AppModule like this:
@Singleton
@Provides
@Named("PreferencesDataStore")
fun providePreferenceDataStore(@ApplicationContext context: Context) = PreferencesDataStore(context)
I dont necessarily need to make the pds class injectable, it works if I leave it like this:
class PreferencesDataStore (context: Context)
To inject viewmodel into fragment, if I want my own viewmodel i can do " by viewmodels" but if I want the viewmodel that is binded to the activity scope i use:
private val mainViewModel: MainViewModel by activityViewModels()
super simple and easy! Also has to add dependencie in app gradle:
implementation 'androidx.fragment:fragment-ktx:1.2.5'
source https://stackoverflow.com/questions/56748334/how-to-get-viewmodel-by-viewmodels-fragment-ktx
Investigate what to DI next - go through main activity and mainviewmodel to see if there any classes or fields that would be better off injecting
so far DI has helped with:
keep on searching - see if i can inject activity context - dialogs creator object - timesplitscontroller
So I also added an Activity Module - this file has the annotation @InstallIn (ActivityComponent::class) and I added the following functions:
@ActivityScoped
@Provides
@Named("exoPlayer")
fun getExoPlayerInstance(@ApplicationContext context: Context): SimpleExoPlayer {
val myDefaultRenderersFactory =
Utils.MyDefaultRendererFactory(
context
).setEnableAudioTrackPlaybackParams(true)
Timber.d("Activity module - exo player instance")
return SimpleExoPlayer.Builder(context, myDefaultRenderersFactory).build().apply { setSeekParameters(
SeekParameters.EXACT) }
}
@ActivityScoped
@Provides
@Named("dataSourceFactory")
fun getDataSourceFactoryInstance(
@ApplicationContext context: Context,
application: Application
): DataSource.Factory {
Timber.d("Activity module - data source factory isntance")
return DefaultDataSourceFactory(
context,
Util.getUserAgent(context, application.packageName)
)
}
These are activity scoped because they are like singletons, but not objects, so the instances are retrievable, they are not being created each time. And they only live through the lifecycle of the activity. So from my understanding, when the activities with AndroidEntryPoint are triggered, they all load the possible injections from the ActivityModule. So because mainviewmodel is injected at mainactivity, it can fetch the constructor injections from the mainactivity dagger hilt module
Also In the ActivityModule I can use the annotation @ActivityContext like I was using @ApplicationContext in AppModule to get the equivalent to "this", or in this case "MainActivity"
Ok so at the moment I am interested in the possiblity of injecting instances into the mainViewModel - such as
So why use dependency injection:
Dependency injection helps to provide (or inject) the parameters you might need to construct a class. This facilitates having to provide parameters for certain functions, which in turn also decouples the functionality of each file
Dagger Hilt is the most recent Android Jetpack component that attempts to provide dependency injection.