Carlos Rosiles
Carlos Rosiles

Reputation: 45

Issue with One of Two ViewModels Not Updating with StateFlow data from same Repository in Jetpack Compose and Dagger-Hilt

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

Answers (1)

Jan B&#237;na
Jan B&#237;na

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:

  1. Remove @Provides fun provideMainRepository()

  2. Remove @Inject and @Singleton from AppRepository and add @Singleton to provideMainRepository()

Upvotes: 1

Related Questions