Building a podcast app series: 2. Player state
After the view is set up, it’s very important to have a model that will drive that UI, something that we can observe and render our UI according to its state. It would be nice to react to model changes with some kind of a listener and it would be great if we don’t have to manually subscribe/unsubscribe to this model. Luckily, Android has a ViewModel class that is built for this exact purpose.
ViewModel
View modes are a relatively new invention. Before ViewModels came along, and the whole AAC (android architecture components), representing ViewModels in a clean and safe way was particularly tricky. Android activity or a fragment can be in multiple states and properly connecting/disconnecting a particular activity or a fragment is a dance around onCreate / onDestrory, onResume / onPause, and other lifecycle methods. Since our in-app player view is visible on all screens, wee need a globally accessible state holder for that player. Luckily, a ViewModel is all we need to safely share the player state across different fragments. Since we are using a single activity pattern, ViewModels are automatically tied to that activity and can be shared across fragments! This means that we can easily tell other views what is the state of our in-app player by just sharing a ViewModel across multiple fragments! Oh, by the way, ViewModels survive configuration change, so that is also being taken care of.
So, how do we create a new view model? We can simply write in any fragment
private val playerViewModel by sharedViewModel<PlayerViewModel>()
The basic idea here is that ViewModels do not care who is using its LiveData, it can be a single fragment or bunch of them, it does not matter. ViewModels should not reference any Fragment or Activity, they simply expose data for Views to observe!
If you are curious about the code for the PlayerViewModel check out this link
LiveData
Once we have a shared state holder for our player, we need a safe way to update our views. That is where LiveData comes in, it is a safe container for any kind of data. And the best part about LiveData is that it automatically knows if our fragment or activity is active and can receive updates, so we don’t have to handle that logic ourselves! This means no more null pointer exceptions of illegal state exceptions. We are free to post new data to LiveData and be sure that nothing will crash as a result of that.
How do we create and update live data?
private val _currentlyPlaying = MutableLiveData<Episode>()
currentlyPlaying.value = currentEpisode
Repository pattern
The last piece of the state puzzle is to identify where does our data come from? In a simple word: from a Repository of Podcasts! Repository pattern is such a simple pattern and very useful. We can make our Repository do a bunch of interesting things. We can cache data to the local DB, we can mock our data, we can fetch something from the internet, everything related to data, and its retrieval is hidden behind our Repository class.
How does a simple repository look like?
class PodcastRepository {
fun getPodcasts(): List<Podcast> =
listOf(Podcast("The Joe Rogan experience"))
}
Basic setup for every screen
In conclusion, every screen in our app will reference ViewModels and will subscribe to a LiveData. ViewModels will fetch data from Repositories and update a LiveData and we have a reactive setup for each screen in our app. Error handling is done in the same manner, we just have a LiveData of error messages where we can push errors and observe them inside Fragments.
Browse the full code on this link. Stay tuned for the next article in this series where we explore exoPlyaer, brains behind any podcast app.