Yandex Releases DivKit, an Open Framework for Server-Driven UI

Tayrinn
Yandex
Published in
10 min readAug 26, 2022

Hi there! Yandex just published DivKit to open source. It’s a framework for server-driven UI rendering; you can use this approach to speed up the app development. It allows you to roll out server-sourced updates to different app versions. Also, it can be used for fast UI prototyping, allowing you to write a layout once and then ship it to iOS, Android, and Web platforms.

The framework includes several tools: a client-side for rendering UIs and a DSL for generating a UI description in Kotlin, TypeScript, and Python. The source code is available on GitHub under the Apache 2.0 license.

Currently, we use DivKit in the Yandex app, the Alice voice assistant, Edadeal (a service that tracks discounts in grocery chains), the Market e-commerce platform, the smart TV app, and a few others. In this post, I’ll begin by reminiscing on the framework’s history. After that, we’ll code a modest Medium blog feed viewer, and in the wrap-up, I’ll demonstrate a few interesting integration examples.

A Brief Dive Into History

For the longest time, we in the Yandex app team had been wondering how to roll changes to our users faster. The main page of the Yandex application consists of cards, each of them can be used to solve a user task. For example, a card can show the weather forecast, traffic jams on the route, or road closures in the city. But how can we warn a person if a metro station has closed, a sudden hailstorm has kicked off, or something else has happened that warrants an immediate change both in the data and the UI?

Our first solution was to append the feed with a WebView-based card dedicated to displaying custom messages. But having spent half a year fencing with odd WebView crashes, we decided to look for another solution.

First, we looked at the card feed and saw many repeating patterns: each card has a header with three dots and a uniform-style footer. The most basic card body contains text or text and an image.

This led to the first version of Divs, comprising high-level semantic blocks. The server would send minimal information:

{
“type”: “title”,
“title”: “Hello World!”,
“title_style”: “title_s”
}

We’d taught the client-side what parameters it needed to set for the title_s style, such as size, font weight, line spacing, and letter spacing.

All went well for about a year. Launching a new card and playing around with its appearance and contents would only take a week. This allowed us to create many cards and easily integrate new services into the app’s main feed. It didn’t take much to achieve mockup-to-production pixel perfection: if all the mockup elements were transferred correctly, they looked identical in production.

But design philosophies are ever-changing, just like in development. First, it became apparent that preset text styles aren’t enough. Later, horizontal spacings also started showing cracks. We could change the current block style gradually but chose to run with an entirely different approach and started from the ground up.

Div 2.0

We decided to forego the idea of presetting anything on the client-side and began sending everything from the server instead. Along with this, we provided the opportunity to configure the cards as flexibly as possible without losing much in performance.

Text, image, a linear container for laying out elements, and the grid have become our base elements.

As an example, here’s a list of parameters that can currently be configured for a text element:

font_size
font_family
line_height: Line spacing (interlining) of the text range. The count starts at the font baseline.
max_lines: The maximum number of rows that will not be truncated when going beyond the limits.
min_hidden_lines: The minimum number of truncated rows when going beyond the limits.
auto_ellipsize: Automatic text truncation according to the size of the container.
letter_spacing
font_weight
text_alignment_horizontal
text_alignment_vertical
text_color
focused_text_color: The color of the text when focusing on the element.
text_gradient: Gradient text color.
text: the text itself.
underline: underscore text.
strike: strikethrough text.
ranges: A range of characters within which additional style parameters can be set. Defined by the required fields start and end.
images: Images embedded in the text.
ellipsis: The text cropping marker; displayed when the text size exceeds the limit on the number of lines
selectable: Selecting and copying text.

The transition to the new technology was gradual. We made the decision only to launch new cards on Div2, while the unchanged cards would continue to be received and supported by the client-side. But when a need for a redesign arose, we did a big purge on the backend and transferred all the cards to the new technology, doing away with the old one for good.

During this time, we barely retouched DivKit, as we were happy with its capabilities. The technology entered its “golden age”: all cards could be delivered to app versions up to a year old, and they worked and functioned the same on each version.

After another year, we started feeling cramped with the current features, as we wanted to change everything from the server, not just the cards in the feed.

So, slowly but surely, the main header (which we call “Bender” internally) also moved to Divs. The user profile, bottom sheet, and other blocks also embraced the Divs.

These clean slates were in dire need of fresh features. Hence, the development revved up once again, and we keep improving the technology to this day.

DivKit Users

Edadeal

Each product scenario in the Edadeal mobile app is a separate mini-web application. This approach has advantages, but the screens where it’s essential to maintain native loading speed and smoothness, such as the main screen, are implemented without using web technologies. As a result, the main screen could not develop as quickly as the other sections of the application.

In order to increase the amount of time users spend in the app and to engage people more in the service’s hierarchical UI, the developers have created a new homepage in the form of a dynamic, personalized feed. It opened up the freedoms of experimentation, an independent release cycle, and unified debugging tools on both mobile platforms.

This is the origin story of Mosaic, a standalone service for native screen UI generation. It’s based on DivKit — a tool for backend-driven UI. To complement Mosaic, we built a WYSIWYG editor for the convenience of layout designers and homepage admins. Mosaic has been in production for almost two years and keeps expanding with new features.

