Dart vs Swift: a comparison

Andrea Bizzotto
Dec 27, 2018 · 23 min read
The Dart logo is licensed under the Creative Commons Attribution 3.0 Unported license.

Dart and Swift are my two favourite programming languages. I have used them extensively in commercial and open-source code.

This article offers a side by side comparison between Dart and Swift, and aims to:

  • Highlight the differences between the two.
  • Be a reference for developers moving from one language to the other (or using both).

Some context:

  • Dart powers Flutter, Google’s framework for building beautiful native apps from a single codebase.
  • Swift powers Apple’s SDKs across iOS, macOS, tvOS and watchOS.

The following comparison is made across the main features of both languages (as of Dart 2.1 and Swift 4.2). As discussing each feature in-depth is beyond the scope of this article, I include references for further reading where appropriate.

Table of Contents

Comparison table

Variables

Variable declaration syntax looks like this in Dart:

String name;
int age;
double height;

And like this in Swift:

var name: String
var age: Int
var height: Double

Variable initialization looks like this in Dart:

var name = 'Andrea';
var age = 34;
var height = 1.84;

And like this in Swift:

var name = "Andrea"
var age = 34
var height = 1.84

In this example type annotations are not needed. This is because both languages can infer the types from the expression on the right side of the assignment.

Speaking of which…

Type inference

Type inference means that we can write the following in Dart:

var arguments = {'argA': 'hello', 'argB': 42}; // Map<String, Object>

And the type of arguments is automatically resolved by the compiler.

In Swift, the same can be written as:

var arguments = [ "argA": "hello", "argB": 42 ] // [ String : Any ]

Some more details

Quoting the documentation for Dart:

And for Swift:

Dynamic types

A variable that can be of any type is declared with the dynamic keyword in Dart, and the Any keyword in Swift.

Dynamic types are commonly used when reading data such as JSON.

Mutable / immutable variables

Variables can be declared to be mutable or immutable.

To declare mutable variables, both languages use the var keyword.

var a = 10; // int (Dart)
a = 20; // ok
var a = 10 // Int (Swift)
a = 20 // ok

To declare immutable variables, Dart uses final, and Swift uses let.

final a = 10;
a = 20; // 'a': a final variable, can only be set once.
let a = 10
a = 20 // Cannot assign to value: 'a' is a 'let' constant

Note: The Dart documentation defines two keywords, final and const, which work as follows:

Further explanations are found on this post on the Dart website:

TL;DR: use final to define immutable variables in Dart.

In Swift, we declare constants with let. Quoting:

let constant name: type = expression

Read more: Swift Declarations.

Functions

Functions are first-class citizens in Swift and Dart.

This means that just like objects, functions can be passed as arguments, saved as properties, or returned as a result.

As an initial comparison, we can see how to declare functions that take no arguments.

In Dart, the return type precedes the method name:

void foo();
int bar();

In Swift, we use the -> T notation as a suffix. This is not required if there is no return value (Void):

func foo()
func bar() -> Int

Read more:

Named and un-named parameters

Both languages support named and un-named parameters.

In Swift, parameters are named by default:

func foo(name: String, age: Int, height: Double)
foo(name: "Andrea", age: 34, height: 1.84)

In Dart, we define named parameters with curly braces ({}):

void foo({String name, int age, double height});
foo(name: 'Andrea', age: 34, height: 1.84);

In Swift, we define un-named parameters by using an underscore (_) as an external parameter:

func foo(_ name: String, _ age: Int, _ height: Double)
foo("Andrea", 34, 1.84)

In Dart, we define un-named parameters by omitting the curly braces ({}):

void foo(String name, int age, double height);
foo('Andrea', 34, 1.84);

Read more: Function Argument Labels and Parameter Names in Swift.

Optional and default parameters

Both languages support default parameters.

func foo(name: String, age: Int = 0, height: Double = 0.0) 
foo(name: "Andrea", age: 34) // name: "Andrea", age: 34, height: 0.0

Read more: Default Parameter Values in Swift.

In Dart, optional parameters can be either positional or named, but not both.

// positional optional parameters
void foo(String name, [int age = 0, double height = 0.0]);
foo('Andrea', 34); // name: 'Andrea', age: 34, height: 0.0
// named optional parameters
void foo({String name, int age = 0, double height = 0.0});
foo(name: 'Andrea', age: 34); // name: 'Andrea', age: 34, height: 0.0

