Reputation: 45
I'm working on a Jetpack Compose app and using Dagger-Hilt for dependency injection. I have two ViewModels (MainViewModel and FormViewModel) subscribing to a StateFlow from a singleton AppRepository. Both ViewModels initialize correctly, but only MainViewModel is receiving updates from the repository, while FormViewModel is not. Both instances are created at same composable and time.
Notes: This application is the most extensive I've worked on using Jetpack Compose and the MVVM pattern. Initially, my app had only one ViewModel as I've always done in my compose projects, MainViewModel, which has grown significantly in size (currently about 800 lines) and is still expanding. Notably, a substantial portion of this ViewModel (approximately half) is dedicated to handling forms. My goal is to refactor this forms-related code into a separate ViewModel, leading to the creation of FormViewModel. I'm uncertain if this approach aligns with best practices in using MVVM and Dagger-Hilt. Is it advisable to maintain a single, large ViewModel, or is it acceptable to split functionalities into multiple ViewModels for better modularity and maintainability?
==============
AppRepository (summarized):
@Singleton
class AppRepository @Inject constructor(...) {
private val _services = MutableStateFlow<List<LocalService>>(listOf())
val services: StateFlow<List<LocalService>> = _services
..
}
MainViewModel:
@HiltViewModel
class MainViewModel @Inject constructor(
private val appRepository: AppRepository,
private val application: Application
) : ViewModel() {
val services: StateFlow<List<LocalService>> = appRepository.services
init {
viewModelScope.launch {
services.collect { ... // Logs to print services.size
}
}
}
}
FormViewModel:
@HiltViewModel
class FormViewModel @Inject constructor(private val appRepository: AppRepository) : ViewModel() {
val services: StateFlow<List<LocalService>> = appRepository.services
init {
viewModelScope.launch {
services.collect { ... // Logs to print services.size
}
}
}
}
Module:
@Module
@InstallIn(SingletonComponent::class)
object Module {
...
@Provides
fun provideMainRepository(
...
): AppRepository {
return AppRepository(
...
)
}
============== LOGS:
2023-11-14 11:31:16.170 15406-15406 FormViewModel com.conecta D MainViewModel init
2023-11-14 11:31:16.172 15406-15406 FormViewModel com.conecta D MainViewModel formQuestionCrossRef has changed: 0
2023-11-14 11:31:16.173 15406-15406 FormViewModel com.conecta D MainViewModel serviceFormCrossRef has changed: 0
2023-11-14 11:31:16.174 15406-15406 FormViewModel com.conecta D MainViewModel services has changed: 0
2023-11-14 11:31:16.757 15406-15406 FormViewModel com.conecta D FormViewModel init
2023-11-14 11:31:16.758 15406-15406 FormViewModel com.conecta D FormViewModel formQuestionCrossRef has changed: 0
2023-11-14 11:31:16.759 15406-15406 FormViewModel com.conecta D FormViewModel serviceFormCrossRef has changed: 0
2023-11-14 11:31:16.760 15406-15406 FormViewModel com.conecta D FormViewModel services has changed: 0
2023-11-14 11:31:20.028 15406-15406 FormViewModel com.conecta D MainViewModel services has changed: 30
2023-11-14 11:31:20.270 15406-15406 FormViewModel com.conecta D MainViewModel formQuestionCrossRef has changed: 1260
2023-11-14 11:31:20.274 15406-15406 FormViewModel com.conecta D MainViewModel serviceFormCrossRef has changed: 90
Upvotes: 0
Views: 114
Reputation: 7248
You don't need that @Provides
method for AppRepository
in the module. With that, dagger has two ways to create an instance of AppRepository
- its @Inject
annotated constructor and that @Provides
method. I'm not sure which wins in the end to be honest, but I'm quite sure that the problem is that you have multiple instances of your repository.
Moreover, the @Provides
method in a module that is installed in SingletonComponent
doesn't mean that dagger will create only one instance of that class. For that, you will have to annotate the method itself with @Singleton
. From the docs:
Warning: A common misconception is that all bindings declared in a module will be scoped to the component the module is installed in. However, this isn’t true. Only bindings declarations annotated with a scope annotation will be scoped.
This means there are two possible solutions:
Remove @Provides fun provideMainRepository()
Remove @Inject
and @Singleton
from AppRepository
and add @Singleton
to provideMainRepository()
Upvotes: 1