An iOS developer's first impressions of Flutter: Part 1
A little about myself
I've been working in mobile app development since 2010, initially with Adobe AIR and PhoneGap, but for nearly ten years now I have mainly focused on native iOS development - Xcode, Objective-C, Swift and more recently of course SwiftUI.
Having just spent a lot of time converting an iOS app to Android, I can really appreciate the value of a cross-platform tool with a single codebase. It's time to check out the state of play in cross-platform development!
Cross-platform app development - a quick catch-up
Ever since iPhones and Android phones have been around, there have been tools to facilitate development on both mobile platforms with just one codebase. Early on, the main contenders were Unity, Adobe AIR and PhoneGap. PhoneGap was made open-source in 2011 and rebranded Cordova, and since then several other cross-platform tools have been built on top of it, such as the Ionic framework, released in 2013.
One drawback of these tools was that native components were not easily available - apps built in these tools often had their own custom look, using HTML5 components, or were often more used in game development or apps with their own UI design. This all changed when native cross-platform development became possible. Xamarin, developed by Microsoft in 2014, React Native, developed by Facebook (now Meta) in 2015, and Flutter, developed by Google in 2017, allowed cross-platform apps to look and behave like native apps.
Until recently, React Native easily lead the field of cross-platform tools, with 42% of developers choosing it in 2019, while other tools only reached 30% or fewer (link). However, since then Flutter has experienced quite a rise in popularity. Flutter was used by 42% of developers in 2021. Meanwhile React Native dropped a few points to 38%. (link)
In StackOverflow's 2021 developer survey, Flutter was loved by 68% of developers, React Native 58%, and Cordova just 31%. With approximately one third of mobile app developers now using cross-platform tools (link), Flutter definitely looks worth checking out!
Flutter - let's check it out
In this article I'm going to take a look at Flutter from the perspective of a native iOS developer.
For those interested in following along and checking Flutter out for yourself, you'll find good guides at flutter.dev. As is usually the case with new software, let's start with installation.
Flutter - installation
The first surprise with Flutter was the installation process, which to be honest, was a little laborious. There is of course an installer which contains the Flutter SDK, but there is also a series of steps that have to be followed to be ready to go. To run apps on Android you'll need to install Android Studio, and add "virtual devices" to be able to run the app on the Android emulator. To run apps on iOS you'll also need to ensure that the latest versions of Xcode and CocoaPods are all set up and ready to go too.
You'll also need to do some tinkering in Terminal to prepare your machine, such as agreeing to iOS and Android licenses. You'll need to add Flutter to your PATH to be able to run Flutter commands in Terminal. The Flutter tool is quite useful in the installation process - just run flutter doctor
and see if you have missed any steps or dependencies.
From the point of view of someone who avoids Terminal if possible - give me a graphical UI any day - all of this messing about in the Terminal was a little unexpected, but not too difficult.
The next surprise was that Flutter doesn't have its own IDE - it allows you to use your own preferred IDE (although Xcode isn't in the list) - Android Studio, IntelliJ, VS Code, or Emacs. It just takes installing a Flutter plugin in your IDE to able to run your app from within the IDE, and also have access to code completion, syntax highlighting, debugging, etc. As you have Android Studio installed anyway to run apps on Android, unless you have a strong preference otherwise, it probably makes most sense to go ahead and work in Android Studio.
Flutter - first impressions
Once you install the Flutter plugin, working with Flutter in Android Studio is surprisingly well integrated into the IDE. Creating a new Flutter project is as straightforward as creating a native Android project. You can choose which platforms you would like to target - iOS, Android, or you can even target web - or the desktop, whether that be MacOS, Windows or Linux. Flutter generates folders for each target which are then used to build your app. Be sure to give your project a name with all lower case, separating words with underscores, Flutter project names do not like camel case!
The most important folder name to be aware of is lib - this is where your source files live, basically where you edit your application. To begin with, this folder just holds the file main.dart, which is where your app begins execution.
My first confusion with Flutter was where to find this source file in the project window on the left, or even where to find the lib folder. After some digging, I found that after creating the Flutter app, the project window by default contained the Android project structure, which only shows files and groups of files relevant to the Android project. To find all files related to your project, you'll need to be sure you have Project selected in the dropdown at the top of the project window. You should now see the lib folder, where you'll find your main.dart source file.
Once you've created your first Flutter project, the first thing you might notice is that you're in a dart file - what is a dart file you may ask? Well, Dart is the language you will be using to develop in Flutter. It is a relatively new language, developed by Google and released in just 2013.
Similar to how Swift uses the @main attribute to identify the main entry point for your app, all Dart programs use the main method (by convention located in main.dart) as the main entry point for execution. In Flutter, this main method will run the app itself.
After some exploration of the code, it becomes clear that similar to working in SwiftUI, we are building the interface of our app right here in code. In Swift/UIKit we can use the storyboard, in SwiftUI we have Xcode previews - the question is, is there a way in Flutter to see the interface visually while we build it in the IDE? The answer is - not really, at least there doesn't seem to be out of the box in Android Studio - but what Flutter does offer isn't a bad substitute, and it’s called hot reload.
Whether you're running your app on the iOS simulator, Android emulator, or on your device, you can see your interface running live as you work. After saving any changes to your code, your app running on your device/emulator/simulator should automatically reload and any changes you have made will be reflected. Not as convenient perhaps as modifying your interface directly in a visual editor, but a fancy solution nonetheless.
Outside of the Android Studio toolkit, there are also other approaches to visual editing in Flutter. For example, an ‘experimental’ plugin for VS Code called Flutter Preview basically gives you the equivalent of Xcode previews. You can also find a fancy web-based app called Flutter Studio where you can edit a Flutter app using a visual interface.
Dart vs Swift
The syntax of Dart is probably not going to be too unfamiliar to you if you're familiar with other modern languages such as Swift. Just like Swift, variables are declared with var, variables are type safe and use type inference to avoid the need for explicit typing.
Dart distinguishes between constants where the value is known at compile time (const) and those that are only known at run-time (final).
Function declaration syntax is a little different to Swift, but again, not so unfamiliar if you’ve ever come across C or Java function syntax.
While Swift might declare a function this way:
func submit(name: String, surname:String, age:Int) {
In Dart it would look like this:
void submit(String name, String surname, int age) {
Rather than init, class constructors in Dart have the same name as the class, and can automatically set instance variables by specifying them in the constructor parameters. For example:
class Person {
final String name;
final String surname;
final int age;
Person(this.name, this.surname, this.age);
}
In Swift you would be used to String interpolation with syntax such as:
print("Your name is \(name)");
In Dart however, the syntax is closer to JavaScript, using a $ sign:
print('Your name is $name')
So much of Dart, though, is almost spookily familiar, coming from a Swift background. Dart has classes, inheritance, enums, generics, closures, control flow statements, multiline strings with triple quotes, and since March 2021, Dart even has optionals, or null-safety, all with very similar if not identical syntax to Swift.
There are some notable differences however - for example Dart core libraries don't support structs, or tuples. On the other hand Dart does have an intriguing concept called a mixin, which allows you to reuse the same code in different classes.
Flutter vs SwiftUI
Another feature of the Dart source code you encounter in a Flutter project is that it uses a declarative rather than imperative syntax. You would be familiar with declarative syntax if you have used SwiftUI. The difference is often described as: imperative programming describes how the program does something while declarative programming describes what the program does.
It is probably easier to start getting your head around the difference with a bit of pseudo code. Here is how we might configure a view in the imperative style:
titleview.setColor(green)
titleview.removeAllChildren()
Text text = new Text('Demo')
titleview.add(text)
And then here is how we might return this configured view in the declarative style.
return TitleView (
color: green,
child: Text('Demo')
)
If you have worked with both UIKit and SwiftUI, you will have experienced how much more succinct your code can be with the declarative style. It can also be easier to read, less buggy and easier to maintain.
There are of course differences to how Flutter and SwiftUI work. Everything you can see in SwiftUI is a view, and as views in SwiftUI are structs and therefore immutable by default, SwiftUI allows us to modify properties by prefixing them with the @State property wrapper. (There are other options - @ObservedObject and @EnvironmentObject, but let's keep things simple for now!)
Meanwhile over in Flutter-land, everything is a widget! Similarly, widgets are completely immutable. Flutter implements a very similar solution to SwiftUI to this problem. If you expect the UI of a widget to change during runtime, this widget would extend the StatefulWidget class, and the widget would retain these changes in a special State object, which is in itself a widget. If you don't expect the UI of a widget to change, the widget would extend the StatelessWidget class.
Though these solutions sound very similar, there are a couple of important differences. In SwiftUI, a view can contain multiple properties that are tagged with the @State property wrapper. In Flutter, a StatefulWidget contains just one State object which itself can contain multiple properties. While SwiftUI @State objects contain simple values, in Flutter the State object is also responsible for building the widget itself that changes when any properties change.
While SwiftUI automatically updates any relevant views when a property has been updated, in Flutter you'll need to perform any updates inside a setState() function to be sure the UI gets updated.
As far as laying out widgets in Flutter, you'll find things work very similarly to SwiftUI, with just differences in terminology. While in SwiftUI you may be used to laying out your views inside stack views (specifically HStack, VStack and ZStack), in Flutter you will be using a similar concept called layout widgets. Flutter's layout widgets are simply called Column, Row and Container.
Thoughts on Flutter so far
So far, I must say I am actually pretty impressed with Flutter.
If you have experience with Swift and SwiftUI, the transition to Dart and Flutter is surprisingly familiar, and the learning curve not too steep.
I'm also loving that after doing all of your work in one framework - i.e. Flutter - you can build natively for multiple platforms. From the perspective of a native iOS developer, tied to one platform for years, pressing a button to make an app available to other platforms is the dream! I’m looking forward to experimenting more with Flutter, and think seriously about working on any future apps using this multi platform tool.
There is one litmus test I am curious about, though. How easy is it to bring in third party dependencies for multiple platforms? In the next article in this two part series, I'll be taking a look at this dilemma.