What if iOS Developers Want To Learn Flutter?
Flutter is a UI Framework that grants you the ability to write once and simultaneously deploy to multiple platforms such as macOS, Windows, Linux, Web, iOS and Android. With that said, the term “write once” might be a bit exaggerated because you’ll still have to adapt the code to each platform based on its environment. For example, you won’t be able to request GPS location on Windows or macOS platform, so writing once is not applicable here anymore. Still, at least you won’t have to start from scratch.
I‘ve been an iOS Developer since the iPhone 3GS era who, for the last 1.8 years, have been spending time working on a Flutter project. I find it to be amazing in terms of language and tools. To quickly pick up the skill, I followed this tutorial.
When I first started writing Dart, I was able to spot some advantages and disadvantages over Swift, along with some similarity to Swift. Looking back to the past 1.8 years, I wish somebody had come up with a cheat sheet for iOS developers to learn Flutter. Today I realize the person who should’ve done this all along is me since I have gained enough knowledge on both platforms to share with other iOS developers. I use https://docs.swift.org/swift-book as a guideline for this article. Let’s start with something fundamental: variables.
Declaring Constants and Variables
Swift
let name = “Swift”var lastname = “Apple”
Dart
final name = "Dart";
const nickName = "Flutter";var lastname = "Google";
Both use var as mutable variables. For immutable variables, Dart uses final while let is used in Swift. In Dart, we have two immutable objects: final
and const
. Here’s the difference.
- final you don’t know the value at compile time and assign it at run time, and the value can’t be changed e.g. fetching data from API
- const you already know the value at compile time and the value won’t be changed e.g. URL of API
Note: don’t forget that unlike Swift, Dart requires you to have a semicolon at the end of the line.
Type Annotations
Swift
private var name: String = "Swift"
let isLoggedIn = false
Dart
String _name = "Swift"; // It's mean private
var _name = "Swift"; // has the same meaning as above but you can omit declare Stringfinal isLoggedIn = false;
In Swift, you can add private
, internal
, public
, protected
, fileprivate
in front of the classes and variables to declare the type. In Dart, _
is added in front of var to declare that it’s private
. Otherwise, it will count as public
as Dart doesn’t have internal, protected or fileprivate like Swift.
In Dart, you can add Type in front of Swift like String or Bool, or you can only declare as var and final. Dart will know the type of these variables itself like Swift.
Tuples
Swift
let http404Error = (404, “Not Found”)
Dart doesn’t have tuples. You’ll have to use Object instead to make it work.
Optionals
Swift
var serverResponseCode: Int? = 404
Dart didn’t have optional until Dart 2.12 which is compatible with Flutter 2.0. If you have Dart 2.12 now, however, optional in Dart will be like this
Int? serverResponseCode = 404;
If Statements and Forced Unwrapping
Swift
var name: String? = "Swift"
if let unwrapName = name {
print(unwrapName)} else {
print("name is null")}// Or you can use Guard with let
var name: String? = "Swift"
guard let unwrapName = name else {
print("name is null") return
}print(unwrapName)
Dart doesn’t have an unwrap for optional, so we have to check if it’s null like the old Java days.
String? name = "Dart";
if (name != null) {
print(name);} else {
print("name is null");}
Error Handling
Swift
func testAge(age: Int) throws {
if age < 0 {
throw "Age can't be negative"
} // do something}do { try testAge(age: -2)} catch { // an error was thrown}
Dart
void testAge(int age) { // void is optional, no need to declare
if (age < 0) {
throw "Age can't be negative";
} // do something
}try {
testAge(-2); } catch(e) {
print(e.message); }
Ternary Conditional Operator
Swift
let contentHeight = 40let hasHeader = truelet rowHeight = contentHeight + (hasHeader ? 50 : 20)
Dart
final contentHeight = 40;final hasHeader = true;final rowHeight = contentHeight + (hasHeader ? 50 : 20);
Strings and Characters
Swift
let someString = "Some string literal value"
let quotation = """The White Rabbit put on his spectacles. “Where shall I begin,please your Majesty?” he asked.“Begin at the beginning,” the King said gravely, “and go ontill you come to the end; then stop.”"""
Dart
const someString = "Some string literal value";const quotation = """The White Rabbit put on his spectacles. “Where shall I begin,please your Majesty?” he asked.“Begin at the beginning,” the King said gravely, “and go ontill you come to the end; then stop.”""";
Basically the same thing.
String Interpolation
Swift
let name = "Swift"
let version = 5.4
let people = People(name: "Apple, age: 4)print("my name is \(name) version: \(version) age: \(people.age)")
Dart
final name = "Flutter";
final version = 2.0;
final people = People("Apple", 4);print("my name is $name version: ${version} age: ${people.age}");
// For simple variable you can omit {}
Arrays
Swift
// Create Empty Array
var someInts = [Int]()
someInts.append(5)var shoppingList: [String] = [“Eggs”, “Milk”]
print(shoppingList[0]) // Print "Eggs"
print("The shopping list contains \(shoppingList.count) items.")
// Prints "The shopping list contains 2 items."shoppingList.append("Fish")
for item in shoppingList {
print(item)
}// Prints
// Eggs
// Milk
// Fish
var peopleList: [People] = [People(name: "Amorn"), People(name: "Swift"), People(name: "Apple")]for people in peopleList {
print(people.name)
}// Prints
// Amorn
// Swift
// Apple
Dart
// Create Empty Array
var someInts = <int>[];
someInts.add(5);var shoppingList = ["Eggs", "Milk"];
print(shoppingList[0]); // Print "Eggs"
print("The shopping list contains ${shoppingList.length} items.") ;
// Prints "The shopping list contains 2 items."shoppingList.add("Fish")for (String item in shoppingList) {
print(item);
}// Prints
// Eggs
// Milk
// FishList<People> peopleList = [People("Amorn"), People("Swift"), People("Apple")];for (People people in peopleList) {
print(people.name);
}// Prints
// Amorn
// Swift
// Apple
Dictionaries or map
Swift
var fruitDictionary: [String: Int] = [“Apple”: 30, “Banana”: 50, "Carrot": 70, "Durian": 100]// Access variable in key
let durianPrice = fruitPrice["Durian"]for (fruitName, fruitPrice) in fruitPrice {
print("\(fruitName): \(fruitPrice)")
}// Prints
// Apple: 30
// Banana: 50
// Carrot: 70
// Durian: 100
Dart
Map<String, int> fruitDictionary = {"Apple": 30, "Banana": 50, "Carrot": 70, "Durian": 100};// Access variable in key
final durianPrice = fruitPrice["Durian"];for (MapEntry e in fruitDictionary.entries) {
print("${e.key}: ${e.value}")
}// Prints
// Apple: 30
// Banana: 50
// Carrot: 70
// Durian: 100
For-In Loops
Swift
for index in 0…5 {
print('hello \(index)')}
Dart
for (int i = 0; i < 5; i++) {
print('hello ${i}');
}
Conditional Statements
Swift
var temperatureInFahrenheit = 30if temperatureInFahrenheit <= 32 { print(“It’s very cold. Consider wearing a scarf.”)} else if temperatureInFahrenheit > 32 && temperatureInFahrenheit < 72 {
print(“It’s cozy, I like it.”)
} else {
print("too hot!!!")}
Dart
var temperatureInFahrenheit = 30;if (temperatureInFahrenheit <= 32) { print(“It’s very cold. Consider wearing a scarf.”);} else if (temperatureInFahrenheit > 32 && temperatureInFahrenheit < 72) {
print(“It’s cozy, I like it.”);
} else {
print("too hot!!!");}
Switch
Swift
let someCharacter: Character = “z”switch someCharacter { case “a”: print(“The first letter of the alphabet”) case “z”: print(“The last letter of the alphabet”) default: print(“Some other character”)
}enum Language {
case english
case thai
case spanish}let language: Language = .english
switch language {
case .english:
print("I speak English") case .thai:
print("I speak Thai") case .spanish:
print("I speak Spanish")}
Dart
final someCharacter = "z";switch (someCharacter) { case "a":
print("The first letter of the alphabet");
break; case "z":
print("The last letter of the alphabet");
break; default:
print("Some other character");
break;
}enum Language {
english,
thai,
spanish
}final Language language = Language.english;switch language {
case Language.english:
print("I speak English");
break; case Language.thai:
print("I speak Thai");
break; case Language.spanish:
print("I speak Spanish");
break;
}
Functions
Swift
// Function Parameters and Return Values
func greet(person: String) -> String {
let greeting = “Hello, “ + person + “!”
return greeting
}// Functions With Multiple Parameters
func greet(person: String, alreadyGreeted: Bool) -> String { if alreadyGreeted { return greetAgain(person: person) } else { return greet(person: person) }}
// Functions Without Return Valuesfunc greet(person: String) { print(“Hello, \(person)!”)}greet(person: “Dave”)// Prints “Hello, Dave!”// Default Parameter Values
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
// If you omit the second argument when calling this function, then// the value of parameterWithDefault is 12 inside the function body.}someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6)
// parameterWithDefault is 6someFunction(parameterWithoutDefault: 4)
// parameterWithDefault is 12
Dart
// Function Parameters and Return Values
// Dart has single line return functionString greet(person: String) => "Hello, $person !";orString greet(String person) {
final greeting = "Hello, $person !";
return greeting;
}// Functions With Multiple Parameters
String greet(String person, bool alreadyGreeted) { if (alreadyGreeted) { return greetAgain(person); } else { return greet(person); }}// Functions Without Return Valuesvoid greet(String person) { print("Hello, $person!");}greet("Dave"); // Dart doesn't have Argument Labels// Prints “Hello, Dave!”// Default Parameter Values
void someFunction(int parameterWithoutDefault, {int parameterWithDefault = 12}) {}someFunction(3, parameterWithDefault: 6);
// parameterWithDefault is 6someFunction(4);
// parameterWithDefault is 12
Closures
Swift
let numbers = [0, 1, 2, 3, 4]
let reversedNumbers = numbers.sorted(by: { $0 > $1 } )
// reversedNames is equal to [4, 3, 2, 1 , 0]
Dart
final numbers = [0, 1, 2, 3, 4];
numbers.sort((a, b) => b.compareTo(a));// numbers is equal to [4, 3, 2, 1 , 0]
Enumerations
Swift
enum CompassPoint: String { case north case south case east case west}// Associated Values
enum Barcode { case upc(Int, Int, Int, Int) case qrCode(String)}// Iterating over Enumeration Cases
enum Beverage: CaseIterable {
case coffee, tea, juice
}let numberOfChoices = Beverage.allCases.count
print(“\(numberOfChoices) beverages available”)for beverage in Beverage.allCases {
print(beverage)
}// coffee
// tea
// juice
Dart doesn’t have powerful enum like Swift. Enum for dart is only used for simple switch case.
Structures and Classes
Swift has a concept of value types: struct, and reference type: class.
Swift
struct Resolution { var width = 0 var height = 0}
class VideoMode { var resolution = Resolution() let interlaced: Bool let frameRate: Double let name: String init(name: String, frameRate: Double, interlaced: Bool) {
self.name = name
self.frameRate = frameRate
self.interlaced = interlaced
}}
Dart
Dart doesn’t have the concept of struct, value types, so everything is reference types.
class Resolution { var width = 0; var height = 0;}class VideoMode {
Resolution resolution = Resolution(); final bool interlaced; final Double frameRate; final String name;
// Automatic initializers in constructors
// if you don't have require, it will have value as null, if you forget to add value VideoMode({
@required this.interlaced, @required this.frameRate, @required this.name, });}
Computed Properties
Swift
struct Rect { var origin = Point() var size = Size() var center: Point { get { let centerX = origin.x + (size.width / 2) let centerY = origin.y + (size.height / 2) return Point(x: centerX, y: centerY) } set(newCenter) { origin.x = newCenter.x — (size.width / 2) origin.y = newCenter.y — (size.height / 2) } // Read only Properties
var isSizeZero: Bool {
return size.width == 0.0 && size.height == 0.0 } }}
Dart
class Rect { var origin = Point(); var size = Size();
int get center { final centerX = origin.x + (size.width / 2); final centerY = origin.y + (size.height / 2); return Point(centerX, centerY);
} void set center(Point newCenter) {
origin.x = newCenter.x — (size.width / 2); origin.y = newCenter.y — (size.height / 2); }
// Single line return will use => bool get isSizeZero => (size.width == 0.0 && size.height == 0.0); } }}
As for the last one…
Cascades
It’s a simple way to add, making it easier to write more fluent interfaces. Swift doesn’t have this.
Swift
let people = People()
people.tag = 1
people.name = "Apple"
people.lastname = "Swift"
people.age = 20
people.isGraduated = false
Dart
People()
..tag = 1
..name = "Apple"
..lastname = "Swift"
..age = 20
..isGraduated = false
I believe that this should cover all the basic ideas for Swift users who want to migrate to Dart. For more advanced stuff, I won’t be covering it here. Otherwise, this article will turn into a book with how long it’s going to be 😅
Next, I will cover the UI part between Swift and Dart.
User Interface
I’ll be comparing UIKit to Dart only since I still don’t have much experience with SwiftUI. For SwiftUI iOS developers, sorry folks! I will return later once I get used to SwiftUI more.
UIKit is Imperative UI while Dart is Declarative UI. For more info, refer to this article.
Simply put, it’s a HTML style UI. In Swift UIKit, here are the main things:
- Storyboard
- Nib Files
- ViewController
- UIView
The entire UI needs to be coded in dart. They don’t have an editor layout to see, but it’s not that big of a deal since Flutter has Hot reload to help with it.
There’s only one thing in Dart and it is Widget. Everything is a widget: text, button, even weird things like adding padding, margin, row, column are still counted as Widget. Dart doesn’t have Storyboard and Nib Files. ViewController and UIView will be counted as a Widget as well.
In UIKit, you can update the UI by assigning a new value to it and the UI will be updated. It’s mutable. Meanwhile, Dart’s concept is everything is immutable and we have to update the UI by updating the state, which is why Dart come up with the Stateful vs Stateless widgets concepts:
- StatelessWidget a type of widget that UI won’t change at all. It will be static e.g. icon the app, text header, Hardcoring page
- StatefulWidget a type of widget that UI will be changed programmatically based on conditions or HTTP requests e.g. loading people's names from API and displaying it in text, dynamic change UI on page
Why not just make everything StatefulWidget just to make it easy? The answer is Performance. You can read more about that here.
NavigationController
Dart
“MaterialApp + Scaffold” the root of the app that I know so far will always start with these two.
MaterialAppScaffold
- MaterialApp to show that you’re going to use Material Design, which is Google Design
- Scaffold is like NavigationController that have default AppBar, BottomBar and other components to help you start the app
You can also use CupertinoApp for iOS Design. But believe me, MaterialApp is a lot better and more mature, which is not surprising since it’s Google’s own product.
How to use
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
)
)
Result
Here’s some more information.
TabbarController
Dart
TableView & CollectionView
Dart
It has only one widget, which is ListView
If you’re working on the long list, which is very likely, you’ll have to use this instead: ListView.builder
TableViewCell Or CollectionViewCell
Dart
Use the above ListTiles, or you can customize your widget to make a UI instead.
UILabel
Dart
UIButton
Dart
There’s a lot of ready-designed buttons. You can pick from…
- Button with shadow
- Button with flat design
- Floating button, Android style
- Button with icon
- Button with wave effect when clicking
- Button with text
- Button with outlines
I’m positive there are more which I don’t know yet. Too many buttons!!
If you want to customize your own, you can add the Tap Gesture over any widget to make it work as a button.
UIImageView
Dart
They have a lot of functionality inside e.g. loading for network, loading from device, support icon.
Threading & asynchronicity
Dart
Here’s an example for Dart threading. Since Dart is single-threaded, your Dart will always run on the main thread.
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
print("This message will show after message is finished"); setState(() {
widgets = jsonDecode(response.body);
});
}
From the code above, await is the keyword to tell Dart, “hey, finish this line first. I don’t care how long I have to wait, I will wait till you finish. Then you can go start working on the next line.”
Http.get will take some time fetching the data from the network but we’re telling Dart that we will wait until we get a response back from the server and decode it as JSON, assign it into variables, and rebuild the whole page.
If you want something similar to call back in iOS. You can use then
keyword like this:
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.get(dataURL).then((response) {
setState(() {
widgets = jsonDecode(response.body);
});
});
print("This message will show without waiting for network");}
The difference is the print message from the example will print immediately without waiting for the network to call, while the first one the network has to finish first before it can call print.
ViewLifeCycle
I have found a very detailed article here.
Swift and Dart are very different here, so I’ll try my best to migrate it.
UIView
awakeFromNibUIViewController
viewDidLoad
viewWillAppear
viewDidAppear
viewWillDisappear
viewDiddisappear
Dart
Widget
initState() - It's called only once when you get into the view, similar to viewDidLoad, it's not like init in ViewController because it doesn't have nib files
build() - The closest that I can think of is draw(_:). build will be called every time the UI in the page is changed, so this function will call several times based on your UI
setState() - call widget to rebuild, similar to setNeedLayout in Swift, after you've called, this build() will be called again
dispose() - similar to deinit in Swift, You want to dispose your subscriber like NotificaionCenter
These are the four main functions in Widget that you will use the most. Surely there are other functions but I barely use it.
Here’s the last thing that Swift doesn’t have, but very important for Flutter.
BuildContext
It’s very complicated and I still can’t explain it clearly, but I will try my best to do it 😢 It’s the object that holds the reference to the widget that it comes from, so they can act on behalf of that widget.
Architecture
Swift
iOS Developers using MVC, MVVM, Clean Swift or VIPER — these are the most four popular architectures in Swift.
Dart
They have a similar concept but a different name called BLoC (Business Logic Component). It’s the same as MVVM that we pull the business logic part out of view and write it as a class, but Flutter prefers to use StreamBuilder.
This widget binds view with the data. It’s similar to the observable pattern in Swift: when data is updated, it will trigger the event to tell the UI to update itself. In Swift, it’s optional and most developers that I know barely use it, contrary to Flutter developers that use it everywhere, like how SwiftUI developers use Combine framework.
Dependency Manager
Swift
Cocoapods, Carthage, Swift package manager
Dart
Pub
If you’re looking to add any libs, you can find it here.
Must-Have Libs
Swift
Alamofire
KingFisher
SnapKit
Lottie
Hero
That’s just an example. You might see some use it differently.
Dart
Everything in Flutter requires libs because Flutter is just a UI framework. Camera, GPS, photo, even open website — we still have to use libs 😄 They might not build them in the Flutter framework, but don’t worry. Flutter team already made all the main libs.
I will keep updating this article in case I miss something that iOS developers need. Meanwhile, I hope you enjoy migrating from iOS to Dart. iOS skill is still needed in Dart, trust me.
Want to read more stories like this? Or catch up with the latest trends in the technology world? Be sure to check out our website for more at www.kbtg.tech