Convert View Model to Use Hilt Dependency Injection

Here are the steps I did and my key learnings to convert my Android News app to use hilt dependency injection

ยท

3 min read

Convert View Model to Use Hilt Dependency Injection

So I followed the steps in How to Implement Hilt in Android App? article to convert the Android News app to use Hilt Dependency Injection. The difference in previous article is it doesn't cover the View Model dependency injection.

1. Add Dependencies and @HiltAndroidApp

The initial setup steps are exactly the same as the following:

2. Add @HiltViewModel and @Inject Constructor

In order for Hilt to create MainViewModel, you need to annotate the class with @HiltViewModel. @Inject constructor is also used to tell Hilt how the dependencies can be created.

@HiltViewModel
class MainViewModel
    @Inject constructor(
        private val repository: ArticlesRepository,
    ) : ViewModel() {
    ...
}

I also use @Inject Constructor on the SqlArticlesRepository.

class SqlArticlesRepository @Inject constructor(
    private val database: ArticlesDatabase,
    private val webService: WebService,
) : ArticlesRepository {
   ...
}

3. Add @Provides and @Binds

To create the dependencies, we use either @Provides and @Binds. @Provides is used to create ArticlesDatabase and WebService instances.

@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext appContext: Context): ArticlesDatabase {

        return Room.databaseBuilder(
            appContext,
            ArticlesDatabase::class.java,
            "articles.db",
        )
            .fallbackToDestructiveMigration()
            .build()
    }
}

@Module
@InstallIn(SingletonComponent::class)
object WebServiceModule {

    @Provides
    @Singleton
    fun provideWebService(): WebService {
        return WebService()
    }
}

@Binds is used to create the implementation of ArticlesRepository interface.

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {

    @Binds
    @Singleton
    abstract fun bindArticlesRepository(impl: SqlArticlesRepository): ArticlesRepository
}

4. Call hiltViewModel() Composable

Now it is done! All manual creations below in MainActivity can be removed.

private val repository by lazy {
    SqlArticlesRepository(
        ArticlesDatabase.getInstance(application),
        WebService(),
    )
}

private val homeViewModel by viewModels<MainViewModel> {
    MainViewModelFactory(repository)
}

[Updated - 16 Oct, 2022]: To use hiltViewModel(), you need to add the following dependency.

implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'

The MainViewModel creation can be done by calling the hiltViewModel() composable function, which generates all the necessary dependencies.

For example, replace this MainScreen composable function

MainScreen(homeViewModel, useSystemUIController = true)

with

MainScreen(viewModel = hiltViewModel(), useSystemUIController = true)

The database creation below can also be removed since it has been provided by hilt @Provides above.

companion object {
    @Volatile
    private lateinit var instance: ArticlesDatabase

    fun getInstance(context: Context): ArticlesDatabase {
        synchronized(this) {
            if (!::instance.isInitialized) {
                instance = Room.databaseBuilder(
                    context.applicationContext,
                    ArticlesDatabase::class.java,
                    "articles.db")
                    .fallbackToDestructiveMigration()
                    .build()
            }

            return instance
        }
    }
}

This MainViewModelFactory can also be removed since this has been taken care by @HiltViewModel.

@Suppress("UNCHECKED_CAST")
class MainViewModelFactory(private val repository: ArticlesRepository)
    : ViewModelProvider.NewInstanceFactory() {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {

        if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
            return MainViewModel(repository) as T
        }

        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

Conclusion

All my installed Hilt modules are in SingletonComponent scope. If I change my installed module to another component, for example ViewModelComponent or ActivityComponent, it fails to compile, and I don't know how to fix it. I guess that is my next step to play around with this Hilt.

Yes, I still don't like Hilt. Call me grandpa, but I still prefer manual dependency injection like this one:

Source Code

Did you find this article valuable?

Support Vincent Tsen by becoming a sponsor. Any amount is appreciated!

ย