Modern Android Development

Tobi Oyelekan
The Startup
Published in
14 min readMay 27, 2020

Just starting out with Android development? Then this article is for you.

In this article, I’ll be discussing what’s new in android, the path I think you should follow, design patterns, good practices, and some dos and don’ts, so brace up let’s take this one after the other. I’ll be starting with MVVM.

MVVM

Model-View-View-Model is an architectural pattern that separates your business logic from UI code. Let’s take this bit by bit.

Model: Model represents your data holder, say something like a data class that is used to receive/hold data, say for example a User model used to receive data from a local database or server.

View: View represents the visual elements on the screen such as activity, fragment, buttons e.t.c

View Model: ViewModel is used to drive the UI basically. Observe the image below and let’s connect the dots together (to make whatever shape lol 😄)

The essence of ViewModel is to facilitate separation of concern. Separation of concern makes you write clean and good code, and it makes your code become less painful. So considering the image above, notice the arrows connected to each segment, that is how data would flow through the app. Let’s start with the repository part.

The repository is a core data source class that determines/decides whether to fetch data from the local DB or server. Ideally you should always follow the single source of truth pattern.

The diagram above is explanatory but let’s talk about it, the beginning says only fetch your data from the local database and dispatch the value/data to your UI (in this case the ViewModel), whether it is empty or not always ask for a refresh from the server to get the latest data (although you shouldn’t always fetch data from the internet all the time, you can use this rate limiter idea on the google github sample ap).

Once the data is gotten, do not directly dispatch that data to the UI, instead save the data to the local database, which your UI should respond to automatically. You should persist as much data as you can, and persist only what needs to be persisted, Room offers the benefit of offline capability which is very cool for the user experience, however, there are some things you just want to directly dispatch to the UI, functions like login, sign up e.t.c and therefore doesn’t need persistence. So basically your repository does all this work. So ViewModel is like a driver for your UI, it’s the supplier of data to the view (activity/fragment as described in the image above). The ViewModel relies on the repository for data. So how does the ViewModel communicate with the UI, it does this by using LiveData. LiveData is a data holder class that can be observed within a given lifecycle and it helps with configuration changes.

LiveData can be used in two ways; switchMap() and map(), switchMap() applies a function to the value stored in the LiveData object and unwraps and dispatches the result downstream. The function passed to switchMap() while map() transforms data to a LiveData. If you do a quick scroll to the code snippet below, you would see that switchMap returns a LiveData from userRepo.getUser() while map also returns a LiveData, but you can see the function getFullName doesn’t return LiveData, it just returns a String value and then the map transforms it to LiveData for it to be observed by the UI.

Do not reference your UI from the ViewModel because it could leak data. Your UI(activity/fragment) might lose data when a configuration change occurs, ViewModel will retain data to an extent, this is why you should keep your data in the ViewModel. However if the app is killed in the background ViewModel would lose its data. See this link for more about UI state and ViewModel.

Don’t do too much in the ViewModel, it wasn’t made for you to bloat it with logics, instead delegate the logics to another class, like a util class, especially plain java/kotlin codes. For example FileUtil to store, retrieve, delete or create files, codes like sorting list, reordering, loops, and all those things that don’t really require view, it makes testing a lot easier and sweet. Also do not expose MutableLiveData to your view, make MutableLiveData private, and expose a LiveData to your view like you see in the above picture.

So how then do we set a value to a MutableLiveData, always use a public method like the setUserId function in the code snippet above to set a MutableLiveData like you would in a regular getter-setter class.

Another take on ViewModel is as long as you make it the driver for your UI, all variables also don’t have to be LiveData, mostly LiveData is best used for UI changes; observe something and then react to that change. If it isn’t doing any of that then you could just declare it as a normal variable and you could still reference them from the UI say something like viewModel.status. I recommend this read to know more about LiveData

So let’s talk about the view (Activity/Fragment), it is the end consumer of our data, according to our first diagram, you see that it gets data from the ViewModel, and it has only one responsibility, guess what that is, to display data in a manner that a user can interpret and understand, say your recyclerview, buttons, TextView and so on, it is what the activity/fragment is meant to do, so why would you want to do otherwise, remember our SOC, make your code simple as possible. So how do we get data from ViewModel, say hello to Observer, yes you observe your data from ViewModel and display them accordingly. For example;

viewModel.userFullName.observe(viewLifeCycleOwner, Observer{ userFullNameView.text = it })

So let the view operations stay in view, including operations like listeners, adapters, setText, and so on. Now let’s talk about events in an activity, when you want to show toast messages, snack messages, or navigate to another activity or fragment, how then do we do this, since our activity/fragment takes order from our ViewModel, well, you use a class called EventObserver, it handles a change once i.e it observes only once, and subsequent calls to onChange() method won’t dispatch/observe any changes, this is the way to go with events because LiveData is known to always dispatch/observe previous data and it wouldn’t make sense when you navigate to another screen via LiveData and when you press back it fires again which is a bad experience, so to prevent that you use EventObserver.

To learn more about EventObserver and how it’s been used, check out this link.

RecyclerView + ListAdapter

