Как заменить LiveData на Flow

Есть один LiveData с именем, а затем у меня есть еще одна переменная с именем myData что замечает любые изменения в sortOrder и соответственно заполняет данные.

      class TestViewModel @ViewModelInject constructor() : ViewModel() {

    private val sortOrder = MutableLiveData<String>()

    val myData = sortOrder.map {
        Timber.d("Sort order changed to $it")
        "Sort order is $it"
    }

    init {
        sortOrder.value = "year"
    }

}

Наблюдение в действии

      class TestActivity : AppCompatActivity() {

    private val viewModel: TestViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        
        // Observing data
        viewModel.myData.observe(this) {
            Timber.d("Data is : $it")
        }
    }
}

Вопрос

  • Как я могу заменить описанный выше сценарий на Flow/StateFlow API без каких-либо изменений в выводе?

3 ответа

Решение

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

У меня есть ощущение, что они конкретизируют функции преобразования для StateFlow / SharedFlow, потому что очень неудобно сопоставлять их с холодными потоками и возвращать их обратно в горячие потоки.

Общедоступный поток должен быть SharedFlow, если вы не хотите вручную четко отображать первый элемент, потому что функция требует, чтобы вы напрямую указали начальное состояние.

          private val sortOrder = MutableStateFlow("year")

    val myData = sortOrder.map {
        Timber.d("Sort order changed to $it")
        "Sort order is $it"
    }.shareIn(viewModelScope, SharingStarted.Eagerly, 1)

Или вы можете создать отдельную функцию, которая вызывается внутри map а также в stateIn вызов функции.

          private val sortOrder = MutableSharedFlow<String>()
    
    private fun convertSortOrder(order: String): String {
        Log.d("ViewModel", "Sort order changed to $order")
        return "Sort order is $order"
    }

    val myData = sortOrder.map {
        convertSortOrder(it)
    }.stateIn(viewModelScope, SharingStarted.Eagerly, convertSortOrder("year"))

С точки зрения фрагмента / активности вы должны создать задание, которое собирает поток в onStart() и отменить в onStop(). С использованием lifecycleScope.launchWhenStartedсохранит поток активным даже в фоновом режиме.

Используйте библиотеку bindin для перехода на Flowлегко. Я пристрастен, хотя.

См. Статью на Medium об этом.

yourViewModel

          private val _sortOrder = MutableStateFlow("")
    val sortOrder: StateFlow<String> = _sortOrder

yourActivity

      lifecycleScope.launchWhenStarted {
        // Triggers the flow and starts listening for values
        yourViewModel.sortOrder.collect { sortOrder ->
           // your code 
        }
    }

Примечание

Использование функций launchWhen() из расширений Lifecycle Kotlin для сбора потока с уровня пользовательского интерфейса не всегда безопасно. Когда представление переходит в фоновый режим, сопрограмма приостанавливается, оставляя активным базовый производитель и потенциально генерируя значения, которые представление не потребляет. Такое поведение может привести к потере ресурсов ЦП и памяти.

StateFlows безопасно собирать с помощью функций launchWhen(), поскольку они привязаны к ViewModels, заставляя их оставаться в памяти, когда View переходит в фоновый режим, и они выполняют легкую работу, просто уведомляя View о состояниях пользовательского интерфейса. Однако проблема может возникнуть у других производителей, которые выполняют более интенсивную работу.

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