Talk from KUGMUC on 20.08.2020.
Recording at https://www.youtube.com/watch?v=GQ_U3LsB3s0!
Jossi Wolf@jossiwolfFrom LiveData toCoroutines Flow
View Slide
@jossiwolf@jossiwolf
@jossiwolfLive Data, LiveData everywhere
@jossiwolfªġƛŸƦŊƴŸƞǛßŊġǕwŸĚġŢmŊǔġ(öƴöÇXmöǛġƞXŭǔŸŞġƦmŊǔġ(öƴöXŭǔŸŞġƦ
@jossiwolf”LiveData is an observable dataholder class”- Android Developers Documentation
@jossiwolfclass HomeViewModel {val latestData: LiveData = MutableLiveData()}LiveData - an example
@jossiwolfclass HomeViewModel {val latestData: LiveData = MutableLiveData()fun refresh() {val data = fetchData()latestData.post(data)}}LiveData - an example
@jossiwolfclass HomeFragment: Fragment() {fun setupUI() {latestData.observe(viewLifecycleOwner) { data #->##...}}}LiveData - an example
@jossiwolf# LiveData is great!*when used properly
@jossiwolf# LiveData &Execution Context
@jossiwolfclass SupermarketRepository {fun fetchOpeningHours(id: SupermarketId): LiveData = ##...}Our Repository
@jossiwolfclass HelpViewModel(private val supermarketRepo: SupermarketRepository): ViewModel() {fun fetchSupermarketOpeningHours(id: SupermarketId) =supermarketRepo.fetchOpeningHours(id).map { openingHours #->openingHours.hours.filter { it #!= null }}}Our ViewModel
@jossiwolfclass HelpViewModel(private val supermarketRepo: SupermarketRepository): ViewModel() {fun fetchSupermarketOpeningHours(id: SupermarketId) =supermarketRepo.fetchOpeningHours(id).map { openingHours #->openingHours.hours.filter { it #!= null }}}
@jossiwolf LiveData map(LiveData source,Function mapFunction) {MediatorLiveData 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.javaandroidx.lifecycle.Transformations#map
@jossiwolf”The events are dispatched on themain thread.”- Android Developers Documentation
@jossiwolf# LiveData Observers are always calledon the main thread
@jossiwolf# It’s easy to let things slide when usingLiveData
@jossiwolfReplace LiveDataMake sure threading is doneproperly
@jossiwolfReplace LiveData@jossiwolf
@jossiwolfOne-Shot Streams of Data
@jossiwolfCoroutines!One-Shot Streams of Data
@jossiwolfCoroutines! FlowOne-Shot Streams of Data
@jossiwolf# Converting to Coroutines
@jossiwolffun fetchOpeningHours(id: SupermarketId): LiveData {val liveData = MutableLiveData()val openingHours = poiService.downloadOpeningHours(poiType = SUPERMARKET, id).enqueue(object: Callback() {override fun onResponse(call: Call, response: Response) {liveData.postValue(response.body())}##...})liveData.postValue(openingHours)return liveData}Repository with LiveData + Callbacks
@jossiwolfsuspend fun fetchOpeningHours(id: SupermarketId): LiveData {val liveData = MutableLiveData()val openingHours = poiService.downloadOpeningHours(poiType = SUPERMARKET, id)liveData.postValue(openingHours)return liveData}Repository with LiveData + Coroutines
@jossiwolfsuspend fun fetchOpeningHours(id: SupermarketId) =poiService.downloadOpeningHours(poiType = SUPERMARKET, id)That method as Coroutine…
@jossiwolfWith Migration Helpersuspend fun fetchOpeningHours(id: SupermarketId) =poiService.downloadOpeningHours(poiType = SUPERMARKET, id)@Deprecatedfun fetchOpeningHoursAsLiveData(id: SupermarketId) =liveData {emit(fetchOpeningHours(id))}
@jossiwolfWith Migration Helpersuspend fun fetchOpeningHours(id: SupermarketId) =poiService.downloadOpeningHours(poiType = SUPERMARKET, id)@Deprecatedfun fetchOpeningHoursAsLiveData(id: SupermarketId) =liveData(context = Dispatchers.IO) {emit(fetchOpeningHours(id))}
@jossiwolfclass HelpViewModel(private val repo: SupermarketRepository): ViewModel() {fun fetchSupermarketOpeningHours(id: SupermarketId) =repo.fetchOpeningHours(id).map { openingHours #->openingHours.hours.filter { it #!= null }}}Our ViewModel
@jossiwolfclass HelpViewModel(private val repo: SupermarketRepository): ViewModel() {fun fetchSupermarketOpeningHours(id: SupermarketId) =repo.fetchOpeningHoursAsLiveData(id).map { openingHours #->openingHours.hours.filter { it #!= null }}}Our ViewModel
@jossiwolfOur ViewModelclass HelpViewModel(private val repo: SupermarketRepository): ViewModel() {fun fetchSupermarketOpeningHours(id: SupermarketId) =repo.fetchOpeningHoursAsLiveData(id).map { openingHours #->openingHours.hours.filter { it #!= null }}}
@jossiwolfRepository with Migration Helpersuspend fun fetchOpeningHours(id: SupermarketId) =poiService.downloadOpeningHours(poiType = SUPERMARKET, id)@Deprecatedfun fetchOpeningHoursAsLiveData(id: SupermarketId) =liveData(context = Dispatchers.IO) {emit(fetchOpeningHours(id))}
@jossiwolfOur ViewModelclass 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)}}
@jossiwolf# Converting to Flow
@jossiwolfsuspend fun fetchSupermarketDetails(id: SupermarketId): LiveData {val liveData = MutableLiveData()val cachedDetails = detailsCache[id]if (cachedDetails #!= null) liveData.postValue(cachedDetails)val freshDetails = poiService.downloadDetails(SUPERMARKET, id)liveData.postValue(freshDetails)return liveData}With LiveData
@jossiwolffun fetchSupermarketDetails(id: SupermarketId): Flow = flow {val cachedDetails = detailsCache[id]if (cachedDetails #!= null) emit(cachedDetails)val freshDetails = poiService.downloadDetails(SUPERMARKET, id)emit(freshDetails)}With Flow
@jossiwolfWith Flow + Migration Helperfun fetchSupermarketDetails(id: SupermarketId): Flow = flow { … }@Deprecated("Please use Flows directly.")fun fetchSupermarketDetailsAsLiveData(id: SupermarketId): LiveData =fetchSupermarketDetails(id).asLiveData()
@jossiwolfWith Flow + Migration Helperfun fetchSupermarketDetails(id: SupermarketId): Flow = flow { … }@Deprecated("Please use Flows directly.")fun fetchSupermarketDetailsAsLiveData(id: SupermarketId): LiveData =fetchSupermarketDetails(id).asLiveData(Dispatchers.IO)
@jossiwolf- developer.android.com/topic/libraries/architecture/livedata- proandroiddev.com/dont-use-livedata-in- repositories-f3bebe502ed3- medium.com/@elizarov/execution-context-of-kotlin-flows-b8c151c9309bResources
@jossiwolf