"004" }, "journeyKeyData": { "campaignCreationPhase": "Teach", "purchaseJourneyPhase": "Awareness", "topicCategory": "Organizer", "topicSubCategory": "Event Team", "topicSubSubCategory": "Null" }, "uploadedData": [{ "dataCategoryLabel": "name of organization", "dataStrings": [{ "string": "UBM" }] }, { "dataCategoryLabel": "our mission", "dataStrings": [{ "string": "We Serve the Needs of B2B Buyers" }] }, { "dataCategoryLabel": "our history", "dataStrings": [{ "string": "UBM was fonded in 1948", "string2": "It is was purchased by Informa in 2017", "string3": "We Serve the Needs of B2B Buyers" }] }, { Backend Client All the form and behavioral structures of the layout are defined by the client "What and how" to draw is the client's responsibility
cycle: The lengthy review and approval processes for releases and publication can cause significant delays in delivering critical updates. • Slow user adoption: Since users must manually download and install updates, the adoption of new features and bug fixes is slower. Forced updates can negatively impact the user experience. • Slow feature experimentation: Due to the slow update cycle, it becomes challenging for teams to quickly experiment with and iterate on specific features they want to test. • Slow feedback loop: The slow update cycle and adoption rate make it difficult to gather user feedback and quickly implement changes in response.
app developers need to acquire new knowledge and skills specific to web apps, involving new languages and frameworks. This shift requires a significant learning curve comparable to a job transition. • Performance Degradation: Hybrid apps rely on web browser engines like WebView Chromium) for rendering, which generally doesn't offer the same level of performance as native rendering systems. • Internet Dependency: While some Progressive Web Apps PWAs support offline caching, most hybrid apps heavily depend on an active internet connection, introducing limitations based on network availability. • Limited Access to Device Features: Hybrid apps often face restrictions in accessing hardware features like GPS, camera, and NFC with the same precision as native apps. Although some features are available in PWAs, they remain limited, and complex protocols like JavaScript Interface are required for integration with WebView.
the layout is defined on the server The server is responsible for "what" to draw, and the client is responsible for "how" to draw Client @Composable fun TextUi() { .. } @Composable fun ImageUi() { .. } @Composable fun ListUi() { .. }
Changes to features (layouts) can be quickly deployed without needing app updates, allowing faster feedback loops and enabling rapid experimentation with new functionalities. • Consistent UI: By establishing a stable component design system, Server-Driven UI can ensure consistent user interfaces and behavior across different app versions as long as the core specifications are maintained. • Native Performance: Server-Driven UI retains flexibility while allowing components to be rendered with native performance, outperforming web and hybrid apps in terms of speed and responsiveness. • Reduced Workload for Mobile Developers: Product managers and designers can define the layout, and the backend takes responsibility for deciding "what to do," allowing mobile developers to focus on developing individual components.
knowledge can quickly setup a server and easily operate it through the dashboard UI. • JSON response: You can generate a response by directly importing/exporting a JSON file. Firebase Realtime Database
knowledge can quickly setup a server and easily operate it through the dashboard UI. • JSON response: You can generate a response by directly importing/exporting a JSON file. • Real-time: A client SDK is provided that allows you to observe database response changes in real-time, allowing you to reflect and visually confirm changes in values in real-time. Firebase Realtime Database
SDK is written entirely in Java code and still provides a callback-based API. • No custom serialization: Neither the official SDK nor the community libraries provide custom serialization options for snapshot data. Therefore, it is difficult to support nullability and handle fallbacks flexibly. You must define a default value. Firebase Realtime Database val listener = object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val value = snapshot.child("post") // .. } override fun onCancelled(error: DatabaseError) { // .. } } @IgnoreExtraProperties data class Post( var uid: String? = "", var author: String? = "", var title: String? = "", ) { @Exclude fun toMap(): Map<String, Any?> { return mapOf( "uid" to uid, "author" to author, "title" to title, ..
TextUi( val text: String, val size: Int, val fontWeight: String ) : UiComponent @Serializable data class ImageUi( val url: String, val size: DpSizeUi = DpSizeUi(0, 0), val scaleType: String, val contentDescription: String = "", ) : UiComponent @Serializable data class DpSizeUi( val width: Int, val height: Int )
TextUi( val text: String, val size: Int, val fontWeight: String ) : UiComponent @Serializable data class ImageUi( val url: String, val size: DpSizeUi = DpSizeUi(0, 0), val scaleType: String, val contentDescription: String = "", ) : UiComponent @Serializable data class DpSizeUi( val width: Int, val height: Int ) @Serializable data class ListUi( val layout: String, val itemSize: DpSizeUi, val items: List<ImageUi>, ) : UiComponent fun String.toLayoutType(): LayoutType { return if (this == "grid") LayoutType.GRID else if (this == "column") LayoutType.COLUMN else LayoutType.ROW } enum class LayoutType(val value: String) { GRID("grid"), COLUMN("column"), ROW("row") } Unifying component types with the UiComponent interface
data class TimelineCenterUi( val title: TextUi, val list: ListUi ): UiComponent @Serializable data class TimelineBottomUi( val title: TextUi, val list: ListUi ): UiComponent Component Design Systems @Serializable data class TimelineUi( val top: TimelineTopUi, val center: TimelineCenterUi, val bottom: TimelineBottomUi ) : UiComponent Composes a series of responses with identical UiComponents from the top to the bottom level objects.
val top: TimelineTopUi, val center: TimelineCenterUi, val bottom: TimelineBottomUi ) : UiComponent val timelineUi: StateFlow<List<UiComponent>> = repository.fetchTimelineUi() .flatMapLatest { result -> flowOf(result.getOrNull()?.buildUiComponentList()) } .filterNotNull() .stateIn() @Serializable data class TimelineTopUi( val banner: ImageUi (UiComponent) ): UiComponent @Serializable data class TimelineCenterUi( val title: TextUi, (UiComponent) val list: ListUi (UiComponent) ): UiComponent @Serializable data class TimelineBottomUi( val title: TextUi, (UiComponent) val list: ListUi (UiComponent) ): UiComponent UiComponent.buildUiComponentList()
0, "height": 250 }, "scaleType": "crop", "handler": { "type": "click", "actions": { "navigate": "to" } } } } Handling actions for navigation, deep links, etc. Define actions for components such as click, touch, etc.
actions: Map<String, String> ) enum class HandlerType(val value: String) { CLICK("click") } enum class HandlerAction(val value: String) { NAVIGATION("navigation") } enum class NavigationHandler(val value: String) { TO("to") } @Serializable data class ImageUi( val url: String, val handler: Handler? = null ) : UiComponent • Since action handlers may not exist depending on the component, it is optional. • It is also a good idea to define them in a top-level abstraction interface such as UiComponent.
specifications change significantly? • What if different designs need to be reflected for each app version? • What if multiple types of components are needed for flexible processing of events and rewards?
} } } @Serializable data class TimelineUi( val version: Int, val top: TimelineTopUi, val center: TimelineCenterUi, val bottom: TimelineBottomUi ) : UiComponent
} } } @Serializable data class TimelineUi( val version: Int, val top: TimelineTopUi, val center: TimelineCenterUi, val bottom: TimelineBottomUi ) : UiComponent enum class UiVersion(val value: Int) { VERSION_1_0(1), VERSION_2_0(2); companion object { fun toUiVersion(value: Int): UiVersion { return when (value) { VERSION_1_0.value -> VERSION_1_0 VERSION_2_0.value -> VERSION_2_0 else -> throw RuntimeException("undefined version!") } } } } Setting the versioning scope of the design system Depending on the situation, you can process versions by screen or by component.
= UiVersion.VERSION_1_0, navigator: (UiComponent) -> Unit = {} ) { when (this) { is TextUi -> ConsumeTextUi( textUi = this, modifier = modifier, version = version ) .. else -> ConsumeDefaultUi( uiComponent = this, version = version ) } } Appropriate fallback handling is required when component matching fails for various reasons, such as serialization failure and data corruption.
to prevent incorrect version information from being passed down due to mistakes by PMs or backend developers. enum class UiVersion(val value: Int) { VERSION_1_0(1), VERSION_2_0(2); companion object { fun toUiVersion(value: Int): UiVersion { return when (value) { VERSION_1_0.value -> VERSION_1_0 VERSION_2_0.value -> VERSION_2_0 else -> throw RuntimeException("undefined version!") or else -> VERSION_1_0 } } } } fun String.toLayoutType(): LayoutType { return if (this == "grid") LayoutType.GRID else if (this == "column") LayoutType.COLUMN else LayoutType.ROW }
New features or layouts can be modified and deployed without needing app updates or going through the review process, leading to faster feedback loops and quicker iterations. This benefits both product managers and mobile developers by reducing repetitive work. • Consistent UI: By establishing a stable component design system, it ensures a consistent UI and behavior across various app versions, as long as the core specifications remain the same. • Native Performance: While offering the flexibility of web apps, Server-Driven UI maintains native-level performance for rendering components, which is superior to typical web-based solutions. • Reduced Burden on Mobile Developers: Developers can focus on "how to present" while the backend dictates "what to present," simplifying the overall development process and reducing workload.
the client has to fetch layout information from the backend, rendering and displaying components may take longer compared to data-driven UI, potentially impacting user experience. • Higher Complexity and Costs: The entire team must clearly define roles and responsibilities for rendering layout data and managing component versions. Without this, the backend team may bear an extra burden, or overall communication costs between teams could rise. • Fallback Handling: Since layout data is generated across multiple teams (PMs, designers, and backend), there’s a risk of data issues. Proper fallback mechanisms must be in place to handle any defects in the layout data.
Home screen: When applied to screens that are first exposed to the user when entering the app, such as the home (feed, timeline) screen, and that require frequent changes, it can be very effective. • Screens with high session time: When applied to screens where the user stays in the app for the longest session time, such as live broadcasts and entertainment, it can be more effective.