Read more: Optional parameters in Dart.

Closures

Being first-class objects, functions can be passed as arguments to other functions, or assigned to variables.

In this context, functions are also known as closures.

Here is a Dart example of a function that iterates over a list of items, using a closure to print the index and contents of each item:

final list = ['apples', 'bananas', 'oranges'];
list.forEach((item) => print('${list.indexOf(item)}: $item'));

The closure takes one argument (item), prints the index and value of that item, and returns no value.

Note the use of the arrow notation (=>). This can used in place of a single return statement inside curly braces:

list.forEach((item) { print('${list.indexOf(item)}: $item'); });

The same code in Swift looks like this:

let list = ["apples", "bananas", "oranges"]
list.forEach({print("\(String(describing: list.firstIndex(of: $0))) \($0)")})

In this case, we don’t specify a name for the argument passed to the closure, and use $0 instead to mean the first argument. This is entirely optional and we can use a named parameter if we prefer:

list.forEach({ item in print("\(String(describing: list.firstIndex(of: item))) \(item)")})

Closures are often used as completion blocks for asynchronous code in Swift (see section below about asynchronous programming).

Read more:

Tuples

From the Swift Docs:

These can be used as small light-weight types, and are useful when defining functions with multiple return values.

Here is how to use tuples in Swift:

let t = ("Andrea", 34, 1.84)
print(t.0) // prints "Andrea"
print(t.1) // prints 34
print(t.2) // prints 1.84

Tuples are supported with a separate package in Dart:

const t = const Tuple3<String, int, double>('Andrea', 34, 1.84);
print(t.item1); // prints 'Andrea'
print(t.item2); // prints 34
print(t.item3); // prints 1.84

Control flow

Both languages provide a variety of control flow statements.

Examples of this are if conditionals, for and while loops, switch statements.

Covering these here would be quite lengthy, so I refer to the official docs:

Collections (arrays, sets, maps)

Arrays / Lists

Arrays are ordered groups of objects.

Arrays can be created as Lists in Dart:

var emptyList = <int>[]; // empty list
var list = [1, 2, 3]; // list literal
list.length; // 3
list[1]; // 2

Arrays have a built-in type in Swift:

var emptyArray = [Int]() // empty array
var array = [1, 2, 3] // array literal
array.count // 3
array[1] // 2

Sets

Quoting the Swift docs:

This is defined with the Set class in Dart.

var emptyFruits = <String>{}; // empty set literal
var fruits = {'apple', 'banana'}; // set literal

Likewise, in Swift:

var emptyFruits = Set<String>()
var fruits = Set<String>(["apple", "banana"])

Maps / Dictionaries

The Swift docs have a good definition for a map/dictionary:

Maps are defined like so in Dart:

var namesOfIntegers = Map<Int,String>(); // empty map
var airports = { 'YYZ': 'Toronto Pearson', 'DUB': 'Dublin' }; // map literal

Maps are called dictionaries in Swift:

var namesOfIntegers = [Int: String]() // empty dictionary
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] // dictionary literal

Read More:

Nullability & Optionals

In Dart, any object can be null. And trying to access methods or variables of null objects results in a null pointer exception. This is one the most common source of errors (if not the most common) in computer programs.

Since the beginning, Swift had optionals, a built-in language feature for declaring if objects can or cannot have a value. Quoting the docs:

In contrast to this, we can use non-optional variables to guarantee that they will always have a value:

var x: Int? // optional
var y: Int = 1 // non-optional, must be initialized

Note: saying that a Swift variable is optional is roughly the same as saying that a Dart variable can be null.

Without language-level support for optionals, we can only check at runtime if a variable is null.

With optionals, we encode this information at compile-time instead. We can unwrap optionals to safely check if they hold a value:

func showOptional(x: Int?) {
// use `guard let` rather than `if let` as best practice
if let x = x { // unwrap optional
print(x)
} else {
print("no value")
}
}
showOptional(x: nil) // prints "no value"
showOptional(x: 5) // prints "5"

And if we know that a variable must have a value, we can use a non-optional:

func showNonOptional(x: Int) {
print(x)
}
showNonOptional(x: nil) // [compile error] Nil is not compatible with expected argument type 'Int'
showNonOptional(x: 5) // prints "5"

The first example above can be implemented like this in Dart:

