What if iOS Developers Want To Learn Flutter?

Amorn Apichattanakul
KBTG Life
Published in
16 min readOct 5, 2021

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 String
final 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
// Fish
List<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 6
someFunction(parameterWithoutDefault: 4)
// parameterWithDefault is 12

Dart

// Function Parameters and Return Values
// Dart has single line return function
String 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 6
someFunction(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:

  1. Storyboard
  2. Nib Files
  3. ViewController
  4. 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
awakeFromNib
UIViewController
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

--

--

Amorn Apichattanakul
KBTG Life

Google Developer Expert for Flutter & Dart | Senior Flutter/iOS Software Engineer @ KBTG