Как ViewModel может просматривать список? MediatorLiveData на обмен никогда не звонил

#android #kotlin #mvvm #android-livedata #android-lifecycle

Вопрос:

Я хочу запустить сопрограмму, чтобы начать загрузку данных из базы данных во время заставки, чтобы основное действие могло быстро отображать данные, но я не хочу задерживать заставку, чтобы дождаться результатов (это хорошая практика?).

Когда Основное действие настраивает пользовательский интерфейс с наблюдателем списка ViewModel, вызовите ViewModel loadList() , чтобы он мог загрузить список в репозиторий и опубликовать значение, чтобы наблюдатель был активирован. Но репозиторий работает в другом потоке, и ViewModel может сначала запросить данные, которые репозиторий может заполнить. Поэтому мне нужно запустить ViewModel, когда репозиторий загружает данные из базы данных

Это код SplashScreen:

 override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    //this class load the data from database
    PersistenceSingleton.getInstance(application)

    startActivity(Intent(this, MainActivity::class.java))
    finish()
}
 

Настойчивость и постоянство:

 private var dbAccess: PersistenceSingletonRepository? = null // Singleton
private lateinit var db: PokemonDatabase
var listOfPokemon: MutableLiveData<MutableList<Pokemon>> = MutableLiveData()

fun getInstance(context: Context) = synchronized(this){
    if (dbAccess == null) {
        dbAccess = PersistenceSingleton()
        db = PokemonDatabase.getInstance(context)
        //load all pokemon
        CoroutineScope(Dispatchers.IO).launch {
            val lan = Locale.getDefault().language
             val list = if (lan.equals("it")) {
                db.pokemonDao().loadAllPokemonIt()
             } else {
                db.pokemonDao().loadAllPokemon()
             }
             CoroutineScope(Dispatchers.Main).launch {
                listOfPokemon.value = list
             }
        }
    }
    dbAccess
}
 

Модель представления:

 var pokemonList: MutableLiveData<MutableList<Pokemon>> = MutableLiveData()

fun loadLists() {
    //list could be empty. ViewModel need to observe the list on PersistenceSingleton (Repository)
    pokemonList.value = PersistenceSingleton.listOfPokemon.value
}
 

Основная деятельность setUI:

 private fun setUI() {
    val observer = Observer<List<Pokemon>> { list ->
        if (list != null) {
            //hide progress bar  and set recycler view adapter
            binding.pbPokemonlist.visibility = View.INVISIBLE

            adapter = PokemonListAdapter(list)
            binding.rvPokemon.adapter = adapter
        }
    }
    pokemonListVM.pokemonList.observe(this, observer)
    //inflate the recycler view
    pokemonListVM.loadLists()
}
 

Редактировать

Чтобы сделать модель представления наблюдателем , я использовал MediatorLiveData , поэтому моя новая модель представления:

 var pokemonList: MutableLiveData<MutableList<Pokemon>> = MutableLiveData()
private val pokemonListMediator: MediatorLiveData<List<Pokemon>> = MediatorLiveData()

init {
    val observer = Observer<MutableList<Pokemon>> { list ->
        Log.d("POKEMON", "onChange")
        pokemonList.value = list
    }
    pokemonListMediator.addSource(PersistenceSingletonRepository.listOfPokemon, observer)
}

//load full list of Pokemon
fun loadLists() {
    //list could be empty. ViewModel need to observe the list on PersistenceSingleton (Repository)
    pokemonList.value = PersistenceSingletonRepository.listOfPokemon.value
}
 

Но наблюдатель onChange() никогда не вызывается

Комментарии:

1. Зачем вообще иметь заставку, если экран не содержит никакой логики? Лучший способ — использовать рисованный фон . Затем вы просто загружаете данные с главного экрана и отображаете индикатор загрузки во время загрузки.

2. Макет заставки объявляется в манифесте с использованием тем

Ответ №1:

Вы могли бы поместить и Lists то, и другое внутрь ViewModel и сделать их MutableLiveData .

Вы бы начали с observe обоих MutableLiveData объектов внутри onCreate функции в вашем Activity .

После того, как вы выполнили минимум настроек, позвоните loadLists внутрь onCreate . (Например, получить a RecyclerView и настроить его Adapter так, чтобы вашим данным было куда идти)

Ваша loadList функция должна выглядеть примерно так

        public var pokemonList: MutableLiveData<List<Pokemon>> = MutableLiveData()
       public var typeList : MutableLiveData<List<Type>> = MutableLiveData() 
       fun loadLists(context: Context) {
            db = PokemonDatabase.getInstance(context)
            //separate CoroutineScope for each database call
            CoroutineScope(Dispatchers.IO).launch {
                pokemonList.postValue(db.pokemonDao().loadAllPokemon())
            }
            CoroutineScope(Dispatchers.IO.launch{
                typeList.postValue(db.pokemonDao().loadAllType())
            }
        }
  
 

У вас OnCreate должно быть это

     mainViewModel.pokemonList.observe(this, Observer { pokemans ->
       //something like this
       pokemonAdapter.list = pokemans
    })
    mainViewModel.typeList.observe(this, Observer { types->
       //something like this
       typeAdapter.list = types
    })
 

При этом ваши данные должны поступать к вам довольно быстро, все более сложное повредит вам позже.

РЕДАКТИРОВАТЬ: Чтобы продолжить редактирование.

Вы почти на месте, вам просто нужен еще один обратный звонок, я воспользуюсь

androidx.lifecycle.Observer<T> для краткости.

 PersistenceSingleton.getInstance(getApplication())!!.getListOfPokemon(
 Observer<List<Pokemon>> { pokemon ->{
       pokemonList.postValue(pokemon)
 }
)
 

А затем в вашей getListOfPokemon функции

 CoroutineScope(Dispatchers.IO).launch {
    observer.onChanged(db.pokemonDao().loadAllPokemon())
}
 

Это вернет список покемонов, когда и только когда он будет доступен. И вам не придется беспокоиться о пустом списке покемонов, так как ничто никогда не испускается, пока не появятся фактические данные.

Комментарии:

1. Я знаю, что это четкий способ заставить это работать, но мне не нравится, что ViewModel должен запускать сопрограмму для запуска метода DAO. Только репозиторий должен получать данные из базы данных. Поэтому в моем случае проблема заключается в том, чтобы уведомить ViewModel, когда репозиторий получит данные. Я постараюсь быть более конкретным в этом вопросе

2. О, я удалил функцию getListOfPokemon (), потому что я опубликовал listOfPokemon. Но зачем делать еще один вызов Dao в getListOfPokemon (), если они уже загружены? Бесполезно делать все это, если мне нужно перезагрузить данные из базы данных

3. getListOfPokemon мог бы просто немедленно использовать данного наблюдателя, если в сохраненном списке уже есть покемон, вместо того, чтобы вытаскивать его снова, если он пуст, он получает его из DAO и использует наблюдателя, как в моем ответе.

4. Цель этого состоит в том, чтобы PersistanceSingleton, являющийся хранилищем, попытался загрузить все данные как можно скорее, а затем, когда представление нуждается в вызове ViewModel, чтобы получить данные. Если у ViewModel есть данные, все в порядке, в противном случае ViewModel ожидает данные