Hello, and welcome to this series! 👋 I’m Daniel, a software engineer at RisingStack, and I’ll be your guiding hand to get to learn Dart and Flutter.

This series is aimed at those who know React-Native, JavaScript, or web development and are trying to get into cross-platform mobile development because I’ll be comparing Dart language examples to JavaScript ones, and Flutter with React and React-Native.

However, if you don’t know any of these technologies yet, don’t let that throw you off from this series - I’ll explain core concepts thoughtfully. Let’s get started!

Let's learn the Dart language as JS developers: We dive into OOP, classes, inheritance, and mixins, asynchrony, callbacks, async/await and streams.

Why would you want to learn Flutter and Dart?

Flutter and Dart are made by Google. While Dart is a programming language, Flutter is a UI toolkit that can compile to native Android and iOS code, has experimental web and desktop app support, and it’s the native framework for building apps for Google’s Fuchsia OS.

This means that you don’t need to worry about the platform, and you can focus on the product itself. The compiled app is always native code as Dart compiles to ARM, hence providing you the best cross-platform performance you can get right now with over 60 fps. Flutter also helps the fast development cycle with stateful hot reload, which we’ll make use of mostly in the last episode of this series.

By the end of this series, you’ll have a basic understanding of Dart, the basic data structures, object-oriented programming, and asynchrony with futures and streams.

In Flutter, you’ll take a look at widgets, theming, navigation, networking, routing, using third-party packages, native APIs, and a lot more. Then, in the last episode of this series, we’ll put it all together and build a full-blown minigame together! Seems exciting? Then keep reading!

This episode of the series focuses on the Dart part of this ecosystem. We’ll look into Flutter in the next episode, and then we’ll put it all together into a fun minigame in the last episode. I’m excited to see what you’ll all build with Flutter, so let’s jump right in!

Sidenote: throughout this series, I'll use the “👉” emoji to compare JS and Dart language examples. Typically, the left side will be the JS, and the right side will be the Dart equivalent, e.g. console.log("hi!"); 👉 print("hello!");

Dart vs JavaScript - the pros and cons

JavaScript and Dart cannot be directly compared as they both have different use cases and target audiences. However, they both have their own advantages and disadvantages, and after a few projects with both technologies, you’ll get to see where they perform well.

There are some things, however, that you’ll notice as you are getting into the Flutter ecosystem: Dart has a steeper learning curve with all those types, abstract concepts and OOP - but don’t let that throw you off your track.

JavaScript has a bigger community, and hence more questions on StackOverflow, more packages, resources, learning materials, and meetups.

But once you get the hang of Dart, you’ll notice that Dart and Flutter has much-much better developer tooling, it’s faster, and compared to pub.dev, (Dart’s package repository) npm has more packages with worse quality.

Variables and types in the Dart language

After the first glance at a Dart code snippet, you may notice a concept that you may be unfamiliar with if you only know JS. Dart is type safe.

It means that when you want to define a variable, you’ll either have to provide an initial value and let the compiler figure out what type matches it (implicit typing), or (and this is the optimal case) you’ll have to provide the type of the variable explicitly.

In programming, types define what kind of data you are trying to store in your variable - for example, with an int type, you’ll be able to store an integer number (e.g. 7). In Dart, the most commonly used primitive types are int, double, string and boolean. Here are some language examples:

// Heads up! This is some nasty Dart code!
var num = 0; // Dart will implicitly give this variable an int type. var, let 👉var
int myInt = 3; // this is an explicitly typed variable
final double pi = 3.14; // const 👉final, static and const, more info below

myInt = 3.2; // will throw an error as 3.2 is not an integer
pi = 3.2; // will throw an error as pi is marked with final

String name = "Mark";

There’s also a “fallback-type” or a non-typed type: dynamic. In Dart, the dynamic type can be used whenever the exact type of a parameter, argument, list item, or anything else cannot be determined while writing your code. Please always be extra careful when working with dynamically typed variables and add extra safety barriers to your code so that your app doesn’t crash when an unexpected type gets passed. Try to avoid using dynamic as much as possible.

Oh, and a quick tip: to play around with Dart, you can use DartPad. It’s an online Dart compiler, or a “playground” made by the Dart team.

A few words about final, static and const

