Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best Way to "Refresh" LiveData Provided by Room Database

I'm currently using Room to store a list of translations of words, and returning queries as LiveData to monitor insertions and updates. I run into problems however when I need to re-fetch the translations for a different source language (my current strategy is to re-assign the livedata to the result from the room query).

I fetch languages to translate and translations for a specific language using the below SQL queries

@Dao
interface TranslationDatabaseDao {
   ...

   //Returns all pairs of languages to translate to/from
   @Query("SELECT * FROM language_pairs")
   fun getAllLanguagePairs(): LiveData<List<LanguagePair>>

   //Returns translations with the specified source language
   @Query("SELECT * FROM translations WHERE sourceLanguage = :language")
   fun getTranslations(language: String): LiveData<List<TranslationResult>>

   ...
}

which I call when instantiating a viewmodel, and again when the user changes the language (see changeLanguage(...) below).

class translationViewmodel(private val database: TranslationDatabaseDao, initLanguage: String): ViewModel() {
   ...

   val languages: LiveData<List<LanguagePair>> = database.getAllLanguagePairs()
   val currentLanguages = Transformations.map(languages) { allLanguages ->
      allLanguages?.let {
         it[0] //Get the first language in the list
      }
   }

   var translations: LiveData<List<TranslationResult>> = database.getTranslations(initLanguage)

   ...

   fun changeLanguage(language: String) {
      coroutinesScope.launch {
         translations = withContext(Dispatchers.IO) {
            database.getTranslations(language)
         }
      }
   }

   ...
}

Re-assigning the list of translations causes the fragment observing the livedata to keep observing the old livedata, so my strategy has been to remove the observer and reassign it in the fragment when changing languages. This doesn't seem like the best solution here, and I'm also not sure how to ensure the data has been loaded before removing the previous observer and recreating it (I'm using coroutines so I would need a way to have a callback when the livedata has been returned).

Possible Solutions

  1. Have the database return just a List object, instead of livedata. I could then store it in mutable livedata. This isn't ideal since insertions and updates aren't automatically reflected in the returned list, so I would have to re-fetch the data every time I change the list of translations for a specific language.
  2. Fetch all translations and then filter them every time the language changes. This would be a bit resource intensive once the list of languages grows large.

So my question is: Is there a better way that re-assigning the livedata after a language change? Or rather, is there an accepted way to have the fragment be aware of this re-assignment?

like image 945
JJ Jacobs Avatar asked Oct 15 '22 10:10

JJ Jacobs


1 Answers

You can create one more LiveData for the language, and use switchMap to map it to the translations, like:

class translationViewmodel(private val database: TranslationDatabaseDao, initLanguage: String): ViewModel() {
  val language = MutableLiveData<String>("en")
  val translations = language.switchMap { language -> 
    database.getTranslations(language)
  }
  ...
}

Now, just change the language and translations will be updated too

viewmodel.language.value = "es"
like image 165
Valeriy Katkov Avatar answered Oct 20 '22 16:10

Valeriy Katkov