Search code examples
kotlinmvvmandroid-roomkotlin-flow

After navigating to the second fragment, the app seems to not collecting data from the database


I have two fragments in my app. In first fragment, I can see, that data is collected from database, unofrtunately, after navigating by Navigation Components to the second fragment, it is not, and I don't know why.

DAO

@Query("SELECT * from base_currency")
fun getBaseCurrency(): Flow<BaseCurrencyModel>

Repository

val baseCurrency: Flow<BaseCurrencyModel> =
        currencyDAO.getBaseCurrency().shareIn(
            scope, SharingStarted.WhileSubscribed(5000L)
        )

ViewModel frag 1

private val _baseCurrencyState: MutableSharedFlow<DatabaseState> = MutableSharedFlow(replay = 1)
val baseCurrency: SharedFlow<DatabaseState> get() = _baseCurrencyState

fun getBaseCurrency() {
        viewModelScope.launch {
            databaseRepository.baseCurrency
                .catch { _baseCurrencyState.emit(DatabaseState.Error(it.cause)) }
                .collect { currency ->
                    _baseCurrencyState.emit(DatabaseState.Success(currency.baseCurr))
                }
        }

ViewModel frag 2

private val _baseCurrencyState: MutableSharedFlow<DatabaseState> = MutableSharedFlow(replay = 1)
val baseCurrency: SharedFlow<DatabaseState> get() = _baseCurrencyState

fun getBaseCurrency() {
        viewModelScope.launch {
            databaseRepository.baseCurrency
                .catch { _baseCurrencyState.emit(DatabaseState.Error(it.cause)) }
                .collect { currency ->
                    _baseCurrencyState.emit(DatabaseState.Success(currency.baseCurr))
                }
        }
    }

Fragment 1

viewLifecycleOwner.lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                mViewModel.getBaseCurrency()
                mViewModel.baseCurrency.collect { baseCurrency ->
                    when (baseCurrency) {
                        is DatabaseState.Success<*> -> {
                            mBinding.latestBase.text = String.format(getString(R.string.formatted_base_currency, baseCurrency.data))
//                            TODO
                        }
                        is DatabaseState.Error<*> -> {
                            Log.i(TAG, "onCreateView: ERROR $baseCurrency")}
                    }
                }
            }
        }

Fragment 2

viewLifecycleOwner.lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                mViewModel.getBaseCurrency()
                mViewModel.baseCurrency.collect { baseCurrency ->
                    when (baseCurrency) {
                        is DatabaseState.Success<*> -> {
                            Log.i(TAG, "onCreateView: ${baseCurrency.data}")
                        }
                        is DatabaseState.Error<*> -> {
                            Log.i(TAG, "onCreateView: ERROR $baseCurrency")}
                    }
                }
            }
        }

Solution

  • Your SharedFlow in the repository doesn't have any replay, and it uses WhileSubscribed(5000L), so if the second fragment is opened within five seconds of the first one going off screen, then the source flow is not restarted when the shared flow is collected in the second ViewModel, and it receives no initial value.

    The solution is to add replay = 1 to the shareIn call in your repository.

    By the way, your code in both ViewModels could be significantly simplified by using shareIn. It is really ugly that they have a backing MutableSharedFlow for no use but to collect another flow into it. And it's even uglier that the collection is done in a function that has to be manually called by an outside class, rather than just doing it in init. And it breaks function naming convention to name that function get...().

    val baseCurrency: SharedFlow<DatabaseState> = databaseRepository.baseCurrency
        .map { DatabaseState.Success(it.baseCurr) }
        .catch { emit(DatabaseState.Error(it.cause)) }
        .shareIn(viewMdoelScope, SharingStarted.WhileSubscribed(5000L), replay = 1)