Musify is a clone of the popular Spotify app built completely using Jetpack compose. Both podcasts episodes and tracks are available for playback. Under the hood, it uses the Spotify API to fetch the data. It is completely built using Jetpack compose. It is also worth noting that it is not a complete one-on-one clone of the app. It can be considered as an app that is heavily inspired by the design of the official Spotify app with some custom design tweaks. It uses many API’s such as Hilt,Retrofit, and Paging 3. I built this project purely for educational purposes. I neither intend to release, nor do I plan on monetizing any part of this project.
- Demo
- Screenshots
- Tech Stack
- Notable features
- Source code, Architecture, & Testing
- Building and running the app
musify-demo-emulator.mp4
- Entirely written in Kotlin.
- Hilt for dependency injection.
- Jetpack Compose for UI and navigation.
- Kotlin Coroutines for threading.
- Accompanist placeholders.
- Coil compose for image loading and caching.
- Lottie compose for displaying animated resources such as a custom loading animation.
- Mokito-Kotlin for mocking dependencies in unit tests.
- Retrofit for communicating with the Spotify API.
- Exoplayer for background playback and music controls.
- Paging 3 for pagination.
- Google's palette api for color extraction from image.
- Kotlin Flows for creating reactive streams.
- Java 8 Date/Time API for dealing with date and time.
- Global Playback state
▶️ - The currently playing track gets highlighted in any screen that contains the currently playing track. This is achieved by maintaining a global playback state. Once the currently playing state changes, it will be notified to all the screens.
- Dynamic theme 🎨
- This app uses Google’s Palette API to fetch the background color of the screens based on an image. For example, the album art associated with the first search result determines the background gradient color of the search screen.
- Specific error messages
⚠️ - When there is an error, the app will try to be as specific as possible. For example, if no tracks are found for a particular search query, then it’ll be specific and state that no “tracks” where found instead of displaying a generic “no results found” message. This applies for the rest of the app as well. In certain cases, it even provides a retry button.
- Insets ⌨️
- The app uses insets to ensure that the content of the screen doesn't get overlaid by system views. It even accomondates the UI for the IME (on screen keyboard). For example, in the search screen, the loading animation will be moved up when the on screen keyboard appears.
- Time & Locale based in-app content 🕐
- The content displayed in the home screen is based on the current time and locale associated with the user's device. This is made possible because the Spotify API allows the specification of timestamp and locale as query parameters.
- Backstack management 🗂
- The app ensures that the backstack is properly managed. By poping upto, but not including the Home Screen everytime the user navigates to a bottom naivgation destination, the number of destinations in the backstack gets reduced. This imporves the overall UX of the app.
- Attention to tiny details 🔍
- The app was built with an attention to even the tiniest of details. These features might seem trivial, but it affects the UX negatively if they are not present. The app tries to mimic such functionality even though they might get unnoticed in order to immitate the features that the app would need, if it were to be officially released. The following are some of the many UX improving features that the app has.
- The clear button of the search bar in the search screen will only appear if there is text within it. It also uses a subtle animation while entering/exiting.
- If the user is inside a nested navigation destination associated with a bottom navigation destination, and taps on the bottom navigation icon, the backstack would be popped.
- Plural strings are used to display gramatically correct text. This can be specifically seen in the metadata of podcast episodes where the date and duration strings are formatted in a gramatically corrected manner. For example, an episode that has a duration of 1 hour is displayed as '1 hr', whereas an episode that has a duration of 2 or more hours, is displayed with 'hrs' as a suffix.
- HTML styled text🖌
- The app leverages compose-view interop to display html styled text. It's mainly used in the descriptions of the podcast show/episode detail screens. This means, these textfields can display text in different styles, such as italics and bold. Moreover, since the text is html styled, it can even display text in other forms such as lists. The URLs in the descriptions are even clickable, allowing the user to navigate to a linked website.
- Additional features 🎄
- As of writing this, the IOS version of the official Spotify app uses a dynamic background color in the search screen. The Android version of the app doesn't have that feature. The Musify app mimics that feature by using a dynamic background color for the search screen.
- All concrete implementations are prefixed by the term "Musify".
- Uses multi-repository pattern.
- MVVM archtecture.
- Commit messages follow the Conventional Commits specification.
- Consists of extensive unit tests with a predominant focus on testing the data layer.
- An illustration depicting the setup of the navigation graph can be found here.
- Create a Spotify account and log into the Spotify Developer Dashboard.
- Create an app from the dashboard and get the
Client ID
andClient Secret
. - Add the two fields to the
local.properties
file of your project in the following manner.
SPOTIFY_CLIENT_ID = PASTE-YOUR-CLIENT-ID-HERE
SPOTIFY_CLIENT_SECRET = PASTE-YOUR-CLIENT-SECRET-HERE
- Build the app in release mode and run it.
You might experience performance issues if you do not build the app in release mode. Please make sure to build the app in release mode. For more info on why this is important, especially in an app built completely with jetpack compose, read this blog post.