Sample App — Android Unidirectional Data Flow
Using LiveData in Coinverse
Code in the wild is always better to learn from than slide samples. Here I’ll be sharing code from Coinverse’s Open App to showcase a live Unidirectional Data Flow (UDF) pattern which makes the app more readable, easier to develop for, and debug.
Open source code:
Coinverse is the first audiocast app for cryptocurrency news, also including YouTube and text. Check it out on the Play…
About UDF + LiveData:
Android Unidirectional Data Flow with LiveData — 2.0
Improving State Models + Coroutines Flow
The previous post outlines the strategy for loading a newsfeed and removing adjacent ads using UDF. This post shares both a basic live example, and a more complex structure to return multiple processes in one method using
By downloading the GitHub project, you can run the code to explore, listen on-the-go, and watch the newsfeed of audiocast and video content. For access to features requiring authentication such as saving and dismissing content, you’ll need to implement the 5 easy-ish steps to set up a Firebase project outlined in Coinverse Open App — Set Up.
Brief UDF Overview
View Events, State, and Effects
- Events — Actions created by the user that effect the data displayed, or system level events like the app starting or stopping
- State — Info that displays or helps determine a view, created in the business layer of logic
- Effects — One time user interface occurrences like navigation, dialogs, and toasts created by the business layer of logic
LiveData provides a simple, Android lifecycle aware, toolset to handle automatically updating data for events.
Loading, Content, Error Network Requests
Network requests returned with LCE states wrapped in LiveData objects ensure that the ViewModel business logic layer accounts for the loading, successful content loaded, and error states.
View Event, State, and Effect — ContentSelected
In this first case when video content is selected in the Adapter it triggers a
ContentSelected view event in the ContentFragment.kt. The event is processed in the ContentViewModel.kt.
contentType is a video, the ViewModel updates the
contentToPlay LiveData value of the view state
FeedViewState with the
ContentToPlay object containing a video url.
The Fragment observes the
contentToPlay value change, and launches the Fragment to play the video.
In addition to the view state being updated in the ViewModel above, a view effect,
NotifyItemChanged is added and observed in the Fragment to update the RecyclerView cell that has been selected.
MediatorLiveData — PlayerLoad
In the first case the
ContentSelected event is straightforward because there is a view event and one piece of data, the
ContentToPlay object, returned in the view state. The
PlayerLoad event created from the AudioFragment.kt, that retrieves the required information to play an audiocast, is more complicated because it requires two network requests.
PlayerViewState returned requires retrieving information from two sources for both the mp3 file to play audio, and a converted Bitmap image to display in the notification player.
The ViewModel could achieve getting both pieces of information by making one network request, waiting for it to return successfully, then make the second request. However, this creates more complicated and nested code. In order for the ViewModel to observe LiveData from the network requests it must be observed by the UI layer in the AudioFragment. The first network request must be observed, then within that method, the second request needs to be observed. This creates nested
LCE code within the ContentViewModel as well which gets ugly.
A Better Solution With MediatorLiveData
A better solution is to return one LiveData object that contains both the mp3 and Bitmap image using
combineLiveData takes in two LiveData objects,
getContentUri returning the mp3, and
bitmapToByteArray returning the Bitmap image.
combineLiveData only returns the
ContentPlayer LiveData once each LiveData item passed into the method has been populated with info, indicated by an emitted boolean in the code below.
This allows for each of the two methods requiring a network request to handle their LCEs separately, and for one LiveData object to be observed from the
ContentPlayer in the AudioFragment.