void showOptional(int x) {
if (x != null) {
print(x);
} else {
print('no value');
}
}
showOptional(null) // prints "no value"
showOptional(5) // prints "5"

And the second one like this:

void showNonOptional(int x) {
assert(x != null);
print(x);
}
showNonOptional(null) // [runtime error] Uncaught exception: Assertion failed
showNonOptional(5) // prints "5"

Having optionals means that we can catch errors at compile-time rather than at runtime. And catching errors early results in safer code with fewer bugs.

Dart’s lack of support for optionals is somehow mitigated by the use of assertions (and the @required annotation for named parameters).

These are used extensively in the Flutter SDK, but result in additional boilerplate code.

For the record, there is a proposal about adding non-nullable types to Dart.

There is a lot more about optionals than I have covered here. For a good overview, see: Optionals in Swift.

Classes

Classes are the main building block for writing programs in object-oriented languages.

Classes are supported by Dart and Swift, with some differences.

Syntax

Here is class with an initializer and three member variables in Swift:

class Person {
let name: String
let age: Int
let height: Double
init(name: String, age: Int, height: Double) {
self.name = name
self.age = age
self.height = height
}
}

And the same in Dart:

class Person {
Person({this.name, this.age, this.height});
final String name;
final int age;
final double height;
}

Note the usage of this.[propertyName] in the Dart constructor. This is syntactic sugar for setting the instance member variables before the constructor runs.

Factory constructors

In Dart, it is possible to create factory constructors. Quoting:

One practical use case of factory constructors is when creating a model class from JSON:

class Person {
Person({this.name, this.age, this.height});
final String name;
final int age;
final double height;
factory Person.fromJSON(Map<dynamic, dynamic> json) {
String name = json['name'];
int age = json['age'];
double height = json['height'];
return Person(name: name, age: age, height: height);
}
}
var p = Person.fromJSON({
'name': 'Andrea',
'age': 34,
'height': 1.84,
});

Read more:

Inheritance

Swift uses a single-inheritance model, meaning that any class can only have one superclass. Swift classes can implement multiple interfaces (also known as protocols).

Dart classes have mixin-based inheritance. Quoting the docs:

Here is single-inheritance in action in Swift:

class Vehicle {
let wheelCount: Int
init(wheelCount: Int) {
self.wheelCount = wheelCount
}
}
class Bicycle: Vehicle {
init() {
super.init(wheelCount: 2)
}
}

And in Dart:

class Vehicle {
Vehicle({this.wheelCount});
final int wheelCount;
}
class Bicycle extends Vehicle {
Bicycle() : super(wheelCount: 2);
}

Properties

These are called instance variables in Dart, and simply properties in Swift.

In Swift, there is a distinction between stored and computed properties:

class Circle {
init(radius: Double) {
self.radius = radius
}
let radius: Double // stored property
var diameter: Double { // read-only computed property
return radius * 2.0
}
}

In Dart, we have the same distinction:

class Circle {
Circle({this.radius});
final double radius; // stored property
double get diameter => radius * 2.0; // computed property
}

In addition to getters for computed properties, we can also define setters.

Using the example above, we can rewrite the diameter property to include a setter:

var diameter: Double { // computed property
get {
return radius * 2.0
}
set {
radius = newValue / 2.0
}
}

In Dart, we can add a separate setter like so:

set diameter(double value) => radius = value / 2.0;

Property observers

This is a peculiar feature of Swift. Quoting:

This is how they can be used:

var diameter: Double { // read-only computed property
willSet(newDiameter) {
print("old value: \(diameter), new value: \(newDiameter)")
}
didSet {
print("old value: \(oldValue), new value: \(diameter)")
}
}

Read more:

Protocols / Abstract classes

Here we talk about a construct used to define methods and properties, without specifying how they are implemented. This is known as an interface in other languages.

In Swift, interfaces are called protocols.

protocol Shape {
func area() -> Double
}
class Square: Shape {
let side: Double
init(side: Double) {
self.side = side
}
func area() -> Double {
return side * side
}
}

Dart has a similar construct known as an abstract class. Abstract classes can’t be instantiated. They can however define methods which have an implementation.

The example above can be written like this in Dart:

abstract class Shape {
double area();
}
class Square extends Shape {
Square({this.side});
final double side;
double area() => side * side;
}

Read more:

Mixins

In Dart, a mixin is just a regular class, which can be reused in multiple class hierarchies.

