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.
Why Jetpack Compose?
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 a
LinearLayout 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.
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.
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.
data class not extending
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.