Photo by Glen Carrie on Unsplash

Composing Web Content

or how to use Jetpack Compose to render web pages

The vast majority of news-related apps have a well defined format, that displays a list of items, and, upon click, the selected news is rendered as web page in a WebView. This approach has neat pros: it’s a fast way to get the data to show, it is a widely adopted solution that works, and any content that already exists for the web can be made available to native mobile users. With the Android WebView component it’s super easy to take any news RSS feed and make a reader: it only requires loading a URL, after all. On the other hand, there are some cons, that are quite important as well. The first one is the lack of personalisation: we are basically stuck with whatever UI, order of elements, and layout adoption that the web page brings. This first point implies the second and the third as well: we might lack a support for dark mode on the website, and the content UI might not be aligned with our app theme.

It is clear, from the introduction, that we want to explore something that can take web content and render it using native technologies. The general idea would be to have a component (let’s call it Parser) that takes in the HTML source of a webpage and splits it into a list of types that can be rendered natively.

In order to render the types, we have two solutions on Android, at the time of writing: we can leverage the old but reliable View system, or we could explore what Jetpack Compose has to offer. As it might be clear from the title, we’ll go with the latter.

Of all the possible solutions using the View system, two really struck me: using a LinearLayout to add all the children as they were identified, or rely on a RecyclerView. After this initial choice, we would need to create XML layouts for all the different child Views, wrap them in a Compound View, create the binding logic and at that point, we would be able to add such components either in aLinearLayout or as elements of RecyclerView. And, of course, all this work has to be done for every new element we want to support.

If we leverage Jetpack Compose, instead, we have several layers less to care for: while we still have to create the UI, Composables are written in Kotlin already, making it super easy to bind the data to the logic. Columns and Rows are first class citizens in the framework, thus making it much easier to align the content as we wish.

As an example, I took an interesting blog post of an important Italian online newspaper, called Il Post. I chose this specific article as it had a few things I wanted to experiment with: images (and GIFs) are shown in different ways and HTML tags, paragraphs can have a subtitle or not, there is an HTML form and it has quite the linear structure, easy to scan.

The Compose version VS the website

We won’t dive deep into the implementation details of this specific blog: while web content usually aligns to some element, there isn’t a strict standard that can always be applied, and minor tweaks to the parsing logic would be needed from a website to another, but the general idea is to create a list of elements that can be rendered by Jetpack Compose: a title, a header, images, text, and everything else we might want to support.

The first step I took was to create some basic models that could represent the part of the blog post I wanted my app to support: for instance, there is a Title, which only contains a String, exactly like a Header. We need to create two elements as we can leverage their type to render them in a different way. Here below, there is the complete list of the types defined:

I decided to use Sealed Classes as I wanted to limit the scope of the components and always be able to understand what has been implemented or not. The use of Sealed Classes is not mandatory, interfaces can do the same, it’s just an implementation detail.

The only data class not extending Component is Article: I preferred to keep the List<Components> wrapped and deal with the specific data type, but it’s not that important.

The next part was to parse the HTML and get the data needed to populate our models. I decided to use JSoup to get the content from the aforementioned post: parsing the DOM is no easy task, but this library is really helpful and by combining selectors and Kotlin, it’s fairly easy to accomplish the task.

In the code here above, we can see that JSOUP takes care of getting the content of a URL for us without the need of any other networking library (line 2): this is quite cool, but it’s worth mentioning that this operation happens on the main thread by default, so it need to be moved to another thread when this code is running on Android.

The parsing itself is really just a simple call to the JSoup API (line 15 and 18) and asks the DOM to return us a specific element with a specific class, like an h1 with the entry-title class, and grab its content. In many cases specifying the class might not be needed, as there might be only one h1 title, but in the example, I preferred to be safer and be as restrictive as possible.

In the blog post we are using for practice, images are stored in a peculiar way: the URL for the image is not inside the src attribute, but rather inside the data-src one, so I used the code below to grab the data:

Now that we have our models filled with the parsed data, we can render them with Jetpack Compose. The idea is to cycle through the Components we declared, and based on the type detected, the logic will create a different Composable to show the data:

After the logic cycles through the Components, it uses their type to define how to render them using a combination of when and smart casts:

As we mentioned earlier in this post, by defining two types for the Title and the Header, it’s super easy to give to each element its style, dimensions and padding.

This post only covers the basics that I implemented to test, but there are some other things related to this subject that can be explored: adding links to paragraphs whenever they are detected, highlighting and selecting text, adding more components. These are just few ideas, but it’s fantastic to see how much easier it just became to create highly dynamic UIs with Jetpack Compose.

The code showed in the post is not deep nor complete (it can be found on GitHub), but it gives us a clear picture of what Jetpack Compose can bring to the table: creating such a project would have been painful with the View system (although doable), but it was pleasantly fast with the new UI framework. At the time of writing, Compose just reached the API stable milestone, and it’s really the right time to explore it.

Huge thanks to Daniele Bonaldo, Ramona Harrison, and Omar Miatello for proofreading this post.

Android developer and GDE for Android