I’d also love to talk about recyclerView, yea so we know recycler view is used to display a list of items. The old way is to use RecyclerView.Adapter<ViewHolder>, what is wrong with this? Actually nothing, but it could be better with ListAdapter. The notorious notifyDataSetChanged() is what we want to improve, normally you would call this method to populate your recyclerview once, and subsequently you make calls to notifyItemChanged…removed e.t.c. to update your list whenever your list changes, that is fine, the problem appears when you want to update the list and you call notifyDataSetChanged(), you shouldn’t do this because it rebinds all the view and the data(the old list and new list you adding ), this is inefficient and it's expensive to do, even google says you should rely on it as a last resort. Another problem again is imagined if you have a long list of items say 100 and you need to update 20 of them, you need to know each position of the item and apply notifyItemChanged(pos) to each of them, quite expensive and boilerplate code right? So a better way is to use ListAdapter with your recyclerview.

ListAdapter is a concept that utilizes an algorithm to calculate the difference between two lists (old list and new list) and dispatches the changes without rebinding all the list, it only updates the item that has changed in a list and adds to it if there’s a new item. Now instead of manually calling notify bla bla bla, ListAdapter uses DiffUtil to help you with that, you simply just call submitList(newList) upon the instance of your recyclerview.

submitList is all you would ever have to call in your activity/fragment, also remember that our data is coming from Room and when there’s a new list, ListAdapter does the calculation to find the old list in the adapter and the incoming list and updates it appropriately. When using ListAdapter with your recyclerview you don’t have to override getItemCount() it takes care of that for you. This is an example below;

To see full details on ListAdapter you can also check out this medium post by Craig Russell

Wait just before we move to another section, look at the ViewHolder and see how simple and neat it is, remember our SOC, recyclerview falls under the category of a view and it basically has only one job, to display a list of items, try as much as possible not to bloat your recycler view with business logic. In the above code snippet look at lines 17 and 18, it would have been a long code containing if and else statements which would become hard to test, if not impossible to test. Also, another thing that I love there is the use of ViewModel in the recyclerview, remember that ViewModel is a driver for the UI and we want to try as much as possible to reduce code in the view(activity/fragment), I love this method, it eliminates boilerplate code such as interfaces or lambdas to receive click actions in the view by overriding them in your activity/fragment, which you would probably still re-direct the actions back to the ViewModel (na double be that 😃).

Doing this won’t hurt and it ensures that your activity/fragment is not bloated with unnecessary code.

Now you can even make this even better further by reducing the actions in your ViewModel with a tool called DataBinding, although I’m not really a fan of it, used it a few times and it’s good.

DataBinding

DataBinding is a library that allows you to bind your UI components to a data source, such that it reduces code in your activity/fragment and ViewModel classes. So let’s see how this is done briefly (Note that this is just a basic introduction, not a full tutorial, you can check out see this free udacity course).

When using DataBinding you usually wrap your layout with alayout tag

So this layout directly communicates with the ViewModel with writing less code in the host fragment/activity, you just have to set it up first.

So imagine you have a button and onClick of the button you want to display a text on a TextView, the ViewModel and layout would basically look like this;

So your layout basically binds with the data in your ViewModel, more like observers. If you observe the code well you see that DataBinding allows us to declare a ViewModel in the layout, with this we can trigger any function in the ViewModel and directly propagate the data in our view like you see in line 37, that’s the power of DataBinding. It also helps eliminate boilerplate observers in your activity/fragment, so this brings us back to the recycler view again, no more click listeners in your adapter, you can now put them in your layout item, note that you can use the same ViewModel as the host fragment/activity, so your layout fires an action, your ViewModel receives it and the layout responds to your data change because they are bound. There’s a lot more to this, do well to check out this codelab for hands-on experience, you can also check out this interesting project for simple DataBinding implementation.

Next up is Navigation Component.

Navigation Component

Navigation Component is a library that eases navigation throughout your app. It preaches the use of a single activity and multiple fragments. Multiple fragments are connected together in a graph and hosted by a single activity. So you basically navigate from a fragment to another, and once set up its as easy as calling findNavController().navigate(fragment_id), it helps you manage backstack, and a lot more you can find on the official doc . Also do well to do this codelab to learn more.

What next, Room database

Room is a library that is used for persistence in android, gone are the days when we used to use SQLiteOpenHelper, we had to write boilerplate code and the awkward cursor.close() after every query or transaction.

Why should you use Room?

Room makes life easier when you want to support offline capability, you can’t for sure store everything in your shared preferences, so Room is good for storing complex data in a structured way. So, there are three components that makes up room which are Entity, Dao, and an abstract database class.

An entity is a table with a data class structure so basically it is a model, for example, if you want to store user data you do something like this:

The second component is Dao: Data Access Object is an interface that is used to access the database, it allows you to fetch, create, update, and delete records. Generally anything that has to do with querying the database is done in the Dao class.

Lastly, the abstract database class is where you set up the room database and connect all the daos and entities;

There is more to room database, check out of the codelab, popular room medium by Muntenescu

Next up is background work, coroutine and work manager

Coroutine

