If you start an app project today, there are some simple decisions, such as the cloud provider or the backend stack you will use. But the biggest challenge is to choose the right frontend app platform: PWA or native? Flutter or one native app per platform? What about React Native?
You are looking for a platform with the less coding effort to give you the best results. While there is no single bullet, knowing the pros and cons of all the available solutions is essential.
Consider Your Options
It’s essential to start by saying that most developers of the apps available in app stores are still developing the front of their apps using the official SDKs from each platform:
- Swift for iOS, iPadOS and macOS
- Kotlin (or Java) for Android
- .NET for Windows
If you decide to go platform-specific (what it’s typically known as native app development), you also have several options today for the user interface of your app:
- On the Apple side, you can choose the classic UI framework, UIKit, or the recent SwiftUI toolkit.
- On Android, you can choose between the classic UI library based on XML files or the recent Jetpack Compose framework.
But using official SDKs means you need to create and maintain two or three separate apps, so that’s why cross-platform solutions are growing fast, helping us generate app experiences for several operating systems using the same source code.
According to the Stack Overflow 2022 Survey, when talking about cross-platform app solutions, the most popular ones are React Native and Flutter, followed by Xamarin and many hybrid web-related technologies such as Electron, or Cordova.
Regarding the web platform, you can ship one app for multiple platforms and don’t need to use any hybrid frameworks mentioned before. Today you can create Progressive Web Apps (PWAs), a design pattern to develop installable offline-capable web apps for every operating system without packaging or using any SDK by default. These PWAs don’t need an app store for distribution; once installed, they look primarily like any other app in the operating system.
In this article, we will focus on the top 6 platforms that we think should be on your decision menu today:
Platform | From | Targetting | Primary Languages |
---|---|---|---|
Android SDK | Android | Kotlin, and Java | |
iOS SDK | Apple | iOS, iPadOS, macOS, watchOS, tvOS | Swift and Objective-C |
Flutter | Android, iOS, iPadOS, Windows, macOS, Linux, Fucshia, PWAs, any browser | Dart | |
React Native | Meta | Android, iOS, iPadOS, Windows, macOS, PWAs, any browser | JavaScript, JSX |
Progressive Web Apps | W3C and browsers | Android, iOS, iPadOS, Windows, macOS, ChromeOS, Linux, Oculus, any browser | HTML,CSS,JavaScript,Web APIs |
Xamarin / .NET MAUI | Microsoft | Android, iOS, iPadOS, watchOS, tvOS, Tizen, Windows | C# and .NET Core |
Consider that more options can also be helpful in specific conditions, such as Cordova, Unity, Capacitor, Qt, and NativeScript.
Note: An special mention to Kotlin Multiplatform Mobile: it’s a framework to write code for apps in Kotlin and use it for Android and iOS applications. However, it’s not a complete app development framework, so we don’t consider it here. It’s useful for different layers of your app. However, the view layer and the final packaging still need to be developed using other platforms, such as SwifUI and Jetpack Compose with Kotlin.
Figure out What Kind of Platform You Need
When choosing, the first thing to understand is that not every project is the same, so you should not make a decision for all your apps. You may want to create
- An end-user app that mostly consumes and renders data from web services.
- An immersive game or a VR/AR experience.
- A corporate app that will be available for end-users.
- An app with deep integration at the OS level, such as creating widgets, background execution, and virtual assistant integration.
You also need to note how you prefer your users to find and use your app. While many developers think of the app store as their first thought, it may not be the best option for many use cases. If your app will offer micro-interactions or be attached to geolocation (think about a QR code that triggers an app), maybe the app store is not your best friend because it will add friction, and you may have a better conversion with a PWA.
Native or Web?
If you think about PWAs, you may also think about the “native or web” dilemma. That question has been in the community for more than a decade. But thinking about it today, that binary view doesn’t fit with reality anymore. For example:
- A PWA can include partially or entirely binary code in WebAssembly, and its user interface in WebGL. Is that Web or native?
- You can use pure JavaScript to create your app with React Native. Is that Web or native?
- A platform-specific app published in app stores can use Cordova, a Web View, or a Trusted Web Activity to render all or part of its contents with a PWA. Is that Web or native?
As you can see, there are a lot of gray areas these days. Instead of thinking about “native or web,” we can separate the possibilities into four groups: Packaging, Compilation, User Interface, and Distribution.
Packaging
In this category, we will separate platforms based on:
- You need to create a platform-specific package as the product, such as an IPA for iOS, an APK/AAB for Android, or an APPX for Windows.
- You don’t need to package the app, as in a web app.
Platform | Packaging |
---|---|
Android SDK | Packages APKs and AABs |
iOS SDK | Packages IPAs / macOS bundles |
Flutter | Packages APKs, AABs, IPAs, APPXs, macOS, and Linux bundles. No package for web |
React Native | Packages APKs, AABs, IPAs, APPXs, and macOS bundles. No package for web |
Progressive Web Apps | No packaging |
Xamarin / .NET MAUI | Packages APKs, AABs, IPAs, APPXs, TPKs, and macOS bundles. |
Compilation
In this category, we can separate the platforms based on their language, their work with source code, and if you will deliver source or compiled code (intermediate or platform machine code).
For example, some platforms will ship source code, others compile it once for a virtual machine, while others make different compilations for different operating systems and architectures.
Platform | Language You Write | You Deploy |
---|---|---|
Android SDK | Java and Kotlin | Dalvik executable code |
iOS SDK | Swift, C++, Objective-C | Binary code |
Flutter | Dart | Binary code (one per platform), and JavaScript source code |
React Native | JavaScript | JavaScript source code |
Progressive Web Apps | JavaScript | JavaScript source code |
Xamarin / .NET MAUI | C# | Intermediate code (Android), Binary code (one per other platform) |
User Interface
There are two groups in this category:
First is how you define and code your user interface.
- In an external declarative file, such as XML, template, or HTML.
- In source code programmatically, such as with JSX or creating UI components in a function.
Then, when designing your user interface, each platform will use one of the following options:
- Use the official UI toolkit in the operating system: if you render a button, it will be the same instance type as the ones you see in the operating system and system apps.
- Use a web UI interface: you have to render your user interface using web technologies: HTML, CSS, SVG, and canvas.
- Use an alternative UI toolkit: in this case, the framework re-designs UI elements in a low-level canvas API. It doesn’t use the default UI toolkit in the operating system.
Platform | Recommended UI toolkit | Definition | Toolkit Type |
---|---|---|---|
Android SDK | Jetpack Compose | Programmatically | Official |
iOS SDK | SwiftUI | Programmatically | Official |
Flutter | Material / Cupertino (Apple-flavored) | Programmatically | Alternative |
React Native | Views mapping to UIKit/Android views | Programmatically | Official |
Progressive Web Apps | HTML | Declarative | Web |
Xamarin / .NET MAUI | XML/Storyboards/XAML | Declarative | Official |
Distribution
Distribution is also one key aspect of deciding on a platform. In this case, one platform can support multi-distribution channels between:
- App stores and catalogs, such as Google Play Store, Apple AppStore, Huawei AppGallery, or Microsoft Store
- Sideloading, such as installing the app from your website, through USB, or by sending the installer package to the user
- Web Browser, such as using the app from a browser using a URL (manually typed, from a QR code or NFC tag, or a link shared to the user). Thanks to the PWA platform, the user can install the app from the browser in the operating system without sideloading or an app store.
Platform | AppStores and Catalogs | Sideloading | Web Browser |
---|---|---|---|
Android SDK | Google Play Store, Amazon AppStore, Huawei AppGallery, Microsoft Store | available | no |
iOS SDK | Apple AppStore | only for enterprise | no |
Flutter | Google Play Store, Apple AppStore, Amazon AppStore, Huawei AppGallery, Microsoft Store | only for enterprise on Apple / available on other platforms | yes |
React Native | Google Play Store, Apple AppStore, Amazon AppStore, Huawei AppGallery, Microsoft Store | only for enterprise on Apple / available on other platforms | yes |
Progressive Web Apps | Google Play Store with TWA, Microsoft Store, AppStore with AppBoundDomains | no need | yes |
Xamarin / .NET MAUI | Google Play Store, Apple AppStore, Amazon AppStore, Huawei AppGallery, Microsoft Store, Tizen Store | only for enterprise on Apple / available on other platforms | yes |
Top App Development Platforms
Now that you know which options are available and their differences let’s discuss every possibility in more detail with samples of each one as the final help you can receive to make a decision for your app.
Flutter
Flutter is an open-source framework created and maintained by Google that will help you develop and compile a cross-platform app for desktop, mobile, and even the Web as a Progressive Web App.
It uses Dart as its source code, and the standard toolchain involves the Flutter CLI, Android Studio, or Visual Studio Code as your IDE and the SDKs for each platform you want to compile. For example, if you’re going to compile for iOS, you will need a macOS computer and Xcode installed. The toolkit includes a fast development process with Hot Reload, so you can see changes in your devices or emulators without recompiling and repackaging your app.
Flutter is heavily based on the concept of Widget. Everything is a Widget. Do you want a Button? It’s a Widget. Do you like it centered on the screen? It’s a Widget. Do you want to capture a gesture on it? It’s a Widget. Widgets are objects of a class that you declare with a simple and nested Dart syntax that takes some time to get used to, but it’s simpler and faster than you think at first sight. Like React’s class components, you can create Stateless or Stateful widgets in Flutter.
Flutter doesn’t use every platform’s UI toolkit. Still, it does a low-level high-performance pixel rendering of UI elements, cloning the Material Design UI typically found on Android or the Apple UI element designs under the Cupertino package. If you want your app to have an iOS design on Apple devices and a Material design on Android and other devices, you will have to abstract your UI widgets with some work.
If you target the Web, it can create a Progressive Web App, designing the UI with HTML and CSS when possible or with a 2d JavaScript-based canvas, the default value. You will deploy the web files as with any other web app.
If you target any other platform, you will get a binary package ready to deploy in app stores or to sideload, if available.
Let’s see what a screen looks like in Dart and Flutter.
class OrderItem extends StatelessWidget {
final ItemInCart item;
final Function onRemove;
const OrderItem({Key? key, required this.item, required this.onRemove})
: super(key: key);
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
flex: 10, // width: 10%
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text("${item.quantity}x"),
)),
Expanded(
flex: 60, // width: 60%
child: Text(
item.product.name,
style: const TextStyle(fontWeight: FontWeight.bold),
)),
Expanded(
flex: 20, // width: 20%
child: Text("\$" +
(item.product.price * item.quantity).toStringAsFixed(2))),
Expanded(
flex: 10, // width: 10%
child: IconButton(
color: Theme.of(context).primaryColor,
onPressed: () {
onRemove(item.product);
},
icon: const Icon(Icons.delete)))
],
),
),
);
}
}
Code language: Dart (dart)
Progressive Web Apps
When it’s time to think on the Web and use standard technologies to create an app, you think about PWAs (Progressive Web Apps). It’s a set of technologies available in modern browsers that let you create a web app and then define metadata and a component known as a Service Worker that will upgrade your app experience with offline support and installation.
By default, users can use your PWA from the browser without installation, but if wanted, they can install it also from a browser. That process works on iOS, Windows, Android, macOS, Linux, ChromeOS, and more. Once the user has installed it, the app experience will look like any other application on that device: an icon in the launcher, start menu or home screen, a standalone window, and some additional OS integration.
A PWA is just a web app, so you can use any stack of technologies you want: from plain vanilla JavaScript to React, Angular, Vue, Unity, or Next.js applications. You can run high-performance native code with WebASM and have access to hardware and OS integration with capabilities APIs.
One of the most significant advantages of PWAs compared with the other solutions using standard technologies and stacks is the simplicity of deploying and updating the app. There is no need for an app store publishing process, and their QA waiting times while updating the app can be done silently without the user’s or device’s intervention.
After you have a web app, you have to add a manifest file that will look like this:
{
"name": "Frontend Masters",
"short_name": "FEM",
"theme_color": "red",
"background_color": "silver",
"start_url": "./",
"id": "fem-1",
"display": "standalone",
"scope": "./",
"icons": [
{
"src": "icon.png",
"type": "image/png",
"sizes": "512x512"
}
]
}
Code language: JSON / JSON with Comments (json)
Finally, you will need to set up a Service Worker that will act as your local web server in one of the possible use cases:
import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
const pageStrategy = new CacheFirst({
// Put all cached files in a cache named 'pages'
cacheName: 'pages',
plugins: [
// Only requests that return with a 200 status are cached
new CacheableResponsePlugin({
statuses: [200],
}),
],
});
Code language: JavaScript (javascript)
Your PWA is not just installable from the browser; if you pass the business rules of app stores, you can also publish a PWA launcher to them while keeping the advantages of being Web and silently updatable.
iOS SDK
Xcode is the IDE available on macOS only that lets you develop and compile native apps for all Apple platforms, including iOS and iPadOS. During the years, Objective-C and Swift were available as languages, while many ways to design the UI were available such as UIKit and Storyboards.
Today, Apple has many operating systems in the market, merging all its platforms into a single framework and way of doing apps: SwiftUI. Using the Swift language and a powerful programmatic simple syntax, you can design your apps in code while still having support from the IDE for partial previews of your components (known as views) and drag and drop.
With SwiftUI, you can have a complete app with navigation and nice transitions in just a glance by creating a couple of view declarations in Swift’s structures. You can compile the app ready for the App Store or sideloading for corporate applications only.
If you are familiar with front-end responsive UI frameworks such as Angular or React, you will find yourself at home with SwiftUI.
A SwiftUI view sample looks like this:
struct OrderItem: View {
var item: (Product, Int)
@EnvironmentObject var cartManager: CartManager
var body: some View {
HStack {
Text("\(item.1)x")
Text(item.0.name)
Spacer()
Text("$ \(Double(item.1)*item.0.price, specifier: "%.2f")")
Image(systemName: "trash")
.font(.title)
.foregroundColor(Color("Secondary"))
.padding()
.onTapGesture {
cartManager.remove(product: item.0)
}
}
}
}
Code language: JavaScript (javascript)
Android SDK
The Android platform has an extensive set of tools and libraries that have been available for over a decade. Still, the team recently released the new generation of UI toolkit for Android: Jetpack Compose.
With the IDE Android Studio and Jetpack Compose, you can create nice-looking apps with less code and a fast development process using the Kotlin language. Kotlin is a language that, for Android apps, compiles into the Dalvik intermediate language that developers commonly created from Java years ago.
JetPack compose lets you create composables: functions that return a piece of user interface with its interaction, similar to what a functional component means in the React ecosystem. These composable functions can be nested in what ends up being your app’s user interface.
Jetpack Compose is trying to replace the tedious classic declarative XML-based UI toolkit on Android that is still available and compatible with this new modern framework.
You code your app’s logic and other layers using Java or Kotlin and the APIs available in the Android SDK and in a set of open source libraries known as Jetpack. From Android Studio, you can package an AAB for Google Play Store or an APK ready for sideloading and some alternative stores such as Microsoft Store for Windows or the Amazon AppStore.
A composable with Kotlin looks like the following code:
@Composable
fun CartItem(it: ItemInCart, onDelete: (Product)->Unit) {
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text("${it.quantity}x")
Text(it.product.name,
modifier = Modifier.width(150.dp)
)
Text("$${(it.quantity*it.product.price).format(2)}",
modifier = Modifier.width(50.dp)
)
Image(
imageVector = Icons.Filled.Delete,
contentDescription = "Delete",
colorFilter = ColorFilter.tint(Primary),
modifier = Modifier.clickable {
onDelete(it.product)
}
)
}
}
Code language: PHP (php)
Xamarin / .NET MAUI
Microsoft has been supporting .NET users in the mobile app for a while now with the Xamarin framework. It is a way to develop iOS, Windows, macOS, and Android apps with C# and .NET.
It needs a version of Visual Studio and the iOS and Android SDKs in Xamarin basic form. While it lets you share business logic in C#, the user interface was done in each platform’s classic UI framework, such as XAML for Windows, XMLs for Android, and Xcode Storyboards for iOS. You don’t share UI code between platforms.
Also, you still need to understand each platform’s specifics and use the classes every SDK needs for an app, such as UIApplication
for iOS or Activity
for Android. Still, instead of using Swift and Kotlin/Java, you use C# for all the platforms.
Because you still need to create two or three apps within the Xamarin project, the team created a new idea known as Xamarin.Forms to design your apps once for all the platforms. That idea evolved in 2022 as a new framework: .NET MAUI (Multiplatform App User Interface).
MAUI may replace Xamarin in the future, and today, both projects are available if you use Visual Studio to create apps. MAUI is more similar to Flutter and React Native, where from one source, you compile apps for several platforms at the same time instead of having to create separate code for each, as in Xamarin.
MAUI abstracts the user interface in a declarative XAML file and then uses different UI frameworks on each target operating system to render that content. The XAML file with data binding looks like the following sample:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamlSamples.SliderBindingsPage"
Title="Slider Bindings Page">
<StackLayout>
<Label Text="ROTATION"
BindingContext="{x:Reference slider}"
Rotation="{Binding Path=Value}"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Slider x:Name="slider"
Maximum="360"
VerticalOptions="Center" />
<Label BindingContext="{x:Reference slider}"
Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Center"
VerticalOptions="Center" />
</StackLayout>
</ContentPage>
Code language: HTML, XML (xml)
React Native
React.js was so popular for the Web that Meta (Facebook, then) created a tool to use the same ideas, patterns, and JSX but for compiling native apps instead of web apps; it’s called React Native.
React Native is the most used cross-platform app development framework, with Flutter about to cross it getting more and more developers every year. With React Native, you use and ship JavaScript code that a native runtime executes and instantiates native UI components to render on the screen. So, it looks like React, but it has no HTML rendering on it.
You use the typical React ecosystem, including components, JSX, hooks, and libraries such as Redux or React Router. Still, the CLI uses the native SDKs to build the final packages for each platform. The official framework compiles for Android and iOS. Still, additional plugins for other platforms, including “React Native for Desktop” and “React Native for the Web,” compile a web app you can use as a Progressive Web App.
When React Native doesn’t offer a JavaScript API for what you need, you can always write your native plugins (for example, using Swift for iOS and Kotlin for Android) that you can use from JavaScript through a bridge. Like React.js, you can use functions or classes to create your components.
A typical React Native set of components with styles may look like this:
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const App = () => (
<SafeAreaView style={styles.container}>
<SectionList
sections={DATA}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => <Item title={item} />}
renderSectionHeader={({ section: { title } }) => (
<Text style={styles.header}>{title}</Text>
)}
/>
</SafeAreaView>
);
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: StatusBar.currentHeight,
marginHorizontal: 16
},
item: {
backgroundColor: "#f9c2ff",
padding: 20,
marginVertical: 8
},
header: {
fontSize: 32,
backgroundColor: "#fff"
},
title: {
fontSize: 24
}
});
Code language: JavaScript (javascript)
We hope you enjoyed Max’s article! Next, check out Max’s mobile development courses:
- iOS App Development with Swift
- Introduction to Kotlin and Android Development
- Cross-Platform Mobile Apps with Flutter
- Build a PWA from Scratch
Also, check out Kadi’s React Native course.
~ Frontend Masters Team