DEV Community

Cover image for Convert View Model to Use Hilt Dependency Injection
Vincent Tsen
Vincent Tsen

Posted on • Originally published at vtsen.hashnode.dev

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

This article was originally published at vtsen.hashnode.dev on July 16, 2022.

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() {
    ...
}
Enter fullscreen mode Exit fullscreen mode

I also use @Inject Constructor on the SqlArticlesRepository.

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

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()
    }
}
Enter fullscreen mode Exit fullscreen mode

@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
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

with

MainScreen(viewModel = hiltViewModel(), useSystemUIController = true)
Enter fullscreen mode Exit fullscreen mode

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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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")
    }
}
Enter fullscreen mode Exit fullscreen mode

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 grand pa, I still prefer manual dependency inject like this one:

Source Code

See Also

Discussion (0)