What I learned When I Moved (Partly) TodoApp to Compose

Nepal Brothers
8 min readMar 31, 2021

I am not going to lie when I say I love writing Jetpack Compose. It’s feels good to be writing one. But, I am also the one who usually does not look into libraries until they are in beta. The documentation reads like this,

Jetpack Compose is currently in beta. The 1.0 API surface is feature-complete but may contain bugs.

Hence Disclaimer

I personally do not think Compose is matured enough to be used in production. With some of its components are currently beta (which is awesome), many of its module libraries are still in alpha, which is subject to change.

However, Google is saying they will release Compose by the end of the year. Therefore, I recommend getting familiar with Compose, as I think it is nicely written and it could just be matter of days for Compose to be de-facto standard for writing the UI components on Android.

TL;DR

We will migrate a part of MVVM based TodoApp to use Compose. While doing so, we will get into changing the XML based UI to the Compose based UI, which includes some of the things that would get you started on Compose, but then quickly gets into some building blocks that you usually see in day-to-day Android work.

Here, I explore some of the areas and try to go over these questions.

  1. How to build UI Elements
  2. How to build a list of UI Elements
  3. How to connect Compose with Fragment and ViewModels
  4. How to connect ViewModel with compose
  5. How Unidirectional Data Flow and Event-Flow make easy to use Compose
  6. How to show Alerts, and save state locally in compose, like cancelling Alert
  7. How to load network image in compose
  8. Disclaimer (See the topic right above this)

Code

The compose branch contains the compose code.

Before moving to Jetpack Compose

Here is a simple TodoApp and these below are two screens. It is a simple MVVM uni-directional data-flow app with Dagger, Retrofit, Glide. The UI is written in XML, just like any other UIs are made today. Since Jetpack Compose is the UI library, any other part of the code is untouched.

  1. Screen1: Dashboard is a list of Todos.
  2. Screen2: Add a Todo

Composify Dashboard: Convert from RecyclerView

If you look at the Dashboard, there are two things we can observe.

  1. It is a list of Cards.
  2. The list is made up of RecyclerView.

The best way to work them out is by going through 1 and then 2.

How to Create UI Elements: Creating Card

Card is a compose element of CardView (this has been a very common building UI Element on Android) . Here is a brief overview of some of the components in Jetpack Compose that are present in current XML layouts.

  1. Text (aka TextView), Image (aka ImageView) are pretty self-explanatory. They are Components.
  2. Modifier adds the behavior to the element. It is like adding attributes in the XML (almost sort of), but it adds little more. Now, modifications need to be done programatically.
  3. Column (Linear Layout with Vertical orientation) places components in stack. If we want to put components in stacked in row, place them in Row.
  4. In line 30, we have placed two Buttons in the Row. Note that we are aligning them to the End (which places elements to the right).

How to Create a list of Todo Cards: A Replacement to RecyclerView

This is where Compose shines. Making a list is super easy. Adapter is removed. One thing we could do is put this in Column, but by default, you cannot scroll things down. To add this capability, we can use Modifier with scrollable (since modifier adds behavior, this would simply make column scrollable). However, documentation suggests,

If you want to show a list of items, consider using LazyColumn and LazyRow instead of these APIs. LazyColumn and LazyRow feature scrolling, and they are much more efficient than the scrolling modifier because they only compose the items as they're needed. See Lists for more information.

Therefore, below implementation will use LazyColumn to get the maximum optimization Jetpack Compose has to provide.

One thing to note in the code above is the DSL items . This essentially is a wrapper that lazily loads whatever is inside it. In our case, we want to lazily load the TodoDisplayCard.

How to Bind Compose to Fragment or Activity

If you have an existing app (which mostly we do) there are some few changes we will need to make. Creating a new app in Compose is not undoable, but it is definitely good to only give it a try. Migrating to using Compose in existing Fragment and Activity would be easy to transition.

A gateway for the Fragment/Activity to load the ComposeViews is simply using the ComposeView UI Element.

Here, in the ComposeView, we are setting a content, theme for the compose, and Surface (which is where we put our ui elements).

