I am currently working on a project to rewrite an existing podcast app that was originally built with xamarin into a fully native Android app. The most important feature of any podcast app is the player, and specifically, the in-app player that has to:
- be visible on all screens
- have collapsed and expanded view
Let’s clarify the second point, the in-app player should have two modes, collapsed mode where the player’s view is small and should sit below the main content. The second mode is expanded mode, where the player’s view should occupy the whole screen. Ideally, there should be an animation when the player is transitioning between those two states. Let’s talk about solving problem number 1.
If we use the navigation library and single activity pattern from AAC (android architecture components), we can easily make our in-app player visible on all screens by restricting the navigation host fragment to be above the in-app player’s view. Since the navigation framework loads / unloads all views inside the navigation host fragment, we can make all views in our app span just up to the in-app player. If the player is hidden, all views automatically span over it, otherwise, they will span to just above the player.
A nice side effect of using the single activity pattern is the player itself will be configured from one place, main activity, and in combination with a view model, we can implement the player without violating the DRY principle. Now let’s talk about implementing the second feature.
Ever since the coordinator layout came out, Android became a lot more flexible in terms of animations and interaction between views. In this specific use case all I had to do was to write a normal constraint layout and add one line to it:
<androidx.constraintlayout.widget.ConstraintLayout .... app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
That was enough to have a view that can sit on the bottom of the screen, be expanded and animate those state changes out of the box. Pretty awesome if you ask me. So, how does the final layout look like?
<CoordinatorLayout> <MainContent/> <InAppPlayer/> </CoordinatorLayout>
Of course, nothing is this simple, is it? :). But, what if our app has tabs and a BottomNavigationView? Our in-app player should, of course, sit above the BottomNavigationView but below the main content. One solution to this edge case is to position the main content above the bottom tabs’ view using constraint layout.
DRY principle and ViewModel
Since the player exists across multiple screens (fragments), we need a view model to group together all the player features in one place and just reuse the view model across all views. What better way to implement this than to use AAC ViewModel class. We can easily share this view model across fragments and have a player UI in sync at all times. PlayerViewModel can get data from a PodcastRepository class which will make network calls or read from the local database.
Please refer to this GitHub link for the code.
Stay tuned for the next article in this series!