Server Driven UI: How to Start.

Cristian Gomez
The Startup
Published in
12 min readDec 22, 2020

--

Photo by Kelly Sikkema on Unsplash

TL; DR. The servers can drive how an App should look and behave, there will be some authors that will define and create flows for the users. By using ideas from atomic design applied to libs like React, Compose, SwiftUI, Flutter and with a sweet mixture with technologies like Firebase/GraphQL this can be applied to any application. The Client request a page using a Context and the server gives a response where you can map all the widgets this Page contains.

Some background

As a Web developer in Matter Supply co, most of our developments were focused on creating experiences that were easy to manage, update, and change. By creating some apps that enhanced the role of an “Author” using a CMS, and by checking the advantages of UI libs like Compose, SwiftUI, React, and Flutter, I decided to take those ideas and try to merge them in a way of creating applications that could be mobile or web. As a side note, this post does not include so much code, and those concepts can be applied to frontend applications including web/mobile tech stacks, just for fun I will use Kotlin, but you can translate this easily. If you wanted a demonstration of this in a particular use-case, give some claps, and we can talk later.

In my last two years of experience working with web apps, I was able to share some ideas of how the server can tell how the UI should work, on this experience I worked with different CMS that helped me to do the “server-side”, without coding it. The team was able to create multiple UI components, create tests for them, use snapshot testing, create a UI toolkit, let “authors” create pages and connections/actions. So, how to this on a Mobile App? Let’s discover it.

There are many companies that are having some approaches similar to what I touch on in this post, one of them is Airbnb, please review things like Lona Components, Epoxy & Showkase that are moving in this direction.

Some Ingredients, for cooking this recipe.

I was talking about UI components some years ago about Atomic Design as the base for creating an extensible UI, and also when I was working on Pager Inc we implemented a very basic version of this onto the Pager’s Android SDK, mostly using Java and the regular Views.

Atomic design is a nice start for you to start integrating those concepts into your App, but it can be hard for your team to negotiate where to draw the lines for checking what’s an atom or a molecule or others. We are going to give some ideas here about how to handle this.

In Pager We started by creating a module with this structure (or similar):

But… where are the atoms, molecules, and all that stuff… well, over these years I learned that using a different language could improve things a little. By defining the limits of how to separate your views and how to compose them, if this is clear and has a “Natural” language.

The “trick” is to create a way for your team to think fast, they need to compose the UI with flexible re-usable components for using them everywhere, I mean use the “same rules” over all your App. But, what if I need to do some small changes for a single element on a Page/Screen, that’s where a concept that I call Feature based components came up.

This is a concept that I learned from a friend some years ago working on HugeInc from a close friend. Basically, the concept is related to create custom components for your current UI feature, based on a smaller component, you need to use different techniques like a wrap, extend, or compose it to give the context it’s used on your current feature. If you have a InfiniteList component you can extend it to create a FeedList one, which can extend the original view and give all the context for a particular use case. This will create a unique block for your working feature, you are not going to mess up the other views and this will allow you to have clear models.