How do we pass the pass the data from ViewModel to Compose?

Now, since the best practice is to do business logic in the ViewModel, lets pass the viewModel into the ComposeView. We need to pass the ViewModel. Here, todoViewModel could be any ViewModel defined in your fragment.

Next, lets change the TodoDisplayList compose function to accept ViewModel.

TodoDisplayList (line 23 and 15) is overloaded, and it is simply to make the Compose function as stateless as possible. This has advantage on testing, and also It can help used in displaying the preview, as shown in the third function, TodoDisplayListPreview.

Now that we have passed the viewModel, lets focus about how we are passing the data. The data is flowing from TodoDisplayList to TodoDisplayCard. In other words, data is flowing from parent to children, also known as uni-directional data flow.

Unidirectional data-flow has the advantage of data flowing down. As a result, you only need to make a change on the top level part of the flow, which in turn, will flow down to the children as needed. And when you have to maintain data only in one place, it is so much easier to know what needs to be flown down.

Handling Click (or any other) Event in Compose

Okay, lets talk about event. How we want to handle click. For instance, let’s say we want the todos present in the list to be editable. When we click on one of those, we would like to open up a new Fragment, where it can be editable.

We can handle them anywhere we want, but that would be hard to maintain if we happen to have many of the Compose components. As we have moved data from parent to children, we generally want to move the event from children to parent (when necessary).

For instance, navigation is provided in the Fragment level, although you can pass navigation to the ComposeView and then to components, we can centralize that into one place and handle in Fragment (or Compose could also be a good place to have navigation).

Here, the click event that occurs when clicking on the TodoDisplayCard moves up to its parent, TodoDisplayList, then moves up to the ComposeView, then ultimately moves to Fragment, where we handle the navigation stuff.

How to show Alerts? Intro to local state

Cool that we were able to handle the state in the ViewModel, and unidirectional data flow really made Compose so easy to write down. But, it is not always that we want to store the state in the ViewModel.

For instance, showing an alert could only be some local thing. Alerts are cool, and it is great way to alert user to confirm they want to actually delete. So, lets create an Alert, but also know how to work with this.

If you think about the behavior the Alerts show, they usually alert the user, and based on what user selects, the alert just disappears. Current implementation in Compose does not close when you display the alert. Which means, you will need to handle how to stop showing alert.

And this is the best way to get introduction about local state. You are storing the state of the alert. And when the state changes, we can write a custom logic to show the Alert if we need to.

var showDeleteAlert by remember { mutableStateOf(false) }

The above statement basically makes the compose remember the value created inside it. mutableStateOf allows to mutate the variable showDeleteAlert and when its value changes, it gets set for recomposition (fancy word for saying the views will be updated).

As you would have guessed, if showDeleteAlert is true, the Alert is displayed. Otherwise, we want it to not show.

How to load Image from Network?

The above discussions are really cool in that it exposes you to the compose. And this one is no different. Right now Compose allows you to load image from Image Resource, or Vector, it does not allow from the network. And, that is cool, because it makes us learn something deep of Android that potentially will be automated in awesome cool library.

When loading an image, we would usually pick our favorite Image library, be it Glide, Coil or Picassa, and then use them to load it over the network programatically (and I just realized there is no easy way to define it in the XML, unless we write custom attribute and its not a pleasant experience for sure).

Creating it in Compose is not hard. And infact, since Composable functions can be overloaded (feature from language), we can write a function with the same name, giving it a native feel.

The above looks so easy that we can just pass the uri and you will get an Image. But, to make that happen, we use produceState of Jetpack Compose that basically creates the state during the composition. As you can see, we are loading the image in different thread, and then passing that value as an state. The return value is State , meaning that the value present in the state will change over time, and Composable function will keep tracking at the state so to trigger a recomposition when the state changes.

Line 21 is simply an overload of the Image composable function that Jetpack Compose provides.

This is what I got for this article. But compose is more powerful and I have found it to make UIs more flexible and cool with animations and gestures. It’s future is looking bright and it is definitely worth taking a look at how its done in Compose.

Let me know if you would like me to go deeper into any of these.

--

--