Несколько LiveData/StateFlow для одной пары Fragment-ViewModel
У меня есть фрагмент, который представляет экран в приложении с одним действием, и ViewModel для этого фрагмента.
ViewModel использует несколько репозиториев для загрузки набора данных из нескольких вызовов API и передает эти данные фрагменту через несколько StateFlow.
Предположим, что фрагмент имеет 2 представления, каждое представление собирает данные из связанного с ним StateFlow. Пока все 2 представления не отображают свои данные, я хочу показать некоторый индикатор выполнения, а затем, когда эти представления будут получать данные, анимируйте видимость всего фрагмента от невидимого до видимого.
У меня вопрос: как правильно обработать, когда все 2 представления получили свои данные?
Репозиторий:
class Repository(private val name: String) {
private val _data = MutableStateFlow<String?>(null)
val data = _data.asStateFlow()
suspend fun load() {
// load from the DB/API if no data
// load fresh from the API if have data
delay(1000)
_data.emit("$name data")
}
}
ViewModel:
class ScreenViewModel : ViewModel() {
// will be injected by Dagger2/Hilt
private val repository1 = Repository("Repository1")
private val repository2 = Repository("Repository2")
private val _flow1 = MutableStateFlow<String?>(null)
private val _flow2 = MutableStateFlow<String?>(null)
val flow1 = _flow1.asStateFlow()
val flow2 = _flow2.asStateFlow()
init {
viewModelScope.launch {
repository1.data.collect {
_flow1.emit(it)
}
}
viewModelScope.launch {
repository2.data.collect {
_flow2.emit(it)
}
}
viewModelScope.launch {
repository1.load()
}
viewModelScope.launch {
repository2.load()
}
}
}
Фрагмент:
class Screen : Fragment(/*layoutd*/) {
private val viewModel: ScreenViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewModel.flow1.filterNotNull().collect {
draw1(it)
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.flow2.filterNotNull().collect {
draw2(it)
}
}
}
private fun draw1(data: String) {
// some stuff
}
private fun draw2(data: String) {
// some stuff
}
}
1 ответ
В этом случае я бы выбрал BindingAdapters .
Сначала в моем файле ViewModel (вне класса ViewModel) я бы создал
Enum Class
class ScreenViewModel : ViewModel() { .... }
enum class LoadingStatus {
LOADING,
ERROR,
FINISHED
}
Затем я бы создал внутри ViewModel MutableLiveData, поддерживаемый LiveData, для отслеживания статуса загрузки.
private val _status = MutableLiveData<LoadingStatus>()
val status: LiveData<LoadingStatus>
get() = _status
Внутри блока инициализации я бы изменил значение MutableLiveData
init {
viewModelScope.launch {
try {
//before fetching data show progressbar loading
_status.value = LoadingStatus.LOADING
repository1.data.collect {
_flow1.emit(it)
}
//after fetching the data change status to FINISED
_status.value = LoadingStatus.FINISHED
}
catch (e:Exception) {
_status.value = LoadingStatus.FINISHED
}
}
В отдельном файле Kotlin я пишу код для адаптера привязки. Это заставляет ProgressBar исчезать в зависимости от статуса.
@BindingAdapter("apiLoadingStatus")
fun ProgressBar.setApiLoadingStatus(status:LoadingStatus?){
when(status){
LoadingStatus.LOADING -> {
visibility = View.VISIBLE
}
LoadingStatus.FINISHED -> {
this.visibility = View.GONE
}
LoadingStatus.ERROR -> {
this.visibility = View.GONE
}
}
}
Затем включите этот код для XML-фрагмента ProgressBar. Обратите внимание , я использую DataBinding здесь
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:motion="http://schemas.android.com/tools">
<!-- TODO: Add data binding node -->
<data>
<variable
name="viewModel"
type="com.yourPackageName.ViewModel" />
</data>
<ImageView
android:id="@+id/statusImageView"
android:layout_width="192dp"
android:layout_height="192dp"
app:apiLoadingStatus="@{viewModel.status}"
В основном, чтобы этот код работал, вам необходимо включить привязку данных, а также добавить зависимость для ViewModel / LiveData в файле gradle.
Вы также можете написать еще один BindingAdapter, чтобы изменить видимость представлений фрагмента с невидимой на видимую.