Kotlin Flow против Android LiveData

21

У меня есть несколько вопросов о Kotlin Flow

  1. Я могу наблюдать LiveData из нескольких фрагментов. Могу ли я сделать это с Flow? Если да, то как?
  2. Мы можем иметь несколько LiveData из одной LiveData, используя map& switchMap. Есть ли способ иметь несколько потоков из одного источника потока?
  3. Используя MutableLiveDataя могу обновить данные из любой точки мира, используя ссылку на переменную. Есть ли способ сделать то же самое с Flow?

У меня есть такой пример использования: я буду наблюдать за SharedPreferencesиспользованием, callbackFlow{...}которое даст мне единственный источник потока. Из этого потока я хочу создать несколько потоков для каждой пары ключ-значение.

Это может звучать глупо. Я новичок в мире Rx и Flow.

zoha131
источник
Какой подход вы выбрали - Flow или LiveData ?
Игорь Ганапольский
2
В настоящее время я использую LiveData для представлений и Flow для всего остального. В ViewModel я получаю Flow и отправляю LiveData для наблюдения из фрагментов.
zoha131
@ zoha131 вы делаете это правильно! Поскольку LiveData можно наблюдать только в основном потоке, они идеально подходят для взаимодействий View <-> ViewModel. Затем потоки позволяют вам выполнять более сложные операции в остальной части вашей архитектуры.
Смора

Ответы:

16

Я могу наблюдать 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"
                    }
    }
Фатих
источник
Я не знаю почему, но у меня было предположение, что я не могу вызвать функцию collect () в нескольких местах для одного потока. Спасибо за ответ.
zoha131
1
Нет. Вы можете собрать один и тот же поток в нескольких местах. val sharedPref = getSharedPref()и вы можете использовать собирать в нескольких местах sharedPref.collect {}. Единственное, потому что сбор приостановлен, вам нужно вызвать его из блока сопрограмм. И рад помочь нп :)
Фатих
для моего третьего вопроса, обходной путь может быть вещательным каналом.
zoha131
Вы можете проверить этот коммит для использования каналов вместо живых данных. github.com/android/plaid/pull/770/commits/…
Фатих
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

В трехуровневой архитектуре: представление предметной области, поток должен происходить на уровне данных (базы данных, сеть, кэш-память ...) и затем как отметил Сэмюэль Урбанович, вы можете сопоставить поток с LiveData.

В общем, Flow - это почти то, что Observable (или Flowable) для RxJava. Не путайте это с LiveData.

больше здесь: https://medium.com/@elizarov/cold-flows-hot-channels-d74769805f9

gts13
источник
Для одноразовых операций (т. Е. Чтения базы данных) достаточно LiveData.
Игорь Ганапольский