Here is how we could extend the Person class we defined before with a NameExtension mixin:

abstract class NameExtension {
String get name;
String get uppercaseName => name.toUpperCase();
String get lowercaseName => name.toLowerCase();
}
class Person with NameExtension {
Person({this.name, this.age, this.height});
final String name;
final int age;
final double height;
}
var person = Person(name: 'Andrea', age: 34, height: 1.84);
print(person.uppercaseName); // 'ANDREA'

Read more: Dart Mixins

Extensions

Extensions are a feature of the Swift language. Quoting the docs:

This is not possible with mixins in Dart.

Borrowing the example above, we can extend the Person class like so:

extension Person {
var uppercaseName: String {
return name.uppercased()
}
var lowercaseName: String {
return name.lowercased()
}
}
var person = Person(name: "Andrea", age: 34, height: 1.84)
print(person.uppercaseName) // "ANDREA"

There is a lot more to extensions than I have presented here, especially when they are used in conjunction with protocols and generics.

One very common use case for extensions is adding protocol conformance to existing types. For example, we can use an extension to add serialization capabilities to an existing model class.

Read more: Swift Extensions

Enums

Dart has some very basic support for enums.

Enums in Swift are very powerful, because they support associated types:

enum NetworkResponse {
case success(body: Data)
case failure(error: Error)
}

This makes it possible to write logic like this:

switch (response) {
case .success(let data):
// do something with (non-optional) data
case .failure(let error):
// do something with (non-optional) error
}

Note how the data and error parameters are mutually exclusive.

In Dart we can’t associate additional values to enums, and the code above may be implemented along these lines:

class NetworkResponse {
NetworkResponse({this.data, this.error})
// assertion to make data and error mutually exclusive
: assert(data != null && error == null || data == null && error != null);
final Uint8List data;
final String error;
}
var response = NetworkResponse(data: Uint8List(0), error: null);
if (response.data != null) {
// use data
} else {
// use error
}

A couple of notes:

  • Here we use of assertions to compensate for the fact that we don’t have optionals.
  • The compiler can’t help us to check for all possible cases. This is because we don’t use a switch to process the response.

In summary, Swift enums are a lot more powerful and expressive than in Dart.

3rd party libraries such as Dart Sealed Unions provide similar functionality to what is offered by Swift enums, and can help to fill the gap.

Read more: Swift Enums.

Structs

In Swift we can define structures and classes.

Both constructs have many things in common, and some differences.

The main difference is that:

Quoting the documentation:

To see what this means, consider the following example, where we re-purpose the Person class to make it mutable:

class Person {
var name: String
var age: Int
var height: Double
init(name: String, age: Int, height: Double) {
self.name = name
self.age = age
self.height = height
}
}
var a = Person(name: "Andrea", age: 34, height: 1.84)
var b = a
b.age = 35
print(a.age) // prints 35

If we re-define Person to be a struct, we have this:

struct Person {
var name: String
var age: Int
var height: Double
init(name: String, age: Int, height: Double) {
self.name = name
self.age = age
self.height = height
}
}
var a = Person(name: "Andrea", age: 34, height: 1.84)
var b = a
b.age = 35
print(a.age) // prints 34

There is a lot more to structs than I have covered here.

Structs can be used to great effect to handle data and models in Swift, leading to robust code with fewer bugs.

For a better overview, read: Structures and Classes in Swift.

Error handling

Using a definition from the Swift docs:

Both Dart and Swift use try/catch as a technique for handling errors, with some differences.

In Dart, any method can throw an exception of any type.

class BankAccount {
BankAccount({this.balance});
double balance;
void withdraw(double amount) {
if (amount > balance) {
throw Exception('Insufficient funds');
}
balance -= amount;
}
}

Exceptions can be caught with a try/catch block:

var account = BankAccount(balance: 100);
try {
account.withdraw(50); // ok
account.withdraw(200); // throws
} catch (e) {
print(e); // prints 'Exception: Insufficient funds'
}

In Swift, we explicitly declare when a method can throw an exception. This is done with the throws keyword, and any errors must conform to the Error protocol:

enum AccountError: Error {
case insufficientFunds
}
class BankAccount {
var balance: Double
init(balance: Double) {
self.balance = balance
}
func withdraw(amount: Double) throws {
if amount > balance {
throw AccountError.insufficientFunds
}
balance -= amount
}
}

