Android Dagger 2 ViewModel injection - android

I am using Dagger 2 with Kotlin. I`ve provided a viewmodel(CarViewModel) injection on the following way. Everywhere is written that it has to be done via ViewModelFactory injection. I want to ask if my way to directly inject viewmodel is correct? Here is my AppModule:
#Module
class AppModule {
#Provides
#Singleton
fun getContext(application: Application): Context = application.applicationContext
#Provides
#Singleton
fun getDb(context: Context): MyDatabase = MyDatabase.getInstance(context)
#Provides
fun injectViewModel(application: Application): CarViewModel=
ViewModelProvider.AndroidViewModelFactory.getInstance(application).create(CarViewModel::class.java)
}
Than injected in activity:
#Inject
lateinit var carViewModel: CarViewModel

No, your way of injecting a view model is not quite correct. The main reason is that the view model isn't associated with an activity, so it won't be reused when the activity is recreated, instead it will be recreated as well. The proper way of creating a view model is using ViewModelProvider, not the ViewModelProvider.Factory directly:
// `this` refers to the activity or fragment
viewModel = ViewModelProviders.of(this)[MyViewModel::class.java]
or:
viewModel = ViewModelProvider(this)[MyViewModel::class.java]
if you use the newest alpha version (ViewModelProviders.of() is deprecated).
If you want to inject a view model and also be able to inject into view model (using constructor injection), you have to create your implementation of the ViewModelProvider.Factory interface and use it to create view models with non-empty constructors.

you only have to inject viewmodel factory & bind viewmodel and then you can obtain view model via viewmodel factory.
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
val carViewModel: CarViewModel by viewModels{viewModelFactory}

Related

When do I need to add annotation when I use Hilt as dependency injection?

I'm reading the article Using Hilt in your Android app.
The Code A, Code B and Code C are from the sample project in solution branch.
I'm confused when I should add the Hilt annotation.
In Code A, the parameter database of fun provideLogDao is injected, but the author doesn't add any annotation, but the parameter appContext of fun provideDatabase is marked with annotation #ApplicationContext, why ?
In Code B, the parameter logDao of class LoggerLocalDataSource is injected, but the author doesn't add any annotation, why?
In Code C, I'm told the following content, why isn't the parameter activity of class AppNavigatorImpl added any annotation? and you know ApplicationContext is predefined binding too, but the author add annotation #ApplicationContext in fun provideDatabase in Code A.
Because an AppNavigator instance is provided in the Activity container , FragmentActivity is already available as a predefined binding.
Code A
#InstallIn(SingletonComponent::class)
#Module
object DatabaseModule {
#Provides
#Singleton
fun provideDatabase(#ApplicationContext appContext: Context): AppDatabase {
return Room.databaseBuilder(
appContext,
AppDatabase::class.java,
"logging.db"
).build()
}
#Provides
fun provideLogDao(database: AppDatabase): LogDao {
return database.logDao()
}
}
Code B
class LoggerLocalDataSource #Inject constructor(private val logDao: LogDao) : LoggerDataSource {
...
}
Code C
class AppNavigatorImpl #Inject constructor(private val activity: FragmentActivity) : AppNavigator {
...
}
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#Inject lateinit var navigator: AppNavigator
...
}
#ApplicationContext "tells" hilt to inject the application context, and not activity or service contexts. Also reminds us that injecting it will not cause memory leak.
#Inject is right there, at the left of the constructor. In DI terms, is called "Constructor Injection", which means that all of the parameters of the constructor will be injected by hilt without annotating those one by one.
Singleton component's object graph is not being garbage collected until your app is killed - injecting an activity there will cause memory leak.

How to inject a singleton using hilt inside a composable

I'm trying to inject a singleton class that was defined in a hiltmodule inside a composable.
I know how to inject viewmodels but what about singleton classes ?
#Inject
lateinit var mysingleton: MySingletonClass
This code works fine in an activity but carrying it around from the activity to the composable that uses it is a long way ...
Any better solution ?
You cannot inject dependencies into a function, which is what a #Composable is. #Composable functions don't have dependencies, but can get values returned by Hilt functions, like hiltViewModel().
If you need access to a ViewModel-scoped (or Application-scoped) singleton inside a #Composable, you can have that singleton injected into the ViewModel, and then access the ViewModel from the #Composable.
You can inject that singleton into the ViewModel by annotating the provider function for that object in the ViewModel hilt module as #ViewScoped.
You could install the provider into the SingletonComponent::class and annotate it as #Singleton, if you want a singleton for the whole app, instead of a singleton per ViewModel instance. More info here.
Hilt module file
#Module
#InstallIn(ViewModelComponent::class)
object ViewModelModule {
#ViewScoped
#Provides
fun provideMySingleton(): MySingletonClass = MySingletonClass()
}
Your ViewModel class:
#HiltViewModel
class MyViewModel
#Inject constructor(
val mySingleton: MySingletonClass
): ViewModel() {
...
}
Your #Composable function:
#Composable fun DisplayPrettyScreen() {
...
val viewModel: MyViewModel = hiltViewModel()
val singleton = viewModel.mySingleton //no need to assign it to a local variable, just for explanation purposes
}
I also thought that is not possible but then found way... tried it and seems it works.
define you entry point interface:
private lateinit var dataStoreEntryPoint: DataStoreEntryPoint
#Composable
fun requireDataStoreEntryPoint(): DataStoreEntryPoint {
if (!::dataStoreEntryPoint.isInitialized) {
dataStoreEntryPoint =
EntryPoints.get(
LocalContext.current.applicationContext,
DataStoreEntryPoint::class.java,
)
}
return dataStoreEntryPoint
}
#EntryPoint
#InstallIn(SingletonComponent::class)
interface DataStoreEntryPoint {
val dataStoreRepo: DataStoreRepo
}
DataStoreRepo is singleton defined in Hilt
#Singleton
#Provides
fun provideDataStoreRepository(dataStore: DataStore<Preferences>): DataStoreRepo =
DataStoreRepo(dataStore)
and then use in composable:
#Composable
fun ComposableFuncionName(dataStoreRepo: DataStoreRepo = requireDataStoreEntryPoint().dataStoreRepo){
...
}

How can I use Hilt to inject Retrofit to Repository, which is injected to ViewModel?

I have just learnt manual dependency injection, but I am trying out Hilt to handle these dependency injections.
I want to inject a ViewModel into a Fragment. The fragment is contained within an Activity. Right now, I have added the annotations to Application, Activity, and Fragment.
#HiltAndroidApp
class MovieCatalogueApplication : Application()
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
...
}
#AndroidEntryPoint
class HomeFragment : Fragment() {
private lateinit var binding: FragHomeBinding
private val viewmodel: HomeViewModel by viewModels()
...
As can be seen, my HomeFragment depends on HomeViewModel. I have added a ViewModel injection as described here like so.
class HomeViewModel #ViewModelInject constructor(
private val movieRepository: MovieRepository,
private val showRepository: ShowRepository,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
However, the ViewModel requires two repositories. Right now, my MovieRepository is like so.
class MovieRepository (private val movieApi: MovieService) {
...
}
In the above code, MovieService will be created by Retrofit using the Retrofit.create(class) method. The interface used to create MovieService is like so.
interface MovieService {
...
}
To get my Retrofit instance, I am using the following code.
object RetrofitService {
...
private var _retrofit: Retrofit? = null
val retrofit: Retrofit
get() {
return when (_retrofit) {
null -> {
_retrofit = Retrofit.Builder()
.client(client)
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
_retrofit!!
}
else -> _retrofit!!
}
}
}
I am not too sure how I can inject the Retrofit into the Repository to be used by my ViewModel later on. Could someone give me some pointers or step-by-step instructions on how to do this?
Apparently, it is not as hard as it seems.
You have to first define the binding information to Hilt. Binding information tells Hilt how to provide the instances of the dependency specified. Because MovieService is created using a Retrofit (which is a 3rd-party class not created by yourself) using the builder pattern, you can't use the constructor injection and you have to instead use Hilt modules and the annotation #Provides to tell Hilt about this binding information.
As described in the doc, the annotated function in the Hilt module you have created will supply the following information to Hilt so that Hilt can provide the instances of the dependency.
• The function return type tells Hilt what type the function provides instances of.
• The function parameters tell Hilt the dependencies of the corresponding type.
• The function body tells Hilt how to provide an instance of the corresponding type. Hilt executes the function body every time it needs to provide an instance of that type.
In the end, you only need to modify the MovieRepository class, add a module for each repository, and annotate the function that tells Hilt how to provide the service instance created with Retrofit with #Provides.
Code.
class MovieRepository #Inject constructor(
private val movieApi: MovieService
) {
...
}
interface MovieService {
...
}
#Module
#InstallIn(ActivityRetainedComponent::class)
object MovieModule {
#Provides
fun provideMovieService(): MovieService
= RetrofitService.retrofit.create(MovieService::class.java)
}
As you can see, the ActivityRetainedComponent is referred in the #InstallIn annotation because the Repository is to be injected to a ViewModel. Each Android component is associated to different Hilt components.

Hilt Inject into ViewModel without constructor params

With the new dependency injection library Hilt, how to inject some classes into ViewModel without constructor params and ViewModelFactory?
Is it possible?
Like in Fragment, we use only #AndroidEntryPoint and #Inject.
how to inject some classes into ViewModel without constructor params and ViewModelFactory? Is it possible?
Hilt supports constructor injection of ViewModel via the #HiltViewModel (previously #ViewModelInject) annotation.
This allows for any #AndroidEntryPoint-annotated class to redefine their defaultViewModelProviderFactory to be the HiltViewModelFactory, which allows the creation of #HiltViewModel-annotated ViewModels correctly instantiated via Dagger/Hilt.
NEW HILT VERSION:
#HiltViewModel
class RegistrationViewModel #Inject constructor(
private val someDependency: SomeDependency,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
OLD HILT VERSION:
class RegistrationViewModel #ViewModelInject constructor(
private val someDependency: SomeDependency,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
And then
#AndroidEntryPoint
class ProfileFragment: Fragment(R.layout.profile_fragment) {
private val viewModel by viewModels<RegistrationViewModel>() // <-- uses defaultViewModelProviderFactory
Yes, it is possible to inject dependency into a ViewModel class without constructor params. First we need to create a new interface annotated with #EntryPoint to access it.
An entry point is an interface with an accessor method for each
binding type we want (including its qualifier). Also, the interface
must be annotated with #InstallIn to specify the component in which to
install the entry point.
The best practice is adding the new entry point interface inside the class that uses it.
public class HomeViewModel extends ViewModel {
LiveData<List<MyEntity>> myListLiveData;
#ViewModelInject
public HomeViewModel(#ApplicationContext Context context) {
myListLiveData = getMyDao(context).getAllPosts();
}
public LiveData<List<MyEntity>> getAllEntities() {
return myListLiveData;
}
#InstallIn(ApplicationComponent.class)
#EntryPoint
interface MyDaoEntryPoint {
MyDao myDao();
}
private MyDao getMyDao(Context appConext) {
MyDaoEntryPoint hiltEntryPoint = EntryPointAccessors.fromApplication(
appConext,
MyDaoEntryPoint.class
);
return hiltEntryPoint.myDao();
}
}
In the code above we created a method named getMyDao and used EntryPointAccessors to retrieve MyDao from Application container.
Notice that the interface is annotated with the #EntryPoint and it's
installed in the ApplicationComponent since we want the dependency
from an instance of the Application container.
#Module
#InstallIn(ApplicationComponent.class)
public class DatabaseModule {
#Provides
public static MyDao provideMyDao(MyDatabase db) {
return db.MyDao();
}
}
Though the code above has been tested and worked properly but it is not the recommended way to inject dependency into ViewModel by android officials; and unless we know what we're doing, the best way is to inject dependency into ViewModel through constructor injection.

Dagger2 dependency Cycle by Using #Binds and #Inject fields

I am getting a dependency cycle whenever I try to use a subcomponent with binding objects. I have an app scope and an activity scope. At the app scope I create my web service then when the activity opens I want to create a storage object, controller, and navigator (all custom classes not androidx classes) and inject them into my androidx ViewModel class. But when I do so I get a dependency cycle.
My top level component looks like
#AppScope
#Component(modules = [AppModule::class])
interface AppComponent {
val activityComponentBuilder: ActivityComponent.Builder
}
#Module(subcomponents = [ActivityComponent::class])
interface AppModule {
#Binds
fun mockWebService(mockWebService: MockWebService): MockWebService
}
Next my subcomponent looks like
#ActivityComponent
#Subcomponent(modules = [ActivityModule::class])
interface ActivityComponent {
fun inject(sharedViewModel: SharedViewModel)
#Subcomponent.Builder
interface Builder {
#BindsInstance
fun storage(storage: Storage): Builder
fun build(): ActivityComponent
}
}
In my activity module I bind two objects
#Binds
abstract fun controller(controller: Controller): Controller
#Binds
abstract fun navigator(navigator: Navigator): Navigator
Each object has an #Inject constructor
class Navigator #Inject constructor(private val storage: Storage)
class Controller #Inject constructor(
private val webService: MockWebService,
private val navigator: Navigator,
private val storage: Storage
) {
Inside my shared view model I try to build my component and inject the fields
#Inject
lateinit var navigator: Navigator
#Inject
lateinit var controller: Controller
init {
MainApplication.component.activityComponentBuilder
.storage(InMemoryStorage.from(UUID.randomUUID().toString()))
.build()
.inject(this)
}
But dagger won't build. I get an error
[Dagger/DependencyCycle] Found a dependency cycle: public abstract interface AppComponent {
MockWebService is injected at di.AppModule.mockWebService(mockWebService)
MockWebService is injected at ActivityModule.Controller(webService, …)
Controller is injected at SharedViewModel.controller
SharedViewModel is injected at
But the error message cuts off there. Am I missing something in how to use a subcomponent to put objects on the graph and then inject them into an object? Is this not possible with Dagger?
#Binds is used to let dagger know the different implementations of an interface. You don't need #Binds here since Navigator and Controller are simple classes that do not implement any interface. I'd assume that's the case with MockWebService too. Also, those classes have #Inject constructor, which means dagger can instantiate them and we don't need to write extra #Provides functions for those classes.
#Binds isn't doing any scoping. Its only job is to tell dagger about different implementations. You can add #XScope with #Binds to make some object scoped. Or, you could just add the scope annotation to the class declaration. Here's an example of how you can add scope to class declaration.
As for the dependency cycle, I think it's because you're telling ActivityComponent to use ActivityModule and telling ActivityModule to install ActivityComponent. Doing just either one should be the case (I think).

Categories

Resources