#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 ожидает данные