When handling errors, we use a try keyword inside a do/catch block.

var account = BankAccount(balance: 100)
do {
try account.withdraw(amount: 50) // ok
try account.withdraw(amount: 200) // throws
} catch AccountError.insufficientFunds {
print("Insufficient Funds")
}

Note how the try keyword is mandatory when calling methods that can throw.

And error themselves are strongly typed, so we can have multiple catch blocks to cover all possible cases.

try, try?, try!

Swift offers less verbose ways of dealing with errors.

We can use try? without a do/catch block. And this will cause any exceptions to be ignored:

var account = BankAccount(balance: 100)
try? account.withdraw(amount: 50) // ok
try? account.withdraw(amount: 200) // fails silently

Or if we are certain that a method won’t throw, we can use try!:

var account = BankAccount(balance: 100)
try! account.withdraw(amount: 50) // ok
try! account.withdraw(amount: 200) // crash

The example above will cause the program to crash. Hence, try! is not recommended in production code, and it is better suited when writing tests.

Overall, the explicit nature of error handling in Swift is very beneficial in API design, because it makes it easy to know if a method can or cannot throw.

Likewise, the usage of try in method calls draws the attention to code that can throw, forcing us to consider error cases.

In this respect, error handling feels safer and more robust than in Dart.

Read more:

Generics

Quoting the Swift docs:

Generics are supported by both languages.

One of the most common use cases for generics is collections, such as arrays, sets and maps.

And we can use them to define our own types. Here is how we would define a generic Stack type in Swift:

struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}

Similarly, in Dart we would write:

class Stack<Element> {
var items = <Element>[]
void push(Element item) {
items.add(item)
}
void pop() -> Element {
return items.removeLast()
}
}

Generics are very useful and powerful in Swift, where they can be used to define type constraints and associated types in protocols.

I recommend reading the documentation for more information:

Access control

Quoting the Swift documentation:

Swift has five access levels: open, public, internal, file-private, and private.

These are used in the context of working with modules and source files. Quoting:

The open and public access levels can be used to make code accessible outside modules.

The private and file-private access level can be used to make code not accessible outside the file it is defined in.

Examples:

public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}

Access levels are simpler in Dart, and limited to public and private. Quoting:

Examples:

class HomePage extends StatefulWidget { // public
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> { ... } // private

Access control was designed with different goals for Dart and Swift. And as a result the access levels are very different.

Read more:

Asynchronous Programming: Futures

Asynchronous programming is an area where Dart really shines.

Some form of asynchronous programming is needed when dealing with use cases such as:

  • Downloading content from the web
  • Talking to a backend service
  • Perform a long running operations

In these cases it is best to not block the main thread of execution, which can make our programs freeze.

Quoting the Dart documentation:

As an example, let’s see of how we may use asynchronous programming to:

