The new shiny

On the shift from imperative to declarative UI, and what it might mean for the apps we build today and tomorrow.
Part of
Issue 18 August 2021

Mobile

There’s a shift happening in how mobile apps are built—again. A few years ago, mobile developers moved from languages created in the 1980s and ʼ90s (think Objective-C and Java) toward more modern and expressive programming languages (Swift and Kotlin). Now, we’re seeing a transition from imperative to declarative UI frameworks in the form of SwiftUI and Jetpack Compose. This promises to dramatically increase developer productivity, enabling us to build apps faster with less code.

Declarative UI requires thinking about app development from a new perspective, accepting that it’s okay to rebuild parts of your UI from scratch instead of modifying them. Modern CPUs are fast enough to do so, and can even handle animations. In a declarative framework, changes in state trigger a rebuild of the UI, which removes a whole category of state-related bugs and makes it easier to reason about updates. You describe what the UI should look like for a given state, and the framework figures out how to do it using sensible defaults and context. This greatly reduces the amount of code you need to write and makes it easier to iterate.

By tracing the history of the declarative UI paradigm, we can better understand what SwiftUI and Jetpack Compose have to offer, why mobile engineers are eager to adopt them, and why your development team should consider them, too.

Declarative UI’s beginnings

The trend of declarative UI on mobile began in 2013 with React Native, which started as a Facebook hackathon project. The goal was to improve the developer experience by bringing everything people loved about the web—rapid development, instant reload, platform agnosticism—to mobile. The first major declarative UI framework, React Native offered a way to build cross-platform apps with very little platform-specific code. (“Learn once, write anywhere,” as the mantra goes.)

Today, React Native is a polarizing framework. Some companies are dropping it (like Airbnb), while others are doubling down (like Coinbase). Facebook itself is arguably what holds React Native back from broader adoption: The main Facebook app uses a combination of UI technologies, including React Native, in-house frameworks like Litho and ComponentKit, and newer frameworks to fetch UI from a server. Because Facebook’s development setup is highly customized, external developers can run into problems when using React Native. For example, the company has its own release schedule for updating developer tools, which often results in broken setups when external developers want to adopt the newest APIs.

Google had similar ambitions to bring web development concepts to mobile, although it took a different approach. Flutter started as a fork of Chrome animated by the question, “How fast could we go if we dropped all that backward compatibility from the web?” The key features were a fluid “120 Hz or bust” interface and a fast development cycle with hot reload. This didn’t always work out—especially on iOS, where the infamous “jank” problem (choppy animations on first render) hurt the experience, though a recent release has resolved the issue.

Many consider Flutter’s use of the programming language Dart a drawback since it’s barely used outside of the Flutter ecosystem. Still, Flutter has seen some success, especially on Android, where over 12 percent of apps on the Google Play Store use it. What’s more, it comes with first-class support for web and desktop applications, making it an interesting target for cross-platform development. Google’s open communication with developers (via the @fluttercomm Twitter account, for example), rapidly updated tooling, and comprehensive UI library also make it an attractive choice. 

Platform vendors enter the game

While React Native and Flutter led the way in declarative UI, many development teams resisted adopting tools not created by platform vendors. (Flutter is something of a special case: It’s developed within Google, but it’s not maintained by the Android team and isn’t part of Android’s long-term platform strategy.) Their hesitancy is understandable—it’s risky and difficult to introduce a cross-platform framework into an existing app without knowing if it’ll be supported by the OS long-term.

With this in mind, Apple and Google created their own “first-party” solutions, SwiftUI and Jetpack Compose. Both were announced in 2019 and are now becoming production ready. Apple previewed the third version of SwiftUI in June 2021, and Google released the 1.0 version of Jetpack Compose in early July. (The engineering cultures of the two companies are very different, so don’t read too much into the versioning schemes.)

Using a first-party framework has several notable advantages. For one, mobile operates on a yearly hype cycle pegged to the latest iOS and Android releases. In order to be featured in app stores and garner attention from the press, app teams are eager to adopt the newest features, and fast. SwiftUI and Jetpack Compose offer support for these features from day one. In contrast, it might take months—not to mention a lot more work—before apps powered by Flutter or React Native can take advantage of them. 

Apple, in particular, isn’t making it easy for third-party solutions. As part of iOS 15, the company released seven new Swift-only frameworks. While it’s not impossible to bridge them to Dart, JavaScript, or other languages, doing so is considerably more difficult than calling methods from Objective-C–based frameworks. Plus, some features, like widgets, can only be written in SwiftUI, further sweetening the pot for Swift adopters.

For its part, Google continues to strengthen its investment in Jetpack Compose. In a May 2021 Q&A at Google I/O, Google engineering manager Clara Bayarri hailed it as the “future for UI on Android.”

Bridging the iOS-Android divide

It’s worth noting that SwiftUI and Jetpack Compose are surprisingly similar in both structure and syntax, which enables deeper collaboration across platform teams. For example, compare how the following snippets, which increment a number after tapping a button, are written:

struct CounterView : View {
@State var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") { self.count += 1 }
}
}
}

SwiftUI

@Composable
fun CounterView() {
val count = +state { 0 }
Center {
Column {
Text("Count: ${count.value}")
Button("Increment", onClick = { count.value++ })
}
}
}

Jetpack Compose

Mobile teams that develop natively tend to operate in silos. But with similar languages and the declarative approach to building UI, it’s much easier for teams to use the same architecture. This allows engineers to review each other’s code and identify bugs in how the business logic is implemented. Responsibilities and knowledge can even be shared across platforms. If the iOS person responsible for a certain part of the app is on holiday, their Android counterpart can step in, even if they’ve never written a line of Swift. 

Despite their structural similarities, Jetpack Compose tends to be easier to adopt than SwiftUI. Whereas Google distributes Compose as a library that works on every Android version down to 5.0 (released in 2014), most apps that adopt SwiftUI need to target iOS 14 (released in 2020) or later. (Technically, SwiftUI has been available since iOS 13, but the first version is missing some features; realistically, most apps will require iOS 14.) SwiftUI is also the only declarative UI solution that’s closed source, complicating attempts to port the framework to other platforms. Not being able to see the source code makes debugging much more challenging, and bugs can only be fixed by Apple, which often works with a yearly release cycle.

The future is declarative

The reality of cross-platform development is more of a mixed bag than the “write once, run anywhere” ideal. Some developers would rather quit than write a couple of lines in React Native. And while Flutter is well liked, far more developers have experience with native frameworks, and many are enthusiastic about learning SwiftUI and Jetpack Compose.

With both frameworks reaching maturity, now is an apt time to consider adopting them. Each provides a convenient fallback to the existing platform primitives whenever a declarative API is missing, and the gap between the older imperative UI and the new declarative frameworks is closing with every release. In 2021, Apple addressed missing features such as keyboard control and search and added new ones like Markdown support, while Google finalized the Compose API, added coroutine support, and updated Compose layouts to support its TalkBack tool.

I’m convinced SwiftUI and Jetpack Compose are the future of app development, especially for teams eager to take advantage of the latest and greatest platform features as they roll out. My prediction: Companies that adopt these technologies will have an easier time attracting and retaining engineers, and teams will be able to build more modern, engaging apps, with a tech stack that will stay relevant for the next decade.

About the author

Peter Steinberger is the founder of PSPDFKit, where he researches new technologies and trends in mobile development and helps teams adopt them.

@steipete

Buy the print edition

Visit the Increment Store to purchase print issues.

Store

Continue Reading

Explore Topics

All Issues