What’s the relationship with Atomic design, it will be something like this: create base components (Atoms), extend them to provide custom feature components (Molecules), aggregate those to create Modules (Organisms), align them to create templates, and grab the data to fill them for creating the pages (I'm going to explain the last three a little later).

UI Components Module

This can be the most important part of this post, even if you are not going to use Server Driven UI, applying this to your project will help you to create more features more quickly.

If you need some inspiration to go to check those Figma designs and maybe a good exercise is to try to convert one of them into code.

By having those building blocks now you can start thinking about all the possibilities, on my case the possibilities were to have multiple modules/apps having the same standards, not creating everything again.

Think that we can have a library like Material UI but with the rules used by our app, the code will be easier to read, fewer changes are going to be integrated with every pull request, fewer bugs, and inconsistencies.

But starting it’s hard, we need to have a very deep conversation with the team including managers and designers, and maybe for some teams, this could be the hardest part of this process because if your app is too large you will require so much time for this refactor. My recommendation, create a POC to show the benefits of this, starts by creating a UI components module, and then apply this to your most recent feature (extract all your UI to the new module, try to have a re-usable list of components, and extend those into your feature). Once you complete this POC you can try to convince your team to move forward with this approach, by refactoring every feature. This needs to be fast (or at least something you need to do continuously before adding more features) because it touches all your app, and you don’t want to maintain two versions of your app.

Wanted to start now? Start by creating your typography elements like Body, Eyebrow, H1, H2, H3, Bolds, RichTexts also the Buttons (Primary, Secondary, etc) and Form elements. You can have really custom elements like Skeleton components, but your work here it’s to have the major number of components that will let you describe your UI most easily. You can check some approaches like Carbon’s Design System from IBM to get an idea of how to separate your views (I will cover this in a future example, stay in touch).

Feature Components

This is kind of tricky because I see nobody else is talking about this, or something similar. Let’s pretend we have an old-fashioned (a Button) component that's used on our Login feature, and We can use it as it is, the component can be so simple that we can import it and use it without modifications. As usual, a new definition comes up and now your users need to enter an email click on your button, the button will change their content to a loading icon, when the email is validated you change the text to something new to let the user know the password is required now. Maybe not each Button is going to need this new Behavior (and if it's something required for all we need to move all the new code to the base component).

In this case, it could be safer to add this new behavior by extending our current component and allow it to change its content to a loading view. You can reach this by doing many things here, extending the component, composing the component, do a decorator, use an extension function, you can use whatever suits you best, the idea is to create a way in which you can tell the component easily to do something particular for your new use case.

Another most common example is using RecyclerView, those are so generics that it’s son normal to have something like this on our code:

This is normal, but in my opinion is not the best, is not prepared for change, is not prepared to be adaptable, this is my suggestion as a refactor.

As you noticed I used a convention for my code, UI components references use the suffix View, Form, Button, Field or something indicating that it's a view, sometimes you will find variables with the same name as the UI component, and you need to know which is which.

In a lot of cases, you may want to have a solution that does not involves extending another class you have, because you know, inherit multiple times can drive you to have to maintain multiple versions of the same code. The idea behind this is having some base components, then when you need something more specific to your feature you create only this component that handles the way it looks because it’s extending the base, but behaves differently because you add feature specific functions to it.

In the end an Feature component is just a component that extends one or more base UI building blocks to allow their use in a certain and specific situation.

So if Feature components are a way of creating UI elements for a particular context why do we need modules? Because the modules are the elements that add those building blocks and add the extra functionality if they are needed, the same as with Components and Feature Components, this layer could be implemented by a mixture of base blocks and the extended ones. The idea of a module is to create a section that behaves in a particular way, in the case of the Login Screen, the Login Module will be the one in charge of handling the login flow (Not the data flow), and in the case of the Feed List, the module could include a side view that will be shown on tablets with a master/detail pattern. Thinks about that a module is something you put on the screen that has behaviors, animations, but not data flows (Those will be defined in the next section).

The “Data”

This is the part where we are going to talk about how we are going to create the UI based on the server guidelines, first thing is having a request/response system, where we can catch the view related to our current position.

The data that comes from the server is going to describe the pages/templates by giving all the information needed for our App to create the App, sounds confusing… maybe a bit, but the idea is to handle an app that navigates to itself with the arguments provided by the server.

If my app is starting we can ask the server that gives us the home, the welcome, or the login.

Request: {
path: "/home",
context: {
user: null,
language: "en/US",
device: "Galaxy 3 very old",
os: {
name: "Android",
version: "2.1",
},
app: {
version: "1.2.3",
type: "beta",
},
},
}
Response: {
modules:[
{
name: "Login",
params: {
labels: {
username: "Let's begin with your username",
password: "Shhhh nobody needs to see this",
action: "Click me to Login"
},
},
actions: {
loginClick: {
moveTo: null,
},
registerClick: {
moveTo: "/register",
},
forgotClick: {
moveTo: "/forgot",
},
},
},
]
}