  • authenticate a user with a server
  • store the access token to secure storage
  • get the user profile information

In Dart this can be done with async/await in combination with Futures:

Future<UserProfile> getUserProfile(UserCredentials credentials) async {
final accessToken = await networkService.signIn(credentials);
await secureStorage.storeToken(accessToken, forUserCredentials: credentials);
return await networkService.getProfile(accessToken);
}

In Swift, there is no support for async/await and we can only accomplish this with closures (completion blocks):

func getUserProfile(credentials: UserCredentials, completion: (_ result: UserProfile) -> Void) {
networkService.signIn(credentials) { accessToken in
secureStorage.storeToken(accessToken) {
networkService.getProfile(accessToken, completion: completion)
}
}
}

This leads to a “pyramid of doom” due to nested completion blocks. And error handling becomes very difficult in this scenario.

In Dart, handling errors in the code above is simply done by adding a try/catch block around the code to the getUserProfile method.

For reference, there is a proposal to add async/await to Swift in the future. This is documented in detail here:

Until this is implemented, developers can use 3rd party libraries such as this Promises library by Google.

As for Dart, the excellent documentation can be found here:

Asynchronous Programming: Streams

Streams are implemented as part of the Dart core libraries, but not in Swift.

Quoting the Dart documentation:

Streams are at the basis of reactive applications, where they play an important role with state management.

For example, streams are a great choice for searching content, where a new set of results is emitted each time the user updates the text in a search field.

Streams are not included in the Swift core libraries. 3rd party libraries such as RxSwift offer streams support and much more.

Streams are a broad topic that is not discussed here.

Read more: Dart Asynchronous Programming: Streams

Memory management

Dart manages memory with an advanced garbage collection scheme.

Swift manages memory via automatic reference counting (ARC).

This guarantees great performance because memory is released immediately when it is no longer used.

It does however shift the burden partially from the compiler to the developer.

In Swift we need to think about lifecycle and ownership of objects, and use the appropriate keywords (weak, strong, unowned) correctly to avoid retain cycles.

Read more: Swift Automatic Reference Counting.

Compilation and execution

First of all, an important distinction between just-in-time (JIT) and ahead-of-time (AOT) compilers:

JIT compilers

A JIT compiler runs during execution of the program, compiling on the fly.

JIT compilers are typically used with dynamic languages, where types are not fixed ahead of time. JIT programs run via an interpreter or a virtual machine (VM).

AOT compilers

An AOT compiler runs during creation of the program, before runtime.

AOT compilers are normally used with static languages, which know the types of the data. AOT programs are compiled into native machine code, which is executed directly by the hardware at runtime.

Quoting this great article by Wm Leler:

As a static language, Swift is compiled ahead-of-time.

Dart can be compiled both AOT and JIT. This provides significant advantages when used with Flutter. Quoting again:

With Dart we get the best of both worlds.

Swift suffers from the main drawback of AOT compilation. That is, compilation time increases with the size of the codebase.

For a medium sized app (between 10K and 100K lines), it can easily take minutes to compile an app.

Not so with Flutter apps, where we consistently get sub-second hot-reload, irrespective of the size of the codebase.

Other features not covered

The following features were not covered as they are quite similar in Dart and Swift:

  • Operators (see reference for Swift and Dart)
  • Strings (see reference for Swift and Dart)
  • Optional chaining in Swift (known as conditional member access in Dart).

Concurrency

  • Concurrent programming this is provided with isolates in Dart.
  • Swift uses Grand Central Dispatch (GCD) and dispatch queues.

My favourite Swift features missing from Dart

  • Structs
  • Enums with associated types
  • Optionals

My favourite Dart features missing from Swift

  • Just-in-time compiler
  • Futures with await/async (see async/await proposal by Chris Lattner)
  • Streams with yield/async* (RxSwift offers a superset of streams for reactive applications)

Conclusion

Both Dart and Swift are excellent languages, well suited for building modern mobile apps and beyond.

Neither language is superior, as they both have their own unique strong points.

When looking at mobile app development and the tooling for the two languages, I feel that Dart has the upper hand. This is due to the JIT compiler, which is at the foundation of stateful hot-reload in Flutter.

And hot-reload delivers a huge productivity gain when building apps, because it speeds up the development cycle from seconds or minutes to less than a second.

Developer time is a more scarce resource than computing time.

So optimising for developer time is a very smart move.


On the other hand, I feel that Swift has a very strong type system. Type safety is baked into all language features, and leads more naturally to robust programs.


Once we set aside personal preferences, programming languages are just tools. And it is our job as developers to choose the most appropriate tool for the job.

In any case, we can hope that both languages will borrow the best ideas from each other as they evolve.

References & Credits

Both Dart and Swift have an extensive feature set, and have not been fully covered here.

I prepared this article borrowing information from the official Swift and Dart documentation, which can be found here:

In addition, the section about JIT and AOT compilers is heavily inspired by this great article by Wm Leler:

Have I missed something? Let me know in the comments. 🙂

Happy coding!

UPDATE: My Flutter & Firebase Udemy course is now available for Early Access. Use this link to enroll (discount code included):

For more articles and video tutorials, check out Code With Andrea.

I’m @biz84 on Twitter. You can also see my GitHub page.

Code With Andrea

Learn to build iOS and Android apps with Dart and Flutter

Andrea Bizzotto

Written by

iOS, Flutter Developer & Instructor ❖ https://codewithandrea.com ❖ Open Source https://github.com/bizz84 ❖ Watching #ClimateChange

Code With Andrea

Learn to build iOS and Android apps with Dart and Flutter

Andrea Bizzotto

Written by

iOS, Flutter Developer & Instructor ❖ https://codewithandrea.com ❖ Open Source https://github.com/bizz84 ❖ Watching #ClimateChange

Code With Andrea

Learn to build iOS and Android apps with Dart and Flutter

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store