Upgrade to Pro — share decks privately, control downloads, hide ads and more …

From LiveData to Coroutines & Flow - Android Summit

Jossi Wolf
October 08, 2020

From LiveData to Coroutines & Flow - Android Summit

Slides from my talk from Android Summit 2020 about converting LiveData to Coroutines & Flow, with a small section about StateFlow.

Jossi Wolf

October 08, 2020
Tweet

More Decks by Jossi Wolf

Other Decks in Programming

Transcript

  1. @jossiwolf class HomeViewModel { val latestData: LiveData<String> = MutableLiveData() fun

    refresh() { val data = fetchData() latestData.post(data) } } LiveData - an example
  2. @jossiwolf class HelpViewModel( private val supermarketRepo: SupermarketRepository ): ViewModel() {

    fun fetchSupermarketOpeningHours(id: SupermarketId) = supermarketRepo.fetchOpeningHours(id).map { openingHours #-> openingHours.hours.filter { it #!= null } } } Our ViewModel
  3. @jossiwolf class HelpViewModel( private val supermarketRepo: SupermarketRepository ): ViewModel() {

    fun fetchSupermarketOpeningHours(id: SupermarketId) = supermarketRepo.fetchOpeningHours(id).map { openingHours #-> openingHours.hours.filter { it #!= null } } } Our ViewModel
  4. @jossiwolf class HelpViewModel( private val supermarketRepo: SupermarketRepository ): ViewModel() {

    fun fetchSupermarketOpeningHours(id: SupermarketId) = supermarketRepo.fetchOpeningHours(id).map { openingHours #-> openingHours.hours.filter { it #!= null } } } Our ViewModel
  5. @jossiwolf class HelpViewModel( private val supermarketRepo: SupermarketRepository ): ViewModel() {

    fun fetchSupermarketOpeningHours(id: SupermarketId) = supermarketRepo.fetchOpeningHours(id).map { openingHours #-> openingHours.hours.filter { it #!= null } } }
  6. @jossiwolf class HelpViewModel( private val supermarketRepo: SupermarketRepository ): ViewModel() {

    fun fetchSupermarketOpeningHours(id: SupermarketId) = supermarketRepo.fetchOpeningHours(id).map { openingHours #-> openingHours.hours.filter { it #!= null } } }
  7. @jossiwolf class HelpViewModel( private val supermarketRepo: SupermarketRepository ): ViewModel() {

    fun fetchSupermarketOpeningHours(id: SupermarketId) = supermarketRepo.fetchOpeningHours(id).map { openingHours #-> openingHours.hours.filter { it #!= null } } }
  8. @jossiwolf <X, Y> LiveData<Y> map( LiveData<X> source, Function<X, Y> mapFunction

    ) { MediatorLiveData<Y> result = new MediatorLiveData#<>(); result.addSource(source, (x) #-> { result.setValue(mapFunction.apply(x)); }); return result; } cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.java androidx.lifecycle.Transformations#map
  9. @jossiwolf <X, Y> LiveData<Y> map( LiveData<X> source, Function<X, Y> mapFunction

    ) { MediatorLiveData<Y> result = new MediatorLiveData#<>(); result.addSource(source, (x) #-> { result.setValue(mapFunction.apply(x)); }); return result; } cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.java androidx.lifecycle.Transformations#map
  10. @jossiwolf <X, Y> LiveData<Y> map( LiveData<X> source, Function<X, Y> mapFunction

    ) { MediatorLiveData<Y> result = new MediatorLiveData#<>(); result.addSource(source, (x) #-> { result.setValue(mapFunction.apply(x)); }); return result; } cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.java androidx.lifecycle.Transformations#map
  11. @jossiwolf <X, Y> LiveData<Y> map( LiveData<X> source, Function<X, Y> mapFunction

    ) { MediatorLiveData<Y> result = new MediatorLiveData#<>(); result.addSource(source, (x) #-> { result.setValue(mapFunction.apply(x)); }); return result; } cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.java androidx.lifecycle.Transformations#map
  12. @jossiwolf <X, Y> LiveData<Y> map( LiveData<X> source, Function<X, Y> mapFunction

    ) { MediatorLiveData<Y> result = new MediatorLiveData#<>(); result.addSource(source, (x) #-> { result.setValue(mapFunction.apply(x)); }); return result; } cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.java androidx.lifecycle.Transformations#map
  13. @jossiwolf fun fetchOpeningHours(id: SupermarketId): LiveData<OpenHoursInformation> { val liveData = MutableLiveData<OpenHoursInformation>()

    val openingHours = poiService.downloadOpeningHours(poiType = SUPERMARKET, id).enqueue( object: Callback<OpenHoursInformation>() { override fun onResponse(call: Call<OpenHoursInformation>, response: Response<OpenHoursInformation>) { liveData.postValue(response.body()) } ##... } ) liveData.postValue(openingHours) return liveData } Repository with LiveData + Callbacks
  14. @jossiwolf suspend fun fetchOpeningHours(id: SupermarketId): LiveData<OpenHoursInformation> { val liveData =

    MutableLiveData<OpenHoursInformation>() val openingHours = poiService.downloadOpeningHours(poiType = SUPERMARKET, id) liveData.postValue(openingHours) return liveData } Repository with LiveData + Coroutines
  15. @jossiwolf With Migration Helper suspend fun fetchOpeningHours(id: SupermarketId) = poiService.downloadOpeningHours(poiType

    = SUPERMARKET, id) @Deprecated fun fetchOpeningHoursAsLiveData(id: SupermarketId) = liveData { emit(fetchOpeningHours(id)) }
  16. @jossiwolf With Migration Helper suspend fun fetchOpeningHours(id: SupermarketId) = poiService.downloadOpeningHours(poiType

    = SUPERMARKET, id) @Deprecated fun fetchOpeningHoursAsLiveData(id: SupermarketId) = liveData { emit(fetchOpeningHours(id)) }
  17. @jossiwolf With Migration Helper suspend fun fetchOpeningHours(id: SupermarketId) = poiService.downloadOpeningHours(poiType

    = SUPERMARKET, id) @Deprecated fun fetchOpeningHoursAsLiveData(id: SupermarketId) = liveData { emit(fetchOpeningHours(id)) }
  18. @jossiwolf With Migration Helper suspend fun fetchOpeningHours(id: SupermarketId) = poiService.downloadOpeningHours(poiType

    = SUPERMARKET, id) @Deprecated fun fetchOpeningHoursAsLiveData(id: SupermarketId) = liveData { emit(fetchOpeningHours(id)) }
  19. @jossiwolf With Migration Helper suspend fun fetchOpeningHours(id: SupermarketId) = poiService.downloadOpeningHours(poiType

    = SUPERMARKET, id) @Deprecated fun fetchOpeningHoursAsLiveData(id: SupermarketId) = liveData(context = Dispatchers.IO) { emit(fetchOpeningHours(id)) }
  20. @jossiwolf class HelpViewModel( private val repo: SupermarketRepository ): ViewModel() {

    fun fetchSupermarketOpeningHours(id: SupermarketId) = repo.fetchOpeningHours(id).map { openingHours #-> openingHours.hours.filter { it #!= null } } } Our ViewModel
  21. @jossiwolf class HelpViewModel( private val repo: SupermarketRepository ): ViewModel() {

    fun fetchSupermarketOpeningHours(id: SupermarketId) = repo.fetchOpeningHoursAsLiveData(id).map { openingHours #-> openingHours.hours.filter { it #!= null } } } Our ViewModel
  22. @jossiwolf class HelpViewModel( private val repo: SupermarketRepository ): ViewModel() {

    fun fetchSupermarketOpeningHours(id: SupermarketId) = repo.fetchOpeningHoursAsLiveData(id).map { openingHours #-> openingHours.hours.filter { it #!= null } } } Our ViewModel
  23. @jossiwolf Our ViewModel class HelpViewModel( private val repo: SupermarketRepository ):

    ViewModel() { fun fetchSupermarketOpeningHours(id: SupermarketId) = repo.fetchOpeningHoursAsLiveData(id).map { openingHours #-> openingHours.hours.filter { it #!= null } } }
  24. @jossiwolf Repository with Migration Helper suspend fun fetchOpeningHours(id: SupermarketId) =

    poiService.downloadOpeningHours(poiType = SUPERMARKET, id) @Deprecated fun fetchOpeningHoursAsLiveData(id: SupermarketId) = liveData(context = Dispatchers.IO) { emit(fetchOpeningHours(id)) }
  25. @jossiwolf Our ViewModel class HelpViewModel( private val repo: SupermarketRepository ):

    ViewModel() { fun fetchSupermarketOpeningHours(id: SupermarketId) = repo.fetchOpeningHoursAsLiveData(id).map { openingHours #-> openingHours.hours.filter { it #!= null } } }
  26. @jossiwolf Our ViewModel class HelpViewModel( private val repo: SupermarketRepository ):

    ViewModel() { fun fetchSupermarketOpeningHours(id: SupermarketId) = liveData(context = Dispatchers.IO) { val openingHours = repo.fetchOpeningHours(id) val filteredHours = openingHours.hours.filter { it #!= null } emit(filteredHours) } }
  27. @jossiwolf suspend fun fetchSupermarketDetails( id: SupermarketId ): LiveData<SupermarketDetails> { val

    liveData = MutableLiveData<OpenHoursInformation>() val cachedDetails = detailsCache[id] if (cachedDetails #!= null) liveData.postValue(cachedDetails) val freshDetails = poiService.downloadDetails(SUPERMARKET, id) liveData.postValue(freshDetails) return liveData } With LiveData
  28. @jossiwolf suspend fun fetchSupermarketDetails( id: SupermarketId ): LiveData<SupermarketDetails> { val

    liveData = MutableLiveData<OpenHoursInformation>() val cachedDetails = detailsCache[id] if (cachedDetails #!= null) liveData.postValue(cachedDetails) val freshDetails = poiService.downloadDetails(SUPERMARKET, id) liveData.postValue(freshDetails) return liveData } With LiveData
  29. @jossiwolf suspend fun fetchSupermarketDetails( id: SupermarketId ): LiveData<SupermarketDetails> { val

    liveData = MutableLiveData<OpenHoursInformation>() val cachedDetails = detailsCache[id] if (cachedDetails #!= null) liveData.postValue(cachedDetails) val freshDetails = poiService.downloadDetails(SUPERMARKET, id) liveData.postValue(freshDetails) return liveData } With LiveData
  30. @jossiwolf fun fetchSupermarketDetails( id: SupermarketId ): Flow<SupermarketDetails> = flow {

    val cachedDetails = detailsCache[id] if (cachedDetails #!= null) emit(cachedDetails) val freshDetails = poiService.downloadDetails(SUPERMARKET, id) emit(freshDetails) } With Flow
  31. @jossiwolf fun fetchSupermarketDetails( id: SupermarketId ): Flow<SupermarketDetails> = flow {

    val cachedDetails = detailsCache[id] if (cachedDetails #!= null) emit(cachedDetails) val freshDetails = poiService.downloadDetails(SUPERMARKET, id) emit(freshDetails) } With Flow
  32. @jossiwolf fun fetchSupermarketDetails( id: SupermarketId ): Flow<SupermarketDetails> = flow {

    val cachedDetails = detailsCache[id] if (cachedDetails #!= null) emit(cachedDetails) val freshDetails = poiService.downloadDetails(SUPERMARKET, id) emit(freshDetails) } With Flow
  33. @jossiwolf fun fetchSupermarketDetails( id: SupermarketId ): Flow<SupermarketDetails> = flow {

    val cachedDetails = detailsCache[id] if (cachedDetails #!= null) emit(cachedDetails) val freshDetails = poiService.downloadDetails(SUPERMARKET, id) emit(freshDetails) } With Flow
  34. @jossiwolf With Flow + Migration Helper fun fetchSupermarketDetails( id: SupermarketId

    ): Flow<SupermarketDetails> = flow { … } @Deprecated("Please use Flows directly.") fun fetchSupermarketDetailsAsLiveData( id: SupermarketId ): LiveData<SupermarketDetails> = fetchSupermarketDetails(id).asLiveData()
  35. @jossiwolf With Flow + Migration Helper fun fetchSupermarketDetails( id: SupermarketId

    ): Flow<SupermarketDetails> = flow { … } @Deprecated("Please use Flows directly.") fun fetchSupermarketDetailsAsLiveData( id: SupermarketId ): LiveData<SupermarketDetails> = fetchSupermarketDetails(id).asLiveData(Dispatchers.IO)
  36. @jossiwolf With Flow + Migration Helper fun fetchSupermarketDetails( id: SupermarketId

    ): Flow<SupermarketDetails> = flow { … } @Deprecated("Please use Flows directly.") fun fetchSupermarketDetailsAsLiveData( id: SupermarketId ): LiveData<SupermarketDetails> = fetchSupermarketDetails(id).asLiveData(Dispatchers.IO)
  37. @jossiwolf With Flow + Migration Helper fun fetchSupermarketDetails( id: SupermarketId

    ): Flow<SupermarketDetails> = flow { … } @Deprecated("Please use Flows directly.") fun fetchSupermarketDetailsAsLiveData( id: SupermarketId ): LiveData<SupermarketDetails> = fetchSupermarketDetails(id).asLiveData(Dispatchers.IO)
  38. @jossiwolf “A Flow that represents a read-only state with a

    single updatable data value that emits updates to the value to its collectors.” - StateFlow Documentation # StateFlow
  39. @jossiwolf Our ViewModel class HelpViewModel( private val repo: SupermarketRepository ):

    ViewModel() { fun fetchSupermarketOpeningHours(id: SupermarketId) : LiveData<OpeningHoursState> }
  40. @jossiwolf Our ViewModel class HelpViewModel( private val repo: SupermarketRepository ):

    ViewModel() { fun fetchSupermarketOpeningHours(id: SupermarketId) : Unit }
  41. @jossiwolf Our ViewModel class HelpViewModel(…): ViewModel() { val _openingHoursState =

    MutableStateFlow(OpeningHoursState.Empty) val openingHoursState: StateFlow<OpeningHoursState> = _openingHoursState fun fetchSupermarketOpeningHours(id: SupermarketId) { viewModelScope.launch(Dispatchers.IO) { val openingHours = repo.fetchOpeningHours() _openingHoursState.value = openingHours } } }
  42. @jossiwolf Our ViewModel class HelpViewModel(…): ViewModel() { val _openingHoursState =

    MutableStateFlow(OpeningHoursState.Empty) val openingHoursState: StateFlow<OpeningHoursState> = _openingHoursState fun fetchSupermarketOpeningHours(id: SupermarketId) { viewModelScope.launch(Dispatchers.IO) { val openingHours = repo.fetchOpeningHours() _openingHoursState.value = openingHours } } }
  43. @jossiwolf Our ViewModel class HelpViewModel(…): ViewModel() { val _openingHoursState =

    MutableStateFlow(OpeningHoursState.Empty) val openingHoursState: StateFlow<OpeningHoursState> = _openingHoursState fun fetchSupermarketOpeningHours(id: SupermarketId) { viewModelScope.launch(Dispatchers.IO) { val openingHours = repo.fetchOpeningHours() _openingHoursState.value = openingHours } } }
  44. @jossiwolf All methods of data flow are thread- safe and

    can be safely invoked from concurrent coroutines without external synchronization. - StateFlow Documentation
  45. @jossiwolf -Single API instead of two APIs -More control over

    execution context -Integrates nicely with Jetpack Compose Why StateFlow?