Blog Infos
Author
Published
Topics
,
Published

If you think it is a good idea to use Jetpack Compose (or any other multi-platform UI library like Flutter or React Native) to build an Android and iOS application, it is probably because you’ve never developed or used an iOS application before. That was my case not so long ago and it was definitely a bad idea.

Nice to meet you iOS

For a recent project, I created a mobile application for Android and, for the first time, iOS. My first idea was to wait for the compatibility of Jetpack Compose with the iOS system, but that was also the opportunity for me to learn SwiftUI and I chose this last option.

This project is a real application, not just developed to write this article. It’ll be used as the mobile application for the Devfest Lille, a French tech event in Lille. You can find the entire source code in this GitHub repository.

GitHub – GerardPaligot/conferences4hall: Import Conference Hall event data to create an agenda and…

Now let’s go back to my story. Before I started to write my first line of Swift code, I took my wife’s iPhone to know more about the system. I was surprised to realize that there are a lot of differences between Android and iOS. Where in Android, we have Material and everyone can implement it like they want, on iOS you don’t have any choice. If you want to build an iOS application which respects the platform guidelines, you have only one way.

I started an exploration phase of the iOS system to know more about fundamental design principles and about the user experience on the navigation in order to understand the better approach to interact with components.

Differences between SwiftUI and Jetpack Compose

Compose and SwitfUI are both new declarative UI frameworks. Due to this paradigm, it is easy to develop with one of them if you know the other one. As an Android engineer, I develop a lot with Jetpack Compose and even if it was the first time I used SwiftUI and the language Swift, it was easy to get started. However, there are some key differences.

First difference is that SwiftUI is packaged with the OS where Compose is a library. Even if performance will be better with SwiftUI because it is pre-compiled in the system, I prefer the Compose approach. I know there is a better user adoption of new OS versions in the iOS ecosystem, but there are always two (sometimes three) versions in parallel. If you need a new feature in the framework, you need to wait for your project to configure the appropriate minimum version where you just need to upgrade the library version with Compose.

Second difference is that SwiftUI uses protocol implementation where Compose uses a compiler plugin and the composition. The composition is an elegant way to delegate a responsibility to another class but I’m forced to admit that the SwiftUI approach doesn’t require adding a Modifier equivalent to all my components.

Thirdly, maybe the worst idea but also the best one, is the strong coupling of SwiftUI with the system. Due to this decision, it is hard to write an application which doesn’t respect the design of iOS. With Compose, you can easily create a component which respects Material specifications but you need much more code to build an application with a beautiful UI.

This last point is a design decision from Google.

The reasons are good and understandable. With this design decision, Jetpack Compose is now multi-platform and can be used for desktop, web and soon iOS. But this decision is at the expense of a better experience for developers who build applications only for the Android platform.

Where is SwiftUI better than Compose?

Of course, this section won’t be exhaustive. I don’t know all things inside SwiftUI but I’ve a short list where I think that SwiftUI experience is insanely better compared to Jetpack Compose.

Previews

Previews are available in both technologies but with Compose and Android Studio, it is often broken (sometimes, you don’t know why) and you are quickly limited in possibilities. You can just preview a composable function which emits UI without any request call or a dependency to a ViewModel.

In SwiftUI and Xcode, it is much more permissive. You can interact with your preview and load data asynchronously. You don’t need to think about your composable architecture to get a preview. However, my application is pretty small and I’m curious to see SwiftUI previews with a large code base.

SwiftUI vs Jetpack Compose Previews

Toolbar navigation

Maybe the most critical component inside a mobile application because it is the main component for the navigation and it is where you display the title of your current screen or the name of the application. You can’t create a mobile application without this component but fortunately, declaring this component is super easy for both platforms.

However, where you need to declare a dedicated component with Compose, it is a decorator with SwiftUI. You can call navigationTitle (and all variants) anywhere in your components hierarchy. That could be dangerous for complex navigation but Compose is worse. It is quickly hard to customize a toolbar shared between multiple screens if you are also using a bottom navigation.