This will be very introductory. Firstly, as an Android developer you need to consider the performance of your app and how fast they respond, in this context, I’m talking about not running some heavy work on the main thread because the main thread is a single thread that handles all updates to the UI, like click listeners and all UI and lifecycle callbacks. The main thread can also dispatch other threads that may run in the background, therefore it is essential not to block the UI thread, blocking means the UI thread is waiting.

Coroutine is a lightweight thread that simplifies running asynchronous code in an idiomatic way, more like writing async code sequentially. It uses the suspend keyword to mark functions that may run in the background or main thread.

To use coroutines we need: Job, Dispatcher and Scope

Job is basically anything that can be canceled, all coroutine has a job and it can be used to cancel a coroutine.

Dispatcher sends off coroutine to run on various threads, for example;

Dispatchers.Main — run task on the main thread

Dispatchers.IO — run task off the main thread, like network calls

Dispatchers.Default — run intensive work

Scope combines information including a job dispatcher to define the context in which the coroutine runs. It keeps track of coroutine. So when a coroutine is launched, it runs within a scope

Do well to lay your hands on this codelab to how it works, and read extensively about coroutine here by Sean McQuillan

WorkManager is a library that allows you to schedule tasks that are guaranteed to run even if the app exits or the device restart. Imagine you have to run a task in the background that is deferrable ( i.e the task doesn’t need to be run immediately) for example, you want to periodically upload some data from your room database to the server.

You can schedule tasks in two ways in WorkManager. One is OneTimeWorkRequest and the other is PeriodicWorkRequest.

OneTimeWorkRequest is used to schedule a task that is to run once.

PeriodicWorkRequest: as its name implies, run a task periodically, i.e frequently run a task within a given time interval. The minimum time interval is 15 minutes.

When you create a WorkManager class, all you need to do is override doWork() and return Result.Success , you can also get the state of your work using observers. For example, you have an app where you always want to fetch data periodically and store them in your local DB or you want to send some data that has been stored in the local database to the server, you would typically register the WorkManager in your Application class to make it run once your app starts;

The Application Class is where you could globally set up somethings and make it accessible throughout your app

Interesting things about WorkManager is that you can schedule a task to run based on certain conditions with Constraints example are;

  • when there’s internet getRequiredNetworkType()
  • when the battery is not low requiresBatteryNotLow()
  • when the device storage is not low requiresStorageNotLow()
  • when the device is charging requiresCharging()
  • when the device is idle requiresDeviceIdle()

There’s a WorkManager codelab check it out to learn more.

WorkManager isn’t always the way to do background work, check out this doc to know why and how.

Material Components

The new Material components (is it really new 🤔), includes enhanced widgets with cool functionalities, beautiful designs. The material component is a new way to style your app, what you first need to do is add the dependency and then change the theme of your app with one of the material component themes, for example; Theme.MaterialComponents.Light.NoActionBar and you good to go, among other things we have newly designed buttons, chips, EditText, and great attributes to customize them to your taste. You almost don’t have to design a round button using drawables, with the new MaterialComponent you customize it right in the XML.

Want to start using MDC, check this Nick’s medium post, also do well to visit the material component warehouse for more info

Dependency Injection

DI in itself is a very broad topic, but let’s just have a basic understanding of what it is and why you need it.

Now what is DI?

DI is a concept whereby an object supplies the dependencies of another object. Hold still, let’s try to understand these terms well, for example a repository class needs a retrofit service to make requests from the server and probably also needs a dao object to fetch data from the database. The retrofit service and dao object are called dependencies and the act of providing them to the repository class is called dependency injection, just like a normal injection, the substance in the injection are the retrofit object/service and dao object that you want to inject into the body(repository class), and the injection itself is a class that houses the dependencies and provides it to whoever needs it :)

Now what birthed the idea of DI

let’s consider two classes A and B, consider this code below

With the code above we can say A is dependent on B and B is a dependency of A. Something is wrong with this code, we’ll talk about that later. Let’s take another real-life scenario, imagine a computer system with a soldered RAM, i.e it’s the RAM is not replaceable nor removable, this means when the RAM is faulty, it cannot be replaced and cannot even be tested with another RAM to see if it’s really the RAM that is even faulty. Therefore, you have to get another system. This situation is due to a tightly coupled system, now let’s revisit the problem with the code above, B is created in A, there’s a general rule that says classes shouldn’t create or instantiate objects/classes themselves, instead, they should be provided, and how do you provide them, you do so through constructors. So in the above code, we will now pass our dependency(B) via the constructor of A;

Why dependency injection? It’s a good software practice and the most benefit of all is it makes testing a breeze.

We can manually create and provide dependencies through constructors which is good, but as the app grows it becomes a pain in the ass and you should consider delegating it to a library called Dagger. There are other libraries like kodein and koin

Check out the official doc on Dagger and do well to lay your hands on this Dagger codelab. Ideally you should learn more about testing before moving into Dagger, I’d recommend this testing codelab

And that is all for now, I hope this helped someone 🙂

There are a number of things to look out for in Android Development that hasn’t be covered by this article or at least its been introduced, still, the best place to find them is the official doc;

Catch me on twitter anytime 👌

--

--