Yandex releases DivKit, an open-source framework for server-driven UI

"templates": {
    "habr_card": {
        "type": "container",
        "background": [
            {
                "type": "solid",
                "color": "#FFF"
            }
        ],
        "paddings": {
            "left": 16,
            "top": 16,
            "right": 16,
            "bottom": 16
        },
        "margins": {
            "bottom": 8
        },
        "items": [
            {
                "type": "container",
                "orientation": "horizontal",
                "items": [
                    {
                        "type": "user_avatar",
                        "$image_url": "avatar"
                    },
                    {
                        "type": "user_name",
                        "$text": "username"
                    },
                    {
                        "type": "time",
                        "$text": "time"
                    }
                ],
                "margins": {
                    "bottom": 8
                }
            },
            {
                "type": "title",
                "$text": "title"
            },
            {
                "type": "container",
                "orientation": "horizontal",
                "items": [
                    {
                        "type": "footer_icon",
                        "image_url": "https://yastatic.net/s3/home/yandex-app/div_demo/diamond.png"
                    },
                    {
                        "type": "footer_text",
                        "text_color": "#7AA600",
                        "$text": "votes"
                    },
                    {
                        "type": "footer_space"
                    },
                    {
                        "type": "footer_icon",
                        "image_url": "https://yastatic.net/s3/home/yandex-app/div_demo/eye.png"
                    },
                    {
                        "type": "footer_text",
                        "$text": "views"
                    },
                    {
                        "type": "footer_space"
                    },
                    {
                        "type": "footer_icon",
                        "image_url": "https://yastatic.net/s3/home/yandex-app/div_demo/flag.png"
                    },
                    {
                        "type": "footer_text",
                        "$text": "favorites"
                    },
                    {
                        "type": "footer_space"
                    },
                    {
                        "type": "footer_icon",
                        "image_url": "https://yastatic.net/s3/home/yandex-app/div_demo/bubble.png"
                    },
                    {
                        "type": "footer_text",
                        "$text": "comments"
                    }
                ]
            }
        ],
        "orientation": "vertical"
    },
    "user_name": {
        "type": "text",
        "font_size": 13,
        "text_color": "#414b50",
        "font_weight": "medium",
        "width": {
            "type": "wrap_content"
        },
        "alignment_vertical": "center",
        "margins": {
            "right": 5
        }
    },
    "user_avatar": {
        "type": "https://habr.com/ru/company/yandex/blog/683886/image",
        "width": {
            "type": "fixed",
            "value": 24
        },
        "height": {
            "type": "fixed",
            "value": 24
        },
        "border": {
            "corner_radius": 3
        },
        "margins": {
            "right": 5
        }
    },
    "time": {
        "type": "text",
        "font_size": 13,
        "text_color": "#777",
        "width": {
            "type": "wrap_content"
        },
        "alignment_vertical": "center",
        "margins": {
            "right": 5
        }
    },
    "title": {
        "type": "text",
        "font_size": 20,
        "text_color": "#333",
        "line_height": 23,
        "font_weight": "medium",
        "margins": {
            "bottom": 20
        }
    },
    "footer_icon": {
        "type": "https://habr.com/ru/company/yandex/blog/683886/image",
        "scale_type": "fit",
        "width": {
            "type": "fixed",
            "value": 24
        },
        "height": {
            "type": "fixed",
            "value": 24
        },
        "border": {
            "corner_radius": 3
        },
        "paddings": {
            "top": 4,
            "bottom": 4,
            "left": 4,
            "right": 4
        }
    },
    "footer_text": {
        "type": "text",
        "font_size": 13,
        "text_color": "#BDCDD6",
        "font_weight": "medium",
        "width": {
            "type": "wrap_content"
        },
        "margins": {
            "left": 4
        },
        "alignment_vertical": "center"
    },
    "footer_space": {
        "type": "separator",
        "width": {
            "type": "match_parent",
            "weight": 1
        },
        "delimiter_style": {
            "color": "#0000"
        }
    }
}

We substitute data in the template:

"card": {
    "log_id": "div2_sample_card",
    "states": [
        {
            "state_id": 0,
            "div": {
                "type": "container",
                "background": [
                    {
                        "type": "solid",
                        "color": "#F0F0F0"
                    }
                ],
                "items": [
                    {
                        "type": "habr_card",
                        "avatar": "https://habrastorage.org/r/w32/getpro/habr/avatars/133/48f/f00/13348ff00887755177016af1d4ea450e.png",
                        "username": "Barseadr",
                        "time": "сегодня в 11:48",
                        "title": "Встречи формата 1-on-1: не противостояние, а слаженное взаимодействие",
                        "votes": "+4",
                        "comments": "10",
                        "views": "200",
                        "favorites": "2"
                    }
                ]
            }
        }
    ]
}

Here you can see that the data takes up a very small amount. Templates help to significantly reduce the layout by highlighting all the repeating fragments. Here’s what the card will look like:

Add a factory for creating views. android:

internal class DivViewFactory(
    private val context: Div2Context,
    private val templatesJson: JSONObject? = null
) {

    private val environment = DivParsingEnvironment(ParsingErrorLogger.ASSERT).apply {
        if (templatesJson != null) parseTemplates(templatesJson)
    }

    fun createView(cardJson: JSONObject): Div2View {
        val divData = DivData(environment, cardJson)
        return Div2View(context).apply {
            setData(divData, DivDataTag(divData.logId))
        }
    }
}

DivParsingEnvironment

– a library of parsed templates, they will be reused when creating cards. method

createView

we create a view from templates and data. The factory constructor mentions

DivContext

– it is similar to the context in Android and is used for the same purposes. In it, you can share common dependencies between views.

How the context is created:

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

Adding data to the view:

setData(divData, DivDataTag(divData.logId)

. DivData is a card DTO model that is built from templates and data.

iOS:

func setCard(_ card: DivData) throws {
  let context = components.makeContext(
    cardId: DivCardID(rawValue: card.logId),
    cachedImageHolders: []
  )
  let block = try card.makeBlock(context: context)
  let view = block.reuse(
    state?.view,
    observer: nil,
    overscrollDelegate: nil,
    renderingDelegate: nil,
    superview: self
  )
  state = State(block: block, view: view)
}

components.makeContext()

– create a context. To do this, you need to pass a unique card ID and image storage within the context so that they do not blink when reused.

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

You can see the application code on Githubthere it is slightly improved: not one card was made, but a tape on DivKit, and there is example integrations for iOS.

By the way, support for variables and functions allows you to write more complex interface blocks using DivKit. For example, you can make a widget for color settings:

What’s next?

More and more teams at Yandex are moving their applications to DivKit, and we are fine-tuning the technology to suit their requirements. In the nearest plans:

  • New layout with “constructs”
  • Flutter
  • Auxiliary components for working with DivKit

DivKit as a Server Driven UI technology is convenient because it does not require much to start. You can translate one screen or even part of it into divs – a card, a button, or another element. At the very beginning, the server is not required: DivKit is easy to use as a framework that facilitates the work, all data is sewn up on the client, and when the server response mechanism appears, start receiving updates.

For experimentation, there sandbox. The DivKit web engine is connected to it, you can also download it from Google Play and connect it to the sandbox demo application (It will appear in the App Store in September). The data will be updated live: the sandbox connects to the demo app via web sockets. There are answers to many questions on the siteif you didn’t find something – ask in the comments or in telegram chat.