Несколько 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, чтобы изменить видимость представлений фрагмента с невидимой на видимую.

Другие вопросы по тегам