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.
Hereâs a little chart I made to help you wrap your head around the main differences between functional and object-oriented programming:
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 if
s 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:
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:
- Isolates and event loops
- Dart Futures
- Dart Streams
- Async/Await
- Generators
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