Bye XML, it was nice knowing you (pt. 3)

Filip Wiesner
MateeDevs
Published in
6 min readApr 9, 2021

This article will focus on the basics of Compose and example of implementation of specific UI design (just looks — no state management nor architecture).
Not everything is explicitly defined for a reason. You should follow along with your own code and not just copy mine :)
If you really don’t want to code along or are just stuck,
here is the full project.
Part 1
here and part 2 here.

In the previous part, we’ve learned what Modifier is and how it works. Then we’ve learned how to persist object between recompositions with remember and how to use State to notify Compose runtime of our changes.
This time we’ll look into using LazyRow/LazyColumn, the Compose version of RecyclerView.

Design by Aris Rahmat Fatoni

Before we get to the LazyRow we’ll have to have something to display inside it, and this looks like a perfect place for a Card component from Material design. But first things first, let’s divide this card into components. I’ve decided on:
1. HouseImage
2. HouseInfo
3. HouseFeatureList
and again, you can split it more, less, or just differently if you want to.

House image

Image handling is not an easy task, and there are many “hidden” steps like caching and scaling, so it’s not a shame if we reach for a library to do the heavy lifting for us. There are a few image loading libraries in the Android world like Picasso, Glide, or the relatively new Coil. I’ve chosen to use Coil because it’s 100% Kotlin and there is a wrapper for Compose by Chris Banes, so it will be easy to adopt.

With the library settled, we can get down to business. I’ve prepared some data, so you don’t have to search for random houses. Let’s first prepare the root HouseCard component with Material design Card inside it. Because the card will be in a horizontal row, we should define its width, so all of the cards are the same regardless of the image size. We can do that using the width modifier (I’ve set the width to 300 dp). The Card component also takes shape so we can provide the large shape: shape = MaterialTheme.shapes.large.

Next, we’ll create the HouseImage component, and it will just be the CoilImage component that will display the image from house.pictureUrl. But just showing the image won’t be enough. In the design, we can see that the image is supposed to be square and should have a corner radius.
While we can round the corners using the Coil-provided RoundedCornersTransformation, I don’t think that will be as fun (and easy) as using Modifier to cut the corners ourselves, and additionally, we can use the MaterialTheme provided shapes for it. With Modifier, we can just add .clip(MaterialTheme.shapes.medium) , and voilà, we have our rounded shape! To make the image square, it will be just one additional line: .aspectRatio(1f) (I actually used 10/9f for my implementation, but that’s just a detail).

House info

The HouseInfo component seems pretty simple at first glance, but there are a couple of things we need to get right.
First, we’ll put both texts inside a Column and then wrap that inside Row so the rating can be next to it. So far, our hierarchy looks like this.

As with the Header in the previous article, we will need to use fillMaxWidth() modifier and SpaceBetween arrangement on the Row to push the text column and rating to each side of the card (we’ll get to the implementation of the Rating component later). To divide the space correctly, so that rating takes all the space it needs on the right and text takes the rest on the left, we’ll use a concept you are probably familiar with from LinearLayout - weight. Both Row and Column have their specific scopes (RowScope and ColumnScope) which provide functionality specific to them. Here we’ll use RowScope.weight modifier to force the text column to respect the space of the rating component. Without it, the rating would be pushed by longer text.
Now when the Text can’t use all the space, it wants we need to make sure that it does not just wrap to the next line by setting the maxLines property to one and overflow to TextOverflow.Ellipsis.

Next up there is the Rating component. If we are used to the View system, the first thing we’ll think of is that this will be harder. But when you think about it, it’s just five stars with a few of them with lower alpha depending on the rating, so it’s one conditional. And Compose is perfect for this type of situation. We can just loop five times, calling the Icon component, and when the star position is greater than the rating, we’ll just use the alpha modifier to set it to a lower value. It won’t have the same functionality as the design as there won’t be any half-stars, but doing that would be a little bit harder.

LazyRow

With everything set up, we can finally put it all together. Let’s call LazyRow component and see what we can do with it.
First of all, we can notice that it does not take any list of items, nor it gives us any way of specifying how many items there will be. But if we look closer at the content lambda, it has a LazyListScope receiver similar to the Row and Column having their own scopes. This time however it doesn’t give us access to a bunch of modifiers but rather a bunch of item methods used for adding items to the list. The reason we can’t just pass the items to the LazyRow is that there are several ways to add the items, and there is even an experimental feature of adding sticky headers. So inside the LazyRow we can just call items(houseItems) { HouseCard(it) } to add everything.

Finishing up

Ok, everything is almost done! The last thing missing is the Popular House card.

I believe you won’t have any troubles implementing it after everything we’ve done ;)
I would like to mention that it uses a lot of similar parts as the house item in the row, so we could generalize it a little bit. Both the texts are the same, and the rating is just below them instead of next to them, so I believe it should be straightforward. If you get stuck on anything, you can look at my implementation, but I would highly recommend you try it yourself first.

Summary

This is the screenshot of the final setup. There are a couple of things different from the original design, like icons and fonts used, but I think we did pretty well overall.
More importantly, I think we covered all of the basic features of Compose, and I hope you are a little bit more comfortable using it now :)

If you have any questions about this project, be sure to ask them in the comment section or make an issue in the project repository.
If you have questions about Compose in general, I highly recommend joining the #compose room in the Kotlin Slack channel. I used it while writing this series as well.

Good luck coding.

--

--