blonsky95 / TimeStopperAndroid

0 stars 0 forks source link

Dependency Injection - Dagger Hilt #49

Open blonsky95 opened 3 years ago

blonsky95 commented 3 years ago

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.

blonsky95 commented 3 years ago

https://www.youtube.com/watch?v=ZE2Jkvnk2Bs&ab_channel=PhilippLackner

blonsky95 commented 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

blonsky95 commented 3 years ago

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"
blonsky95 commented 3 years ago

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

blonsky95 commented 3 years ago

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

blonsky95 commented 3 years ago

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 ()
blonsky95 commented 3 years ago

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

image

blonsky95 commented 3 years ago

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)

blonsky95 commented 3 years ago

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

blonsky95 commented 3 years ago

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

blonsky95 commented 3 years ago

so far DI has helped with:

blonsky95 commented 3 years ago

https://proandroiddev.com/all-about-hilt-a-dependency-injection-framework-869b9c2bcb09

blonsky95 commented 3 years ago

keep on searching - see if i can inject activity context - dialogs creator object - timesplitscontroller

blonsky95 commented 3 years ago

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

blonsky95 commented 3 years ago

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"

blonsky95 commented 3 years ago

Ok so at the moment I am interested in the possiblity of injecting instances into the mainViewModel - such as