Building A Shazam Clone Part 4 (TDD with Kotlin) — Building Android Apps Series

In the last post, we did some initial project setup. Its been a while since the last post and I sincerely apologize for the delay. In this post, we will be implementing the main feature of the app which is to identify the song that is playing around the user.

This and the next coming posts is going to be more in-depth, filled with lots of activity (coding). Just in case you prefer videos, I created a companion video lesson that explains the concepts on this post:

Video lesson

The app will be written completely in Kotlin like I said in the last post. We will also be using the test-driven development (TDD) process to build each feature of the app.

Test-driven development is a software development process that relies on the repetition of a very short development cycle: first the developer writes an (initially failing) automated test case that defines a desired improvement or new feature, then produces the minimum amount of code to pass that test, and finally refactors the new code to acceptable standards.

When developing a feature, you start by writing a new (UI) test. The test fails at first because the feature isn’t implemented yet. A feature is usually made up of multiple units. For each unit, you write a corresponding unit test. Your unit test should be comprehensive, covering standard interactions, invalid inputs or other errors. You can check out the talk at Google IO 17 on test-driven development on Android.

Let’s dive into the project and see some TDD in practice. You can download the starter project here and follow the post step by step. Doing this will help you understand the process better.

In order to save some time, I added all the needed permission and dependencies to the project already. I will briefly walk through them. First, the app needs the following permissions to work:

The INTERNET, ACCESS_NETWORK_STATE, and RECORD_AUDIO are required by the app. Notice here, that we are only requesting the permissions we need. This is best practice, you don’t want the user thinking, oh why does a music identifier app need access to my contact.

Also, you will notice that the normal permissions are separated from the dangerous permissions. You don’t have to request for normal permissions at runtime on Android 6.0 and above, but you have to do that for dangerous permissions. I personally like to separate them when I declare them in the app manifest file, but this is not a rule and you don’t have to.

Second, the libraries needed by the app. I added most of the required dependencies to the app build.gradle file already:

We added some support library dependencies and some dependencies required to write and run our tests. I added Gson, which is a library used to serialize and deserialize Java objects to JSON. I also added Glide, the image loading library and SpinKit which is just a fancy progress view library.

If you have been using older versions of Gradle, you will notice that we are using implementation instead of compile. Compile got deprecated in Gradle 3.0 and should be replaced with implementation or api. You can read this StackOverflow post for more details.

You might have observed that we are not putting in the libraries version numbers rather we have something like $rootProject.ext.gsonVersion. We are defining the libraries version at the project level using something called extra properties. The top-level build.gradle file contains the code below:

After defining the versions here, we can easily reference them in each module build.gradle. This has some benefits:

  1. If you have multiple modules in your project that use the same library, you can easily add or update the version across modules. This doesn’t apply to this project because we only have one module in this project which is the app module.
  2. For multiple libraries from the same family with the same version code, it’s quite useful to just declare the version number in one property. An example is the Android support library.

We will be using a 3rd party service called ACRCloud to identify the song that is playing. While building our own music identification engine will be fun, It will take a lot of effort to make it great and I don’t want to get into any legal issues with Shazam or other players. ACRCloud is really powerful and is being used by big apps like MusixMatch.

If you are feeling adventurous, you can check out this awesome python audio fingerprinting library. To learn more about how music identification (audio fingerprinting) works, you can read about it in this blog post.

Fortunately, ACRCloud has an Android SDK which you can easily include to your app. I just downloaded it and added the native libraries for each device architecture in the jniLibs folder. I also added the SDK jar file to the project libs folder.

With all the setup covered, let’s move to building the discover feature. Remember I said the project will be structured by feature. We will create a new package for the discover song feature called discover.

Inside of the discover package, create a new empty Activity with the name DiscoverActivity. It will be the default launcher Activity. When Android studio is done creating the Activity, you should get an Activity with some Kotlin code like below:

If you are new to Kotlin, I will like to point out a few things from the code above:

  1. To declare a supertype (parent class), we place the type after a colon in the class header. Example:
class Dog: Animal

2. If you are overriding a function from a superclass, you must explicitly say that in the function header using override :

override fun bark() {
// I will bark only when you feed me
}

3. Functions need the fun keyword as you can see above

4. The type of the savedInstanceState variable came after the variable name. This is one important difference between Kotlin and Java. Here is an example below:

In Kotlin:

// When instantiating, you do not need the new keyword :) Pretty neat
val myDog: Dog = Dog()

In Java:

// New keyword required in Java
Dog myDog = new Dog()

5. When the activity is created for the first time, the savedInstanceState variable is null. In Kotlin, you need to explicitly specify that a variable can be null using the question mark (?). Like in the code above, you will notice Bundle? instead of just Bundle. If you don’t specify this, the variable will be non-null by default and if you try to pass in a null value the compiler will complain.

Android studio should also generate a layout file called activity_discover. Let’s edit the layout file with the content below:

In this project, Activities will just be hosting fragments. They will be doing little to no work.

Before showing or creating the DiscoverFragment, let’s write a test that verifies that the DiscvoerFragment is always shown when the activity view is loaded.

The name of the test will be DiscoverActivityTest and it belongs in the androidTest folder. We will be using Espresso to write the UI test:

So what the test does is to look for the view with discoverFragmentView id, and check that it is displayed. We haven’t created the View with that id yet, so we can’t run the test yet. Head over to the discover package and create a new Fragment called DiscoverFragment. When Android studio is done, the fragment should have some Kotlin codes similar to this:

We want to create the discoverFragmentView id, so we have to modify the fragment layout file and add the id.

Now run the DiscoverActivityTest, since we haven’t added the code to display the fragment, the test should fail.

Let’s go ahead to add the implementation in DiscoverActivity that will show the DiscoverFragment :

The FragmentUtils.addIfNotExist object class is just a utility function I created to add the fragment only if it doesn’t exist already. Here is the code for it:

As you can see, the FramentUtils is an object rather than a class. In Kotlin, an object is a singleton. You do not need to create an instance to use it. All functions declared inside an object can be used almost the same way you will use Java static methods/fields.

Try running the DiscoverActivityTest again, it should pass this time since we have added the implementation. With that, we do not have to write more code since we have satisfied the test. This is TDD (Beautiful right?).

I will publish more posts in the series this weekend. Stay tuned.

If you enjoyed this story, please click the 👏 button and share to help others find it! Also feel free to leave a comment below if you have any suggestions or questions.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store