In Dart, we can create constants with three keywords: final, static, and const. final can be only created once in the runtime, while const is created at compile-time. You can think of const as an even stricter final. (When in doubt, you can use final and you’ll be just fine. To read more about the keywords final, static, and const, check out this article on the official Dart blog.

To get to know more about variables and the built-in types in Dart, please refer to this short explanation.

Writing your first Dart language function

Type-safety will come up in a lot of places - for example, when writing functions, you’ll have to define the return type and the type of the arguments.

// return type, function name, parameters with their types and names
double addDoubles(double a, double b) {
    return a + b;
}

addDoubles(3.2, 1.4); // => will return 4.6

And when your function doesn’t return anything, you can throw in the keyword void - just like the entry point of every Dart program, void main() does.

void main() {
    print(addNumbers(2, 3));  // console.log() 👉print()

    // this function does not return anything!
}

What’s an entry point anyways? In JavaScript, the code starts executing from the first line and goes linearly line-by-line until it reaches the end of the file. In Dart, you have to have a main() function that will serve as the body of your program. The compiler will start the execution with the main function, that’s where it enters your code - hence the name entry point.

Control flow statements - if, for, while, etc.

They look and work just like in JavaScript. Here are some examples:

int age = 20;

if(age >= 18) {
    print("here’s some beer! 🍻");
} else {
    print("🙅‍♂️sorry, no alcohol for you...");
}

// let’s count from 1 to 10!
// p.s.: notice the `int i`
for (int i = 1; i <= 10; i++) {
    print("it’s number $i");   // string interpolation: ${} 👉 $ (for variable names)
}

// while loops:
// please don’t run this snippet, it will probably crash or run out of resources...
while("🍌" == "🍌") {  // oh, and forget ===, you don’t need it in Dart!
    print("Hey! 👋 I’m a banana!");
}

Arrays and objects

In JavaScript, to store multiple pieces of data together, we use arrays and objects. In Dart, we call them lists and maps, and they work a bit differently under the hood (and they have some extra APIs!). Let’s look into them!

Array 👉List

In Dart, a list ideally stores an array of homogenous data . That’s right -- no more [1, "banana", null, 3.44] (ideally)! You can create a list with the [] syntax you are already familiar with from JS, and with the new List() constructor.

// the usual, implicitly typed, [] syntax
var continents = ["Europe", "North America", "South America", "Africa", "Asia", "Australia"]; 
continents.add("Antarctica");  // .push() 👉 .add()

// please note that when throwing in multiple types of data, Dart will fall back to the `dynamic` type for your list:
var maybeBanana = [1, "banana", null, 3.44];

// the `new List()` syntax, with a dynamic length:
// note the List<T> syntax: you need to pass in the desired value type between the <>s
List<int> someNiceNumbers = new List();
someNiceNumbers.add(5);

// fixed-length list:
List<int> threeNiceNumbers = new List(3); // this list will be able to hold 3 items, at max.

// dynamic list with the new List() syntax:
List<dynamic> stuff = new List();
stuff.add(3);
stuff.add("apple");  // this is still totally legit because of the <dynamic> type

Want to know more about lists in Dart? Check out the API reference here!

Object 👉Map

Now that we’ve covered arrays, we can move on to objects. In JavaScript, objects store key-value pairs, and the closest we can get to this data structure in Dart is a Map. Just like we saw at the List, we can define a Map both with the { ... } literal and with the new Map() constructor.

// the usual { ... } literal
var notesAboutDart = {
    objects: "hey look ma! just like in JS!",
    otherStuff: "idc we’ll look into them later"
};

// the new Map constructor
Map notesAboutJs = new Map();

// … and of course, you can explicitly type Maps!
// typed Map literal:
Map<String, int> prices = <String, int>{
    "apple": 100,
    "pear": 80,
    "watermelon": 400
};

// typed Map constructor:
final Map<String, String> response = new Map<String, String>();

Knowing about these methods will be just enough for now - but if you want to get to know the advanced stuff like HashMaps right away, be sure to check out the API docs of the Map class.

Imports and exports

In JavaScript, you could simply expose values from your files with export or module.exports and refer to them in other files with import or require(...). In Dart, it’s both a bit more complex and simpler than that.

To simply import a library, you can use the import statement and refer to the core package name, a library name, or a path:

import 'dart:math';  // import math from “math” 👉import “math”;

// Importing libraries from external packages
import 'package:test/test.dart';  // import { test } from “test” 👉import “test/test”;

// Importing files
import 'path/to/my_other_file.dart';  // this one is basically the same

// Specifying a prefix
import 'dart:math' as greatMath;

But how about creating your own libraries or exporting stuff? Dart lacks the usual public, protected or private keywords that Java has for this purpose (sidenote: Dart is compared to Java a lot of times) and even the export keyword that we’re used to in JavaScript. Instead, every file is automatically a Dart library and that means that you can just write code without explicitly exporting stuff, import it in another file, and expect it to work out just fine.

If you don’t want Dart to expose your variable, you can (and should!) use the _ prefix. Here’s an example:

// /dev/a.dart
String coolDudes = "anyone reading this";
String _hiddenSuffix = “...with sunglasses on 😎";

// /dev/b.dart
import "./b.dart";

print("cool dudes: $coolDudes"); // => cool dudes: anyone reading this
print("cool dudes: $coolDudes $_hiddenSuffix") // => will fail as _hiddenSuffix is undefined in this context

Oh, and just a quick note about naming variables: camelCasing is considered a best practice, just like capitalizing abbreviations longer than two characters (e.g. HTTP => Http, or HttpConnectionInfo). To know more about writing efficient and stylish Dart code, make sure that you read the Effective Dart guide later on your journey, once you are confident with the basics.

A quick intro to OOP and classes

Dart is an object oriented language - but what does that mean for you?

If you don’t know OOP yet, that means that you’ll have to learn a brand new paradigm of programming that is utilized in many popular languages like Java, C#, and of course, Dart. While introducing you to OOP isn’t the main goal of this series, I’ll provide you a quick intro so that you can start off with Dart and Flutter.

The first thing to settle is that JavaScript isn’t either strictly OOP nor functional - it contains elements from both architectures.

It’s up to your preferences, the project you work on, and the desired target framework, to choose (if a strict decision is ever made) between the two concepts. On the other hand, Dart is pretty strict about being OOP.

and i oop gif

Here’s a little chart I made to help you wrap your head around the main differences between functional and object-oriented programming:

functional vs object-oriented programming chart - dart language tutorial for beginners

To sum up: before OOP, there was procedural programming. There were a bunch of variables and functions lying around - and it was simple, but if often led to spaghetti code. To solve this, engineers came up with OOP, where we group related functions and variables into a unit. This unit is called an object, and inside it there are variables called properties and functions called methods. While creating this unit, always try to be descriptive. To practice making up these units, you can come up with real-world objects around you and try to describe them with properties and methods.

A car would, for example, have properties like their brand, color, weight, horse power, their license plate number and other stuff that can describe a car. Meanwhile it would have methods for acceleration, breaking, turning, etc.

Of course, you don’t have cars inside your code, so let’s put that abstract idea into code! A great example of a unit inside JS would be the window object. It has properties like the width and height of the window and has methods for resizing and scrolling.

The four principles of OOP are:

  • Encapsulation: Group variables (properties) and functions (methods) into units called objects.This reduces complexity and increases reusability.

  • Abstraction: You should not be able to directly modify the properties or access all methods - instead, think of writing a simple interface for your object. This helps you isolate the impact of changes made inside the objects.

  • Inheritance: Eliminate redundant code by inheriting stuff from another object or class. (Dart achieves this with mixins - we’ll look into concrete examples later). This helps you keep your code base smaller and more maintainable.

  • Polymorphism: Because of the inheritance, one thing can behave differently depending on the type of the referenced object. This helps you in refactoring and eliminating ugly ifs and switch/case statements.

Real-Life Dart Examples

If you are confused or intimidated by this concept, don’t worry. Looking at real-life Dart examples will help you wrap your head around this whole mess we call OOP. Let’s look at a simple class with some properties and a constructor.

class Developer {
  final String name;
  final int experienceYears;

  // Constructor with some syntactic sugar
  // a constructor creates a new instance of the class
  Developer(this.name, this.experienceYears) {
    // The code you write here will run when you construct a new instance of the Developer class
    // e.g. with the Developer dev = new Developer(“Daniel”, 12); syntax!
    // Notice that you don't have to explicitly type
    // this.name = name;
    // one by one. This is because of a Dart syntactic sugar
  }

  int get startYear =>
      new DateTime.now().year - experienceYears; // read-only property

  // Method
  // notice the `void` as this returns nothing
  void describe() {
    print(
        'The developer is $name. They have $experienceYears years of experience so they started development back in $startYear.');
    if (startYear > 3) {
      print('They have plenty of experience');
    } else {
      print('They still have a lot to learn');
    }
  }
}

And somewhere else in the code, you can construct a new instance of this class:

void main() {
  Developer peter = new Developer("Peter", 12);
  Developer aaron = Developer("Aaron", 2); // in Dart 2, the new keyword is optional
  peter.describe();
  // this well print this to the console:
  // The developer is Peter. They have 12 years of experience so they started development back in 2008.
  // They have plenty of experience.

  aaron.describe();
  // =>
  // The developer is Aaron. They have 2 years of experience so they started development back in 2018.
  // They still have a lot to learn.
}

And that’s it! You’ve just made your first Dart class with properties and methods. You used typed variables, get-only (protected) variables, control flow statements, got the current year and printed some stuff out to the console.

Congratulations! 🎉

Inheritance and mixins in Dart

Now while you have momentum, let’s have a peek at inheritance and mixins.

Once you have a solid knowledge of classes and start to think of more complex systems, you’ll feel the need for some way to inherit code from one class to another without copying and pasting code all over the place and making a big ol’ bowl of spaghetti. ❌🍝

For this reason, we have inheritance in OOP. When inheriting code from one class to another, you basically let the compiler copy and paste members of the class (“members” of the class are methods and properties inside a class), and add additional code on top of the previous class. This is where polymorphism kicks in: the same core code can exist in multiple ways by inheriting from a base class (the class you inherit from).

Think of HTML. There are several similar elements that HTML implements, like a TextBox, a Select or a Checkbox. They all share some common methods and properties like the click(), focus(), innerHTML, or hidden. With class inheritance, you can write a common class like HtmlElement and inherit the repetitive code from there.

How does this look in practice? In Dart, we use the extends keyword to inherit code from a base class. Let’s look at a short example:

// notice the extends keyword.
// we refer to the Developer class we defined in the previous snippet
class RisingStackEngineer extends Developer {
  final bool cool = true;
  String sunglassType;
  
  RisingStackEngineer(String name, int experienceYears, this.sunglassType)
      : super(name, experienceYears); // super() calls the parent class constructor
  
  void describeSunglasses() {
    print("$name has some dope-ass $sunglassType-type sunglasses.");
  }
}

And what can this class do? Let’s look at this snippet:

void main() {
  RisingStackEngineer berci = RisingStackEngineer("Bertalan", 300, "cool");
  berci.describe(); // .describe(); is not defined on the RisingStackEngineer class directly - it’s inherited from the Developer class. We can still use it though!
  berci.describeSunglasses(); // => Bertalan has some dope-ass cool-type sunglasses
}

Isn’t that amazing? Let’s make it even better with mixins. Mixins help you mix in more than one class into your hierarchy. For example, let’s give some keyboards for our developers:

class Keyboard {
  int numberOfKeys = 101;
  void describeKeyboard() {
    print("The keyboard has $numberOfKeys keys.");
  }
}

And use a mixin to create some sort of developer-keyboard hybrid person with Dart and the with keyword:

class WalkingKeyboard extends Developer with Keyboard {
  // ...
}

And that’s it! If you want to practice Dart before we move on to our last topic for today (asynchronous programming), be sure to play around with DartPad, an online compiler made by the Dart team.

Write some statements, create some classes and maybe even inherit some code. Don’t just read - pause this article and write some code! Once you feel comfortable with these base concepts (typing your variables, writing lists, maps, using control flow statements, creating classes), we’ll move forward to asynchronous programming with Dart.

Asynchronous programming in the Dart Langauge

Writing asynchronous code is a must when communicating with a server, working with files, or using some native APIs. In JavaScript, we had callbacks and async/await for timing our code. To our luck, Dart utilizes the very same concepts and embraces async/await to avoid callback hell.

Let’s look at a callback example first:

// Promise 👉 Future
// the method return type is an asynchronous void
Future<void> printWithDelay(String message) {
  // Future.delayed delays the code run with the specified duration
  return Future.delayed(Duration(seconds: 1)).then((_) {
    print(message);
  });
}

void main() {
  print("hey hi hello");
  printWithDelay("this message is printed with delay");
}

And look at the very same code with async/await:

// notice that you have to add in the async keyword to be able to await a Future
Future<void> printWithDelay(String message) async {
  await Future.delayed(Duration(seconds: 1));
  print(message);
}

void main() {
  print("hey hi hello");
  printWithDelay("this message is printed with delay");
}

And that was it for the Promise 👉 Future part. If you’d like to know more about the Future API, be sure to read the documentation. But stay tuned! Dart has another API for handling asynchrony: Streams. 🤯

Streams in the Dart Language

Dart’s main advancement in asynchrony compared to many other languages is native support for streams. If you want to have a simple way to wrap your head around the difference between Futures and Streams, think of the following: Future handles “finished future” (e.g. a web API response) with a single value, while Streams handle continuous future (e.g. an asynchronous for loop) with zero or more values.

Consider the following chart:
future vs stream: single value vs iterable in dart language

How do you work with data received from Dart Streams? Whenever a new event happens in the stream (either new data is received or an error happened), Dart notifies a listener. A listener is a snippet of code that subscribes for events of a stream and processes data whenever an event is received. You can subscribe to a stream with the .listen() function, provide a callback and boom, there you go! Isn’t that easy? 🤩 Let’s look at an example to get the hang of it:

// this is an imaginative stream that gives us an integer every one second
final exampleStream = NumberCreator().stream;
// e.g. 1, 2, 3, 4, ...

// print the data received from the stream
final subscription = exampleStream.listen((data) => print(data););

By default, Dart streams only support one listener. Adding another listener to this stream would throw an exception - however, there is a tool that helps us adding multiple listeners to a single stream. Broadcast streams! You can just throw in .asBroadcastStream at the end of your stream and you’ll be able to add multiple listeners to your stream:

// same code but with a broadcast stream. Notice the .asBroadcastStream at the end!
final exampleStream = NumberCreator().stream.asBroadcastStream;

// and you’ll be fine adding multiple listeners
final subscription = exampleStream.listen((data) => print(data););
final subscription2 = exampleStream.listen((data) => print(data););

But while we’re at listeners, let’s have a closer look at that API. I mentioned that you could either receive data or an error in a stream: how can you handle errors? I made a bit more advanced listener with error handling below. You can also run code when a stream finishes sending data (won’t send data anymore), you can explicitly define if you want to cancel listening when an error occurs, and a lot more. Here’s the code:

final advancedSubscription = exampleStream.listen(
    // this runs when new data is received
    (data) {
        print("data: $data");
    },

    // handle errors when one occurs
    onError: (err) {
        print("error: $err");
    },

    // do not cancel the subscription when an error occurs
    cancelOnError: false,

    // when the stream finishes, run some code.
    onDone: () {
        print("done!");
    }
);

Oh, and if this wouldn’t be enough for you, you can do stuff with the subscription object itself too:

advancedSubscription.pause(); // pause the subscription
advancedSubscription.resume(); // resume the subscription
advancedSubscription.cancel(); // remove/cancel the subscription

There is still a lot more that can be done with streams in Dart: you can manipulate them, filter their data, and of course, we didn’t have a look at asynchronous iterators and creating streams - however, this should be just enough for you to start development with Flutter.

If you want to know more about asynchrony in Dart, check out the following videos made by the Flutter team:

And that’s it for asynchronous programming - for now!

Summing our beginner Dart tutorial up

Congratulations on making it this far into the course! 🎉 If it was a bit dry or heavy for you, don’t worry: this was a Dart-only episode. In this episode, we looked at a crap ton of stuff! We went from variables, types, and control flow statements to lists, maps, imports, and exports.

Then, we came to the heavier parts of the Dart ecosystem. We first had a look at why OOP exists, what are its pros, where it performs well, and then we looked at classes, inheritance, and mixins, and if that wouldn’t be enough, we even looked at asynchrony, callbacks, async/await and streams.

Don’t forget: if you want to practice all these new stuff we just learned about, you can always hit up DartPad and play around with it for a bit. (I even encourage you to do so as you’ll need to have a strong Dart knowledge to move on to Flutter).

In the next episode, we’ll look into Flutter: we’ll start with the CLI and a hello world app, and have a look at widgets, lists, styling, state management, props, routing, and networking - and in the last episode, we’ll put it all together and build a fun game. Until then, stay tuned!

All the bests ✌️
Daniel from RisingStack