Ok … We define what are the next routes for the actions on that module, and the labels, for the labels we can also add the success/error labels or any other needed for this module.

Well, you see that our Request is sending the user, and because it is null our server is telling us that we need to show the Login Screen. What if we send the user? the server will be responding with something different, for making this faster I will only include a base reference on the response.

Request: {
path: "/home",
context: {
user: {
token: "abcdef",
id: "1234567890",
name: "Juancho",
},
language: "en/US",
device: "Galaxy 3 very old",
os: {
name: "Android",
version: "2.1",
},
app: {
version: "1.2.3",
type: "beta",
},
},
}
Response: {
modules:[
{
name: "NavBar",
params: {...}
},
{
name: "FeedList",
params: {...}
},
{
name: "BottomNavigation",
params: {...}
},
]
}

Those requests/responses can also contain some headers for giving more directions to the app’s state, like if you are using an OTP.

Feature Flags + A/B testing

This “architecture” gives the possibility to define different responses given a certain “Context”, in our example the context includes information about the user, the app, and the device, but certainly, it can include anything else that you can need, for example, your access token. And given this, the server can tell the client what to render. Even if you feel more “advanced” you can create multiple implementations or `layouts` for a sweet taste to your experiments.

Using the previous request/response example, we can draw something like this:

Request: {
path: "/home",
context: {
user: {
token: "abcdef",
id: "1234567890",
name: "Juancho",
location: "California",
},
language: "en/US",
device: "Galaxy 3 very old",
os: {
name: "Android",
version: "2.1",
},
app: {
version: "1.2.3",
type: "beta",
},
},
}
Response: {
modules:[
{
name: "NavBar",
layout: "light",
params: {...}
},
{
name: "Promotions",
layout: "full-page",
params: {...}
},

{
name: "FeedList",
params: {...}
},
{
name: "BottomNavigation",
params: {...}
},
]
}

So, as you see we added some information to our context, and the server is giving us more information, and also a new param called a Layout , the idea behind this is to have multiple implementations of the same chunk of data.

Funny story, in Matter Supply Co we finished some app with this approach, the CMS gived us a bad end of the project news. The CMS had a limit on the GraphQL responses, so it was only prepared to give us as much 20 different types, so it was hard to have different types, and we needed so much types. The solution for the long term was to add this fancy layout param, that allowed us to create more efficient responses.

Performance

Surely performance is something to take into consideration here, and two concepts are the ones who are going to gives us a pleasant experience, Cache & PreFetch, we can a very reductionist explanation here by saying that the idea is to remember the responses from the server and getting the information from there before is needed. We can scrap the information from our page response and check the links related, and we can download that information before needed, then when we reach that page we do the same. As the information is cached you don’t need that information again unless the cache expires. So having this in mind you only need one request per page, the request is being cached and prefetched but there is still some performance hit over here, we depend so much on the disk and the parser.

If the view description and the cache are too big the process is going to take a while, maybe this could be improved by using another structure system different from just caching the JSON response, maybe something like Protobuffer will be better, but as soon as I’m writing this I don’t know how to improve this (ideas are welcome).

Conclusion

I have some ideas here that will let you adopt or at least try to imagine how this can work for your app in a near future. This approach will let you create different screens or change your app without sending a new release to your users. There are elements you need to take into consideration on this, like where is the best time to load the information about your pages, how you create cache for them, how you structure especial screens that are very “unique”. Something to have in mind is that creating this kind of application must relay into libraries like epoxy on Android which gives the user to handle different kinds of child elements on a list for creating performant native applications.

I hope in the future I can give you real examples about this, I think my next chapters will include a nodeJS based app for getting the info from different sources and exposing this with a GraphQL API, also an example using NextJS and Android/iOs Native. The time will talk and we will see.

--

--