// AppView.swift - Declare the tabs navigation.
TabView {
AgendaVM(agendaRepository: agendaRepository)
.tabItem {
Label("Agenda", systemImage: "calendar")
}
NetworkingVM(userRepository: userRepository)
.tabItem {
Label("Networking", systemImage: "person.2")
}
EventVM(agendaRepository: agendaRepository)
.tabItem {
Label("Event", systemImage: "ticket")
}
}
// Agenda.swift - Declare my toolbar inside my screen.
NavigationView {
Group {
// You screen here
}
// Toolbar navigation calls inside a NavigationView
.navigationTitle(Text("Agenda"))
.navigationBarTitleDisplayMode(.inline)
}
view raw Agenda.swift hosted with ❤ by GitHub

SwiftUI implementation to declare a tab navigation and a toolbar.

@Composable
fun Home(
agendaRepository: AgendaRepository,
userRepository: UserRepository,
modifier: Modifier = Modifier,
startDestination: Screen = Screen.Agenda,
navController: NavHostController = rememberNavController(),
) {
// Check the back stack in the nav controll to know which screen is running.
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
val screen = currentDestination?.route?.getScreen() ?: startDestination
Scaffold(
modifier = modifier,
topBar = {
// Display the topbar expected
TopAppBar(title = screen.title, actions = screen.actions)
},
bottomBar = {
BottomAppBar(
selected = screen,
onClick = {
// Handle the navigation between screens.
navController.navigate(it.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
},
) {
NavHost(navController, startDestination = startDestination.route, modifier = Modifier.padding(it)) {
composable(Screen.Agenda.route) {
AgendaVM(agendaRepository = agendaRepository)
}
composable(Screen.Networking.route) {
NetworkingVM(userRepository = userRepository)
}
composable(Screen.Event.route) {
EventVM(agendaRepository = agendaRepository)
}
}
}
}
view raw Home.kt hosted with ❤ by GitHub

Jetpack Compose implementation to declare a bottom navigation and a toolbar.

Job Offers

Job Offers


    API Engineer

    American Express
    Phoenix, USA
    • Full Time
    apply now

    Senior Android Engineer

    Peloton Interactive
    New York
    • Full Time
    apply now

    Android Entwicklerin Echtzeitkommunikation

    sipgate GmbH
    Germany
    • Full Time
    apply now
Load more listings

OUR VIDEO RECOMMENDATION

Jobs

Where in SwiftUI, you declare what you want to display and let the framework handle the navigation for you. In Compose, you need much more code to handle the navigation and define a suitable architecture.

Bottom sheet

For now, the bottom sheet with Compose is a nightmare. You need at least the documentation to create a bottom sheet, sometimes blog posts to know how to handle complex navigation, because you are forced to use a BottomSheetScaffoldor a ModalBottomSheetLayout which aren’t trivial components.

The equivalent in SwiftUI is just a decorative call on a component which takes a boolean value to know if it should show or not the bottom sheet. Super simple, don’t need to check any documentation, only one call on a screen component. I didn’t had to check the documentation to create this user experience and it worked directly without any issue.

struct Networking: View {
@ObservedObject var viewModel: NetworkingViewModel
// state to displau a sheet
@State private var isPresentingScanner = false
var body: some View {
let uiState = viewModel.uiState
NavigationView {
Group {
Networking(networkingUi: uiState.networkingUi)
}
// use the state to display the sheet or not
.sheet(isPresented: $isPresentingScanner) {
CodeScannerView(codeTypes: [.qr]) { response in
if case let .success(result) = response {
// close the sheet
isPresentingScanner = false
}
}
}
}
}
}

SwiftUI implementation to display a sheet.

Form

In my application, I created a form where the user can insert a first name, last name, email address and a company. With that, I generate a QRcode which respects the vCard standard to share personal data between participants at the event.

With Compose, I can create filled or outlined text fields to display a form but if I need to combine visually text inputs or separate them with sections, I need to make it manually. That was my first approach with SwiftUI but after a little research, Form and Section help a lot to build a form with the correct look and feel and user experience.

var body: some View {
VStack {
// Generate form UI
Form {
// Generate section UI
Section {
TextField("Your email address*", text: $email)
.keyboardType(.emailAddress)
TextField("Your first name*", text: $firstName)
TextField("Your last name*", text: $lastName)
TextField("Your company", text: $company)
}
if let uiImage = qrCode {
Section {
Image(uiImage: uiImage)
.resizable()
.frame(width: 250, height: 250, alignment: .center)
}
}
Section {
Button("Create QRCode") {
onValidation(email, firstName, lastName, company)
}
}
}
}
}

SwiftUI implementation to build a form

@Composable
fun ProfileInput(
profile: UserProfileUi,
modifier: Modifier = Modifier
) {
Scaffold(modifier = modifier) {
// Require to go at the next input from the keyboard.
val focusManager = LocalFocusManager.current
LazyColumn {
if (profile.qrCode != null) {
item {
Image(bitmap = profile.qrCode!!.asImageBitmap())
}
}
item {
TextField(
value = profile.email,
label = { Text("Your email address*") },
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Next,
keyboardType = KeyboardType.Email
)
)
}
item {
TextField(
value = profile.firstName,
label = { Text("Your first name*") },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
)
}
item {
TextField(
value = profile.lastName,
label = { Text("Your last name*") },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
)
}
item {
TextField(
value = profile.company,
label = { Text("Your company") },
keyboardActions = KeyboardActions(onDone = {
focusManager.clearFocus()
}),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done)
)
}
item {
Button(
onClick = {
focusManager.clearFocus()
}
) {
Text("Generate your QR code")
}
}
}
}
}
view raw ProfileInput.kt hosted with ❤ by GitHub

Jetpack Compose implementation to build a form.

But SwiftUI isn’t perfect…

Yes, SwiftUI is a good library but it’s not perfect. In my opinion, there are two big pain points when you develop with this UI framework.

Navigation

In SwiftUI, you need to declare a NavigationView and use NavigationLink inside your components to handle the navigation. It works well. Back stack and transitions are handled automatically but I got into the habit of moving events as high as possible in the components hierarchy to handle the navigation at the top level.

This approach is better because you can build components with a fixed contract without any dependency between screens. Your component doesn’t know that if you click on a button, it’ll start a new screen. Due to this, you can easily share your components in your code base or across applications.

It is also easier to initialize a ViewModel for your screen components if you can pass it from the top level of your application rather than sharing repositories through all components until it reaches a NavigationLink which will use it to create a new screen with its own ViewModel.

Documentation

When you come from Compose and the awesome documentation written by Google teams, the Apple documentation is really poor. You can find a short documentation inside Xcode when you click on a component but it is really limited and you often need more information when you use a component for the first time.

Moreover, SwiftUI isn’t open source and I don’t understand the reason for that. I check the Compose source code all the time to know how it is built inside the library to do the same, or similar, or for inspiration. When you want to check the SwiftUI component source code, you can find only a header file without any additional information.

Fortunately, there is the hacking with Swift website to help you when you are blocked but it should be done by Apple teams to avoid obsolete documentation in the future.

Conclusion

Is SwiftUI better than Compose? In my opinion, yes and no.

One thing that is certain is that SwiftUI is easier to use than Compose because Apple teams build the framework to respect iOS platform only without the perspective to use it with other platforms.

If you think it is a good idea to use Compose for both Android and iOS, I’ll probably disagree with you. It is important to respect UI/UX guidelines of the platform and Compose can’t build an application like SwiftUI does.

But design decisions made by Google teams aren’t bad either. Even if it can require more code than SwiftUI to create an interface, it is less than the XML approach and like the father of Compose (Jim Sproch) said, “it leads to better code quality and a more vibrant ecosystem”.

Thanks for reading this blog post. If you like it, please clap this article and follow me on Twitter, GitHub and Medium to be notified about my next blog posts!

Thanks to my reviewers Mehdi Slimani, Maxime Maraval, Djavan Bertrand, Fanny Demey and Florent Lotthé.

This article was originally published on proandroiddev.com on March 21, 2022

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
blog
Yes! You heard it right. We’ll try to understand the complete OTP (one time…
READ MORE

Leave a Reply

Your email address will not be published.

Fill out this field
Fill out this field
Please enter a valid email address.

Menu