Kotlin Flow vs Android LiveData

20

Mam pytania dotyczące Kotlin Flow

  1. Mogę obserwować LiveData z wielu fragmentów. Czy mogę to zrobić za pomocą Flow? Jeśli tak to jak?
  2. Możemy mieć wiele LiveData z jednego LiveData za pomocą map& switchMap. Czy jest jakiś sposób na posiadanie wielu Flowów z jednego źródła Flow?
  3. Za pomocą MutableLiveDatamogę aktualizować dane z dowolnego miejsca za pomocą odwołania do zmiennej. Czy jest jakiś sposób, aby zrobić to samo z Flow?

Mam przypadek użycia: obserwuję SharedPreferencesużycie, callbackFlow{...}które da mi przepływ z jednego źródła. Na podstawie tego przepływu chcę utworzyć wiele przepływów dla każdej pary klucz-wartość.

Mogą to zabrzmieć głupie pytania. Jestem nowy w świecie Rx i Flow.

zoha131
źródło
Które podejście zdecydowałeś się na Flow lub LiveData ?
IgorGanapolsky
2
Obecnie używam LiveData do widoków i Flow do wszystkiego innego. W ViewModel otrzymuję Flow i emituje LiveData, aby obserwować z fragmentów.
zoha131,
@ zoha131 robisz to we właściwy sposób! Ponieważ LiveData można obserwować tylko w głównym wątku, idealnie pasują do interakcji View <-> ViewModel. Następnie przepływy umożliwiają wykonywanie bardziej złożonych operacji w pozostałej części architektury.
smora

Odpowiedzi:

15

Mogę obserwować LiveData z wielu fragmentów. Czy mogę to zrobić za pomocą Flow? Jeśli tak to jak?

Tak. Możesz to zrobić za pomocą emiti collect. Myśl emitjest podobna do danych na żywo postValuei collectjest podobna do observe. Podajmy przykład.

Magazyn

// 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()
}

Fragment

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
            }
    }
}

Możemy mieć wiele LiveData z jednego LiveData za pomocą map i switchMap. Czy jest jakiś sposób na posiadanie wielu Flowów z jednego źródła Flow?

Przepływ jest bardzo przydatny. Możesz po prostu stworzyć przepływ wewnątrz przepływu. Powiedzmy, że chcesz dołączyć znak stopnia do każdego z danych prognozy pogody.

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) 
                }
    }
}

Następnie zbierz dane w Fragmentie takim samym jak nr 1. Oto, co się dzieje, gdy model widoku zbiera dane z repozytorium, a fragment zbiera dane z modelu widoku.

Korzystanie z MutableLiveData Mogę aktualizować dane z dowolnego miejsca za pomocą odwołania do zmiennej. Czy jest jakiś sposób, aby zrobić to samo z Flow?

Nie możesz emitować wartości poza przepływem. Blok kodu wewnątrz przepływu jest wykonywany tylko wtedy, gdy istnieje kolektor. Ale możesz przekonwertować przepływ na dane na żywo za pomocą rozszerzenia asLiveData z LiveData.

ViewModel

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

W twoim przypadku możesz to zrobić

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
}

Edytować

Dzięki @mark za komentarz. Utworzenie nowego przepływu w modelu widoku dla getWeatherForecastfunkcji jest w rzeczywistości niepotrzebne. Można go ponownie zapisać jako

fun getWeatherForecast(): Flow<String> {
        return forecastRepository
                .getWeatherForecastEveryTwoSeconds(spendingDetailsRequest)
                    .map {
                        it + " °C"
                    }
    }
Fatih
źródło
Nie wiem dlaczego, ale założyłem, że nie mogę wywołać funkcji collect () w wielu miejscach dla jednego przepływu. Dziękuję za odpowiedź.
zoha131,
1
Nie. Możesz zebrać ten sam przepływ w wielu miejscach. val sharedPref = getSharedPref()i możesz używać zbierania w wielu miejscach sharedPref.collect {}. Jedyną rzeczą jest to, że kolekcjonowanie jest zawieszone, musisz wywołać go z bloku coroutine. I chętnie pomożemy np :)
Fatih
w przypadku mojego trzeciego pytania obejściem może być kanał nadawczy.
zoha131,
Możesz sprawdzić to zatwierdzenie dla używania kanałów zamiast danych na żywo. github.com/android/plaid/pull/770/commits/…
Fatih
1
Tak masz rację. To tutaj pojawia się przepływ. Kanały mają wiele rzeczy, o które musisz dbać i są gorące, co oznacza, że ​​są zawsze otwarte, nawet jeśli nie ma obserwatorów. Ale dzięki przepływowi możesz uzyskać te same korzyści bez obaw, ponieważ są zimne. Więc zamiast kanału myślę, że lepiej jest użyć przepływu
Fatih
3

W Flow.asLiveData()nowych androidx.lifecyclepakietach ktx dostępna jest nowa funkcja rozszerzenia . Możesz dowiedzieć się więcej w moim artykule: https://www.netguru.com/codestories/android-coroutines-%EF%B8%8Fin-2020

Samuel Urbanowicz
źródło
Kiedy musimy tego użyć?
IgorGanapolsky
1
Jeśli chcesz zaspokoić interfejs API, który wymaga LiveData za pomocą instancji Flow
Samuel Urbanowicz
Według Google musimy wybrać LiveData lub Flow: codelabs.developers.google.com/codelabs/…
IgorGanapolsky
1

W architekturze trójwarstwowej: prezentacja w domenie danych Flow powinien odbywać się w warstwie danych (bazy danych, sieć, pamięć podręczna ...), a następnie, jak wspomniał Samuel Urbanowicz , można zmapować Flow na LiveData.

Ogólnie rzecz biorąc, Flow jest prawie tym, co Obserable (lub Flowble) jest dla RxJava. Nie myl tego z LiveData.

więcej tutaj: https://medium.com/@elizarov/cold-flows-hot-channels-d74769805f9

gts13
źródło
Do operacji jednorazowych (tj. Odczytu bazy danych) wystarczy LiveData.
IgorGanapolsky