Yandex Market

The Market team considered a few options:

  • Building a dedicated engine for cross-platform development. Costly and impractical.
  • Flutter/React Native. Expensive to train and integrate into the current project, unproductive, and the configuration flexibility is limited.
  • KMM. It doesn’t solve the layout problem nor allow dynamically changing the business logic.

DivKit was the most suitable technology that checked all the boxes. We outlined the most complex card for displaying orders in the online viewer as a proof of concept. Our goal was to see how much time it would take to make one from scratch, without outside help, bar the documentation. As a result, a person who had never encountered DivKit before managed to create a layout in little more than an hour.

In terms of integration, everything went smoothly. All it took was:

  • Connecting the library with the pre-provided sample app and documentation as a reference.
  • Writing boilerplates for existing functions, then calling them directly from DivKit.
  • Sending the server-driven layout to both iOS and Android.

Yandex TV

This app is a special case: smart TV firmware gets updates way less often than usual apps, and the cost of error is too high. Server-driven UI allows us to test hypotheses with the desired frequency, roll out new features as soon as they’re out, and only occasionally release new firmware. One of the modules of the current Yandex TV main page is built using DivKit.

Let’s Get to Business!

We’ll write a super-simple Medium blog feed viewer for Android and iOS.

First, we need to create a DivKit configuration to initiate the library. For Android, it looks like this:

An image loader is a must. I’ll be using the standard option bundled in with the framework, but you can use any library you’re comfortable with: Glide, Picasso, and others.

.supportHyphenation(true) — I’m enabling hyphenation by syllables. It must be done explicitly because turning on hyphenation by default can cause performance drops.

.typefaceProvider(YandexSansTypefaceProvider(this)) — a plug-in font. Yandex Sans comes in a separate package and isn’t included in the main module.

.visualErrorsEnabled(true) — enabling error messages in my view. It simplifies debugging: I’ll see a counter in the upper-left corner if there are any errors in my div layout. These visual cues help cut development time. Naturally, I’ll be disabling error messages in the production version.

Here is the iOS version:

DivKitComponents — a front for working with DivKit

updateCardAction — called when DivKit wants to update the view

urlOpener — an external URL opener

Here, we can talk a bit more about error handling in Divs. If the layout is invalid, the wrong blocks are simply ignored and removed from the rendering. What cases are considered invalid? For example, if there’s no "text" field required for a text div, the div won’t render. The container must have at least one child element in "items". This and other rules are described in our JSON Schema.

Time to parse the JSON. Let’s just use preset data: DivKit doesn’t care where the layout comes from.

Android:

iOS:

DivJson — the structure our server response deserializes into.

DivData.resolve() — creates a card mockup based on data from the response.

“What’s divJson.optJSONObject("templates")?” you may ask. The layout consists of data and templates needed to keep the code concise and easy to understand. A template must have the div type or inherit it from another template with this type.

If we need to insert data in certain fields, we must mark them with a $ in the field name. Example: "$text": "card_title", where "card_title" is a template field name.

Here’s how the card templates would look:

Inserting data into the template:

As you see, the data doesn’t take up a lot of space. Templates highlight the repeated parts and help keep the layout code short. Here’s what the resulting card looks like:

Let’s add a view factory. Android:

DivParsingEnvironment — a library of parsed templates that will be used in creating cards. Using the createView method, we create a view from templates and data. In the factory constructor, we mentioned DivContext, which is analogous to the Android context class and used for the same purposes: in this case, for sharing dependencies between views.

Here’s to create context:

val divContext = Div2Context(baseContext = this, configuration = createDivConfiguration())

We need to add data to the view: setData(divData, DivDataTag(divData,logId). DivData is the card’s data transfer object comprised of templates and data.

iOS:

components.makeContext() — creating a context. To do this, we need to pass a card ID unique within the context, as well as an image storage to ensure that the images don’t flicker upon reuse.

block.reuse() will create a new view or reuse an existing one.

You can view the app’s source code on GitHub, although that version is more refined than the one we studied above: it includes not just one card but an entire DivKit-based feed, and there’s an iOS integration example.

Wrapping up, let me show one more example of an interface powered by DivKit. With the help of variables and functions, you can build, for instance, a color picker widget:

What’s Next?

More and more Yandex teams are using DivKit in their applications, and we’re constantly improving the technology to meet their requirements.

Our short-term plans include:

  • A new layout constraints support
  • Support for using DivKit in Flutter apps
  • Additional high-level components which are based on DivKit

DivKit is an excellent choice to start using server-driven UI in your project because it can be easily integrated as a simple view in any part of your app. At the starting point, you don’t need a server integration. You can include all JSON on the client-side to try it in a real-world application.

Also, we’ve made a sandbox for you to experiment with. You can try different samples in the web editor and see the results on the web or in the Android demo app, both of which are available on Google Play. We’ll publish the iOS demo app shortly. The UI in the demo can be updated live: the sandbox connects to the demo app via web sockets. You can use the DivKit website to find a lot of handy samples and documentation, but feel free to ask us anything here in the comments or via the Telegram community chat.

--

--