Почему одна и та же переменная во ViewModel и Fragment отличается? - PullRequest
0 голосов
/ 09 июля 2020

Я пытаюсь выполнить эти шаги в указанном порядке.

  1. Заполнить мою базу данных Room данными.
  2. Обновить переменную списка (в ViewModel) текущим содержимым базы данных.
  3. Переменная списка доступа (в ViewModel) из моего фрагмента.

После выполнения я получаю тосты в этом порядке

  • 0 в MythFragment
  • 5 в MythViewModel

Я ожидал того же количества элементов. Это отличается из-за сопрограмм? Обязательно ли использовать LiveData?

MythFragment.kt

class MythFragment : Fragment() {

    private lateinit var viewModel: MythViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_myth, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(MythViewModel::class.java)
        viewModel.populateMyths()
        viewModel.fetchFromDatabase()

        Toast.makeText(activity, "${viewModel.myths.size} in MythFragment", Toast.LENGTH_SHORT)
            .show()
    }
}

MythViewModel.kt

class MythViewModel(application: Application) : BaseViewModel(application) {

    var myths = listOf<Myth>()

    fun populateMyths() {
        launch {
            val dao = MythDatabase(getApplication()).mythDao()

            if (dao.getRowCount() > 0)
                return@launch

            val mythList = listOf<Myth>(
                Myth("This is the myth 1", "This is the evaluation of the myth 1"),
                Myth("This is the myth 2", "This is the evaluation of the myth 2"),
                Myth("This is the myth 3", "This is the evaluation of the myth 3"),
                Myth("This is the myth 4", "This is the evaluation of the myth 4"),
                Myth("This is the myth 5", "This is the evaluation of the myth 5")
            )

            dao.insertAll(
                *mythList.toTypedArray()
            )
        }
    }

    fun fetchFromDatabase() {
        launch {
            myths = MythDatabase(getApplication()).mythDao().getAllMyths()
            Toast.makeText(getApplication(), "${myths.size} in MythViewModel", Toast.LENGTH_SHORT)
                .show()
        }
    }
}

BaseViewModel.kt

abstract class BaseViewModel(application: Application) : AndroidViewModel(application),
    CoroutineScope {

    private val job = Job()

    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main

    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }
}

Ответы [ 2 ]

1 голос
/ 09 июля 2020

Поскольку вы заполняете базу данных одновременно с подпиской на поток данных, запрос get выполняется до завершения заполнения, что приводит к 0 in MythFragment. Когда вы получаете данные во второй раз, вы получаете 5 in MythFragment, так как данные уже были вставлены.

Итак, чтобы избежать параллелизма, вы можете сделать вызов последовательным или использовать Transactions из Room с LiveData, что гарантирует, что данные будут отправлены только тогда, когда все запросы, определенные внутри него, будут выполнены.

1 голос
/ 09 июля 2020

Я ожидал того же количества элементов. Это отличается из-за сопрограмм? Должен ли я использовать LiveData?

Да, каждый раз, когда вы используете launch, вы запускаете какой-то код в другом потоке. Вот почему у вас все еще 0 элементов в вашем Fragment. Лучше использовать LiveData, чтобы вы получали уведомление всякий раз, когда ваши данные меняются.

В вашем ViewModel вы можете сделать что-то вроде этого:

private val _myths = MutableLiveData<Myth>() // keep the mutable one private
val myths: LiveData<Myth> = _myths // expose an immutable one

fun fetchFromDatabase() {
    launch {
        _myths.postValue(MythDatabase(getApplication()).mythDao().getAllMyths()) 
        Toast.makeText(getApplication(), "${myths.size} in MythViewModel", Toast.LENGTH_SHORT).show()
    }
}

И в своем фрагменте вы наблюдаете изменения в мифах:

...
viewModel.populateMyths()
viewModel.fetchFromDatabase()
viewModel.myths.observe(viewLifecycleOwner, Observer { myths ->
    Toast.makeText(activity, "${myths.size} in MythFragment", Toast.LENGTH_SHORT).show()
})

Вы можете сделать приведенный выше код более кратким, используя livedata конструктор (требуется зависимость androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0 или выше):

val myths = livedata {
    emit(MythDatabase(getApplication()).mythDao().getAllMyths())
}

Тогда есть больше не нужно звонить fetchFromDatabase().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...