Kotlin Flow проти Android LiveData


20

У мене є запитання щодо Kotlin Flow

  1. Я можу спостерігати LiveData з декількох фрагментів. Чи можу я це зробити за допомогою Flow? Якщо так, то як?
  2. Ми можемо мати декілька LiveData з однієї LiveData за допомогою map& switchMap. Чи є спосіб мати декілька потоків з одного джерела потоку?
  3. За допомогою MutableLiveDataя можу оновлювати дані з будь-якого місця, використовуючи змінну посилання. Чи можна зробити те ж саме з Flow?

У мене є такий випадок використання: я спостерігаю за SharedPreferencesвикористанням, callbackFlow{...}яке дасть мені єдиний джерело Flow. З цього потоку я хочу створити кілька потоків для кожної пари ключ-значення.

Це можуть звучати нерозумно. Я новачок у світі Rx і Flow.


На якому підході ви погодилися - Flow або LiveData ?
ІгорГанапольський

2
Наразі я використовую LiveData для перегляду та потоку для всіх інших. У ViewModel я отримую Flow та випромінює LiveData для спостереження з фрагментів.
zoha131

@ zoha131 ти робиш це правильно! Оскільки LiveData можна спостерігати лише в основному потоці, вони ідеально підходять для перегляду <-> взаємодій ViewModel. Тоді Flows дозволяє вам робити більш складні операції в іншій частині вашої архітектури.
смора

Відповіді:


15

Я можу спостерігати LiveData з декількох фрагментів. Чи можу я це зробити за допомогою Flow? Якщо так, то як?

Так. Це можна зробити за допомогою emitі collect. Думаю emit, схожий на живі дані postValueі collectсхожий на observe. Наведемо приклад.

Сховище

// I just faked the weather forecast
val weatherForecast = listOf("10", "12", "9")

// This function returns flow of forecast data
// Whenever the data is fetched, it is emitted so that
// collector can collect (if there is any)
fun getWeatherForecastEveryTwoSeconds(): Flow<String> = flow { 
    for (i in weatherForecast) {
        delay(2000)
        emit(i)
    }
}

ViewModel

fun getWeatherForecast(): Flow<String> {
    return forecastRepository.getWeatherForecastEveryTwoSeconds()
}

Фрагмент

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    // Collect is suspend function. So you have to call it from a 
    // coroutine scope. You can create a new coroutine or just use 
    // lifecycleScope
    // https://developer.android.com/topic/libraries/architecture/coroutines
    lifecycleScope.launch {
            viewModel.getWeatherForecastEveryTwoSeconds().collect {
                    // Use the weather forecast data
                    // This will be called 3 times since we have 3 
                    // weather forecast data
            }
    }
}

Ми можемо мати декілька LiveData з однієї LiveData, використовуючи map & switchMap. Чи є спосіб мати декілька потоків з одного джерела потоку?

Потік дуже зручний. Ви можете просто створити потік всередині потоку. Скажімо, ви хочете додати знак ступеня до кожного з даних прогнозу погоди.

ViewModel

fun getWeatherForecast(): Flow<String> {
    return flow {
        forecastRepository
            .getWeatherForecastEveryTwoSeconds(spendingDetailsRequest)
                .map {
                    it + " °C"
                }
                .collect {
                    // This will send "10 °C", "12 °C" and "9 °C" respectively
                    emit(it) 
                }
    }
}

Потім збирайте дані у фрагменті так само, як №1. Тут відбувається те, що модель перегляду збирає дані з сховища, а фрагмент збирає дані з перегляду.

Використовуючи MutableLiveData, я можу оновлювати дані з будь-якого місця за допомогою змінної посилання. Чи можна зробити те ж саме з Flow?

Ви не можете виділяти значення поза потоком. Блок коду всередині потоку виконується лише тоді, коли є будь-який колектор. Але ви можете конвертувати потік у живі дані, використовуючи розширення asLiveData від LiveData.

ViewModel

fun getWeatherForecast(): LiveData<String> {
    return forecastRepository
    .getWeatherForecastEveryTwoSeconds()
    .asLiveData() // Convert flow to live data
}

У вашому випадку ви можете це зробити

private fun getSharedPrefFlow() = callbackFlow {
    val sharedPref = context?.getSharedPreferences("SHARED_PREF_NAME", MODE_PRIVATE)
    sharedPref?.all?.forEach {
        offer(it)
    }
}

getSharedPrefFlow().collect {
    val key = it.key
    val value = it.value
}

Редагувати

Дякуємо @mark за його коментар. Створення нового потоку в моделі перегляду для getWeatherForecastфункції насправді непотрібне. Це може бути переписано як

fun getWeatherForecast(): Flow<String> {
        return forecastRepository
                .getWeatherForecastEveryTwoSeconds(spendingDetailsRequest)
                    .map {
                        it + " °C"
                    }
    }

Я не знаю чому, але у мене було припущення, що я не можу викликати функцію збирання () у кількох місцях для одного потоку. дякую за відповідь.
zoha131

1
Ні. Ви можете збирати один і той же потік в декількох місцях. val sharedPref = getSharedPref()і ви можете використовувати збирати в декількох місцях sharedPref.collect {}. Єдине, тому що збирання призупинено, вам потрібно зателефонувати до нього з блоку кореневих програм. І раді допомогти np :)
Fatih

для мого третього питання вирішенням може бути канал мовлення.
zoha131

Ви можете перевірити цю комісію щодо використання каналів замість живих даних. github.com/android/plaid/pull/770/commits/…
Fatih

1
Так, ти правий. Сюди надходить потік. У каналах є багато речей, про які потрібно подбати, і вони гарячі, тобто вони завжди відкриті, навіть якщо спостерігачів немає. Але з потоком ви можете отримати ті самі переваги без жодних турбот, оскільки вони холодні. Тож замість каналу я думаю, що краще використовувати потік
Fatih

3

У Flow.asLiveData()нових androidx.lifecycleпакетах ktx є нова функція розширення . Ви можете дізнатися більше в моїй статті: https://www.netguru.com/codestories/android-coroutines-%EF%B8%8Fin-2020


Коли нам потрібно використовувати це?
ІгорГанапольський

1
Коли ви хочете задовольнити API, який вимагає LiveData з екземпляром Flow
Самуель Урбанович,

За даними google, нам потрібно вибрати LiveData або Flow: codelabs.developers.google.com/codelabs/…
IgorGanapolsky

1

У трирівневій архітектурі: презентація домену даних, Flow повинен відбуватися в шарі даних (бази даних, мережа, кеш ...), і тоді, як згадував Самуель Урбанович, ви можете зіставити Flow на LiveData.

Загалом, Flow - це майже те, що спостерігається (або Flowable) для RxJava. Не плутайте це з LiveData.

докладніше тут: https://medium.com/@elizarov/cold-flows-hot-channels-d74769805f9


Для операцій з одним знімком (тобто зчитування бази даних) достатньо LiveData.
ІгорГанапольський
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.