Flutter: How much time do you save, and at what cost
Table of Contents
When people talking about the benefits of Flutter, time and development costs would be among the list. The idea that you can use one codebase for multiple platforms and cut development time by half is tempting.
"There are no solutions; there are only trade-offs." â Thomas Sowell
In programming, I always think of everything in terms of trade-offs. There is no way you will get something for free without trade something off.
What do you trade for that reduced development time is what I'm going to explore in this article.
The part that makes Flutter or any cross-platform framework can save time is the ability to share the same codebase for multiple platforms.
What do you pay to get this power? These are some aspects I'm going to discuss in this article.
Gaining
Losing
Gaining
These are things that I think helping reduce the development time in Flutter.
Hot Reloading
Hot Reload is a feature where all the changes are live updated and reflect on the simulator without the need to recompile the whole app, allowing you to view the effects of your changes quickly. This feature can reduce development time, but if you are already using SwiftUI or Android Jetpack Compose, you might already having a similar benefit.
Declarative UI
Flutter uses declarative UI. Declarative UI is also the direction that both iOS (SwiftUI) and Android (Android Jetpack Compose) going after. Declarative UI can save you a lot of boilerplate code. You can achieve the same UI with less code, and this is another key that reduces development time, and again you won't gaining time benefits if you already use that in iOS or Android.
Code Reusability
This is a major benefit of cross-platform technology like Flutter. Flutter has Dart Virtual Machine, which offers a just-in-time compiler (JIT) and ahead-of-time compiler (AOT)[1] for performance-wise production code. This means you use the same codebase and run your code on any platform (that Flutter supported).
I use this code and run it on iOS, Android, and the web in the following example.
void main() {
runApp(MainApp());
}
class MainApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(
title: "Flutter",
),
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar( // 1
title: Text(title),
actions: [
IconButton(
onPressed: () {},
icon: Icon(Icons.add),
),
],
),
body: Center( // 2
child: ElevatedButton(
onPressed: () {},
child: Text("Next"),
),
),
);
}
}
<1> Set navigation bar title and a bar button.
<2> The main content view presents a button at the center of the screen.
You got the same look for all platforms.
As you can see, you can reuse 100% of the code for multiple platforms. But the reusability might vary from project to project. Ideally, you could share 100% of your code, which reduces development time by half, but that isn't always the case, and we will discuss it in the upcoming section.
You can easily support sarunw.com by checking out this sponsor.
AI Grammar: Correct grammar, spell check, check punctuation, and parphrase.
Losing
These are the things that we trade for gaining that time benefit.
Control
Control
Every third-party library you use means you are giving away control of that function to others to manage. You should be mindful when adding another dependency to the project. A framework-level dependency like Flutter is the scariest dependency of all. You can't revert your decision easily once you decided to use it.
Here is how your Xcode project looks like with Flutter.
You won't see any conventional classes and structures. Most of the codes are located elsewhere.
For me, this is the biggest trade-off of all. You build your app upon a dependency layer. There might be a bug or breaking changes that prevent you from continue developing. Strong community and support from one of the biggest tech companies might comfort you about all these risks, but this is surely on the list of your consideration.
Native Experience
As you can see, cross-platform doesn't mean you will get a native experience for each of them. The same code will result in the exact same UI elements. In the example above, we use MaterialApp
, which is the design language of Android, Material design. In other words, your app will look like an Android app.
This might not be the behavior you expected if you have an experience with SwiftUI where the same codebase and component render differently to match the design language for each platform.
If you really want a native experience for iOS and Android, you have to add many if-else
statements everywhere.
Here is a simple example of having a native look and feel for each platform.
bool get isIos =>
foundation.defaultTargetPlatform == foundation.TargetPlatform.iOS;
class MainApp extends StatelessWidget {
Widget build(BuildContext context) {
if (isIos) {
return CupertinoApp( // 1
home: MyHomePage(
title: "iOS",
),
debugShowCheckedModeBanner: false,
);
} else {
return MaterialApp( // 2
home: MyHomePage(
title: "Android",
),
debugShowCheckedModeBanner: false,
);
}
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
Widget build(BuildContext context) {
if (isIos) {
return CupertinoPageScaffold( // 3
navigationBar: CupertinoNavigationBar( // 4
middle: Text(title),
trailing: CupertinoButton( // 5
onPressed: () {},
child: Icon(CupertinoIcons.add),
),
),
child: HomeContent(),
);
} else {
return Scaffold( // 6
appBar: AppBar( // 7
title: Text(title),
actions: [
IconButton( // 8
onPressed: () {},
icon: Icon(Icons.add),
),
],
),
body: HomeContent(),
);
}
}
}
class HomeContent extends StatelessWidget {
const HomeContent({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Hello, Flutter!"),
isIos
? TextButton(
onPressed: () {},
child: Text("Next"),
)
: ElevatedButton(
onPressed: () {},
child: Text("Next"),
)
],
),
);
}
}
<1>, <2> CupertinoApp for iOS and MaterialApp for Android.
<3>, <4>, <5> CupertinoApp requires Cupertino UI elements.
<6>, <7>, <8> MaterialApp required Material UI elements.
You can have a native experience for each platform, but it doesn't work out of the box. It is an entirely manual work with a lot of if-else
here and there.
Native Components
If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
At first, Flutter components confuse me, I think that iOS-like UI elements (CupertinoApp and friends) are back by UIKit, but it isn't. Flutter has its own rendering and runtime engine. If you run a Flutter app in Xcode and open Debug View Hierarchy, this is what you will see.
You won't see UILabel or UIButton anywhere. The only thing you see is a magical FlutterViewController. You can think of a rendering engine with predefine UI components to choose from.
Material Components widgets
Material Components widgets https://flutter.dev/docs/development/ui/widgets/
Cupertino (iOS-style) widgets
Cupertino (iOS-style) widgets (https://flutter.dev/docs/development/ui/widgets/cupertino).
Since Flutter is like a canvas, you can have any UI elements on any platform. In the following example, I use Cupertino widgets on Android and the web.
I put this in the Losing section because if there are changes in native components, you will need to wait for the Flutter to catch up on those changes (which might take forever sometime Modal presentations (iOS 13)). Luckily, the Flutter community is getting bigger and bigger, and you probably find what you need out there, but that's mean more dependencies in your project.
Animation
Since widgets are all rendered on the Flutter rendering engine, you can have a different animation curve and duration from the native components. I can feel noticeable lag from time to time (Maybe my poor Flutter code).
Development Time
Is Flutter cut development time in half?
I think this is one of the most important questions that people want to know because it is tempting to save time and money.
Since Flutter is a cross-platform framework, that means instead of having to do the same thing for two platforms, you do it on one. It is quite obvious that development time must be cut in half, right? Unfortunately, the answer is not that simple.
Ideally, Flutter can share 100% of the codebase, which should save you half of the development time. But as you see in the previous sections, not everything can be share. Here is a brief summary of what might affect the reusability and increase development time.
Platform specific UI
If you want your app to follow the design language for each platform, most of your widgets would contain if-else logic, reducing code reusability for the UI parts.
Platform specific Features
If you want to access platform-specific features, such as access camera and location, that's mean you need to write a bridging between native code and Flutter[2]. You can also use third-party lib if you don't want to write the bridging yourself, but that exposes you to another dependency risk.
You can easily support sarunw.com by checking out this sponsor.
AI Grammar: Correct grammar, spell check, check punctuation, and parphrase.
Conclusion
The development time saving is not as straightforward as most people think. The time saved coming from the code you can share between the platform. If you want anything more native, you have to trade the reusability for it.
It is tempting at the very beginning where you want to release the app at the lowest cost possible. I think Flutter can serve that very well. In my case, I can save half of the development time with my simple API CRUD app.
But the risk lies in the uncertain of the future.
You might hit the Flutter limitation.
You might want a different UI for each platform which takes away the development time advantage you get from Flutter.
Dependencies might stop support.
Development time and cross-platform isn't as easy to justify. It varies based on your specific need. If you know the exact scope of your app, Flutter might be the right choice for you, but if your scope isn't clear and you use Flutter to save cost, the result might not be what you expected.
Writing custom platform-specific code (https://flutter.dev/docs/development/platform-integration/platform-channels), Hosting native Android and iOS views in your Flutter app with Platform Views (https://flutter.dev/docs/development/platform-integration/platform-views) âŠī¸
Read more article about Flutter, Development, or see all available topic
Enjoy the read?
If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.
Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.
If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.
Become a patron Buy me a coffee Tweet ShareSearchable modifier in SwiftUI: A UISearchController and UISearchBar equivalent
SwiftUI finally got native search support in iOS 15. We can add search functionality to any navigation view with the new searchable modifier. Let's explore its capability and limitation.
4 Xcode shortcuts to get back your screen space
Working on your MacBook without an external monitor can be troublesome due to the small screen size. I will show you 4 Xcode shortcuts that might mitigate the situation.