Android Test-Driven Development by Tutorials,
Second Edition – Now Updated!

Build testable, sustainable Android apps via JUnit, Mockito, and Espresso
by diving into test-driven development (TDD) in this newly-updated book.

Home Flutter Tutorials

Dart Basics

Get an introduction to the basics of the Dart programming language, used for development with the Flutter SDK for mobile, web and beyond.

4.3/5 4 Ratings

Version

  • Dart 2.12, Flutter 2.2, DartPad
Update note: Jonathan Sande updated this tutorial for Dart 2.12. Joe Howard wrote the original.

Flutter is an exciting UI toolkit from Google that lets you write apps for different platforms including iOS, Android, the web and more, all using one codebase. Flutter uses the Dart language.

If you’re not familiar with Dart yet, this tutorial will introduce you to its basic concepts and show you how it’s similar to other programming languages you may already know.

Over the course of this tutorial, you’ll get an introduction to Dart basics such as:

  • Variables, data types, and operators
  • Conditionals and loops
  • Collections
  • Functions

By the time you’re done, you’ll be ready to dive right into Flutter development using Dart.

Getting Started

Download the example code by clicking the Download Materials button at the top or bottom of the page. You won’t be building a sample project in this tutorial, but you can use the coding examples as a reference.

You can either paste the code from main.dart into DartPad or use the Dart SDK to run the file.

To get started quickly, your best bet is to use the open-source tool DartPad, which lets you write and test Dart code via a web browser:

DartPad editor, annotated to show the location of each element below

DartPad is set up like a typical IDE. It includes the following components:

  • Editor pane: Located on the left. Your code will go here.
  • RUN button: Runs code in the editor.
  • Console: Located on the upper-right, this shows output.
  • Documentation panel: Located on the bottom-right, this shows information about code.
  • Samples: This drop-down shows some sample code.
  • Null Safety button: Use this to opt in to Dart’s new sound null safety feature.
  • Version information: At the very bottom-right, DartPad shows which versions of Flutter and Dart it’s currently using.

If you prefer, you can install the Dart SDK locally on your machine. One way to do so is to install the Flutter SDK. Installing Flutter will also install the Dart SDK.

To install the Dart SDK directly, visit https://dart.dev/get-dart.

Why Dart?

Dart has many similarities to other languages such as Java, C#, Swift and Kotlin. Some of its features include:

  • Statically typed
  • Type inference
  • String expressions
  • Multi-paradigm including object-oriented and functional programming
  • Null safe

Dart is optimized for developing fast apps on a variety of platforms.

Core Concepts

Dart programs begin with a call to main. Dart’s syntax for main looks similar to that of other languages like C, Swift or Kotlin.

Clear out all the code in the default DartPad and add main to the editor:

void main() {
  
}

You’ll see there’s a return type before main. In this case, it’s void, meaning that main won’t return anything.

The parentheses after main indicate that this is a function definition. The curly brackets contain the body of the function.

Inside main, you add the Dart code for your program.

Next, you’ll learn more about the following core concepts:

  • Variables, Comments and Data Types
  • Basic Dart Types
  • Operators
  • Strings
  • Immutability
  • Nullability
  • Condition and Break
  • For Loops

It’s time to dive in!

Variables, Comments and Data Types

The first thing you’ll add to main is a variable assignment statement. Variables hold the data that your program will work on.

You can think of a variable as a box in your computer’s memory that holds a value. Each box has a name, which is the name of the variable. To denote a variable with Dart, use the var keyword.

Add a new variable to main:


var myAge = 35;  

Each Dart statement ends in a semicolon, just as statements do in C and Java. In the code above, you’ve created a variable, myAge, and set it equal to 35.

You can use the built-in print in Dart to print the variable to the console. Add that call after the variable:


print(myAge); // 35

Click RUN in DartPad to run the code. You’ll see the variable’s value, 35, prints in the console.

First Dart output

Comments

Comments in Dart look just like those in C and in other languages: Text following // is a single-line comment, while text within /* ... */ is a multi-line comment block.

Here’s an example:


// This is a single-line comment.

print(myAge); // This is also a single-line comment.

/*
 This is a multi-line comment block. This is useful for long
 comments that span several lines.
 */

Data Types

Dart is statically typed, meaning that each variable in Dart has a type that must be known when you compile the code. The variable type cannot change when you run the program. C, Java, Swift and Kotlin are also statically typed.

This contrasts with languages like Python and JavaScript, which are dynamically typed. That means variables can hold different kinds of data when you run the program. You don’t need to know the type when you compile the code.

Click myAge in the editor window and look in the Documentation panel. You’ll see Dart inferred that myAge is an int because it was initialized with the integer value 35.

If you don’t explicitly specify a data type, Dart uses type inference to try to determine it, just like Swift and Kotlin do.

Type Inference

Dart also uses type inference for types other than int. Enter a variable, pi, equal to 3.14:


var pi = 3.14;

print(pi); // 3.14

Dart infers pi to be a double because you used a floating-point value to initialize it. You can verify that in the Dart info panel by clicking pi.

double data type inference

Alternatively, instead of using type inference, you can declare the type.

Basic Dart Types

Dart uses the following basic types:

  • int: Integers
  • double: Floating-point numbers
  • bool: Booleans
  • String: Sequences of characters

Here’s an example of each Dart type:

Dart Data Types

int and double both derive from a type named num. num uses the dynamic keyword to mimic dynamic typing in the statically typed Dart.

Do this by replacing var with the type you want to use:


int yourAge = 27;

print(yourAge); // 27

The Dynamic Keyword

If you use the dynamic keyword instead of var, you get what’s effectively a dynamically typed variable:


dynamic numberOfKittens;

Here, you can set numberOfKittens to a String using quotes. You’ll learn more about the String type later in the tutorial.


numberOfKittens = 'There are no kittens!';

print(numberOfKittens); // There are no kittens!

numberOfKittens has a type, since Dart has static typing. But that type is dynamic, which means you can assign other values with other types to it. So you can assign an int value below your print statement.


numberOfKittens = 0;

print(numberOfKittens); // 0

Or, if you have a kitten in Schrödinger’s box, you could assign a double value:


numberOfKittens = 0.5;

print(numberOfKittens); // 0.5

Schrödingers Cat

Click Run to see the three different values for numberOfKittens printed in the console. In each case, the type of numberOfKittens remains dynamic, even though the variable itself holds values of different types.

Dart dynamic data type

Booleans

The bool type holds values of either true or false.


bool areThereKittens = false;

print(areThereKittens); // false

But, if you look inside Schrödinger’s box, you might switch to having a real live kitten:


numberOfKittens = 1;

areThereKittens = true;

print(areThereKittens); // true

Run the code again to see your Boolean values in the console. It’s a good thing you looked in the box! :]

Dart bool data type

Operators

Dart has all the usual operators you’re familiar with from other languages like C, Swift and Kotlin.

Some examples of Dart’s operators include:

  • arithmetic
  • equality
  • increment and decrement
  • comparison
  • logical
Note: Dart also allows for operator overloading, as in C++ and Kotlin, but that’s beyond the scope of this tutorial. To learn more about the topic, visit Wikipedia’s overator overloading page.

Next, you’ll take a look at each of these operators.

Arithmetic Operators

Arithmetic operators work just as you’d expect. Try them out by adding a bunch of operations to your DartPad:


print(40 + 2); // 42

print(44 - 2); // 42

print(21 * 2); // 42

print(84 / 2); // 42.0

For division, even with integers, Dart infers the resulting variable to be a double. That’s why you get 42.0 instead of 42 for the last print statement.

Note: DartPad displays the result of “84 / 2” as 42 in the console because it formats the output to the console to display just the significant digits. If you print the same statement in a Dart program from the Dart SDK, you’ll get 42.0 as the result.

Equality Operators

Dart uses double-equals (==) equality and not-equals (!=) operators:


print(42 == 43); // false

print(42 != 43); // true

Comparison Operators

Dart uses the typical comparison operators:

  • Less than (<)
  • Greater than (>)
  • Equal to (=>)

Here are some examples:


print(42 < 43); // true print(42 >= 43); // false

In addition, it also uses the usual compound arithmetic/assignment operators:


var value = 42.0;

value += 1; print(value); // 43.0

value -= 1; print(value); // 42.0

value *= 2; print(value); // 84.0

value /= 2; print(value); // 42.0

Compound arithmetic/assignment operators perform two tasks. += adds the value to the right to the variable on the left and then assigns the result to the variable.

A shortened form of += 1 is ++:


value++;

print(value); // 43.0

And Dart has the usual modulo operator (%) to return the remainder after one number has been divided by another:


print(392 % 50); // 42

392 ÷ 50 = 7.84, but where does that 42 in the result pane come from? It’s easier to see with long division.

long division showing 392 divided by 50 with a remainder of 42

Logical Operators

Dart uses the same logical operators as other languages, including && for AND and || for OR.


print((41 < 42) && (42 < 43)); // true

print((41 < 42) || (42 > 43)); // true

The negation operator is the exclamation mark, which turns false to true and true to false.


print(!(41 < 42)); // false

See the Dart documentation for a complete list of supported operators.

Strings

The Dart string type is String. Strings are expressed in Dart using text surrounded by either single or double quotes.

You can use either var and type inference or String to create a string variable, like other types you’ve seen:


var firstName = 'Albert';

String lastName = "Einstein";

Similar to languages like Kotlin and Swift, you can embed the value of an expression inside a string by using the dollar sign symbol: ${expression}.

If the expression is an identifier, you can omit the { }. Add the following:

var physicist = "$firstName $lastName likes the number ${84 / 2}";

print(physicist); // Albert Einstein

$firstName and $lastName are replaced by the variable values. The returns the calculated result.

Escaping Strings

The escape sequences used in Dart are similar to those used in other C-like languages. For example, you use \n for a new line.

If there are special characters in the string, use \ to escape them:

var quote = 'If you can\'t explain it simply\nyou don\'t understand it well enough.';

print(quote);

// If you can't explain it simply

// you don't understand it well enough.

This example uses single quotes, so it needs an escape sequence, \', to embed the apostrophes for can’t and don’t into the string. You wouldn’t need to escape the apostrophe if you used double quotes instead.

If you need to show escape sequences within the string, you can use raw strings, which are prefixed by r.

var rawString = r"If you can't explain it simply\nyou don't understand it well enough.";

print(rawString); 

// If you can't explain it simply\nyou don't understand it well enough.

Here, Dart treated `\n` as normal text because the string started with r.

Click RUN in DartPad to see all your strings in the console.

Dart String data type

Immutability

Dart uses the keywords const and final for values that don’t change.

Use const for values that are known at compile-time. Use final for values that don’t have to be known at compile-time but cannot be reassigned after being initialized.

Note: final acts like val in Kotlin or let in Swift.

You can use const and final in place of var and let type inference determine the type:


const speedOfLight = 299792458;

print(speedOfLight); // 299792458

Here, Dart infers that speedOfLight is an int, as you can see in DartPad’s info panel.

Dart const inference

final indicates that a variable is immutable, meaning you can’t reassign final values. You can explicitly state the type with either final or const:


final planet = 'Jupiter';

// planet = 'Mars';

// error: planet can only be set once

final String moon = 'Europa';

print('$planet has a moon, $moon');

// Jupiter has a moon, Europa

Nullability

In the past, if you didn’t initialize a variable, Dart gave it the value null, which means nothing was stored in the variable. As of Dart 2.12, though, Dart joins other languages, like Swift and Kotlin, to be non-nullable by default.

Additionally, Dart guarantees that a non-nullable type will never contain a null value. This is known as sound null safety.

Normally, if you want to declare a variable, you must initialize it:


String middleName = 'May';

print(middleName); // May

Not everyone has a middle name, though, so it makes sense to make middleName a nullable value. To tell Dart that you want to allow the value null, add ? after the type.


String? middleName = null;

print(middleName); // null

The default for a nullable type is null, so you can simplify the expression to the following:

String? middleName;

print(middleName); // null

Run that and null prints to the console.

Dart null string output

Null-Aware Operators

Dart has some null-aware operators you can use when you’re working with null values.

The double-question-mark operator, ??, is like the Elvis operator in Kotlin: It returns the left-hand operand if the object isn’t null. Otherwise, it returns the right-hand value:


var name = middleName ?? 'none';

print(name); // none

Since middleName is null, Dart assigns the right-hand value of 'none'.

The ?. operator protects you from accessing properties of null objects. It returns null if the object itself is null. Otherwise, it returns the value of the property on the right-hand side:

print(middleName?.length); // null

In the days before null safety, if you forgot the question mark and wrote middleName.length, your app would crash at runtime if middleName was null. That’s not a problem anymore, because Dart now tells you immediately when you need to handle null values.

Control Flow

Control flow lets you dictate when to execute, skip over or repeat certain lines of code. You use conditionals and loops to handle control flow in Dart.

In this section, you’ll learn more about:

  • Conditionals
  • While Loops
  • Continue and Break
  • For Loops

Here’s what you need to know about control flow elements in Dart.

Conditionals

The most basic form of control flow is deciding whether to execute or skip over certain parts of your code, depending on conditions that occur as your program runs.

The language construct for handling conditions is the if/else statement. if/else in Dart looks nearly identical to its use in other C-like languages.

If Statements

Suppose you have a variable, animal, that’s currently a fox. It looks like this:

var animal = 'fox';

Fox

You can use an if statement to check whether animal is a cat or a dog, then run some corresponding code.

if (animal == 'cat' || animal == 'dog') {
  print('Animal is a house pet.');
}

Here, you’ve used the equality and OR operators to create a bool inside the condition for the if statement.

Else Statements

With an else clause, you can run alternative code if the condition is false:

else {
  print('Animal is NOT a house pet.');
}
// Animal is NOT a house pet.

You can also combine multiple if/else statements into an if/else if/else construct:

if (animal == 'cat' || animal == 'dog') {
  print('Animal is a house pet.');
} else if (animal == 'rhino') {
  print('That\'s a big animal.');
} else {
  print('Animal is NOT a house pet.');
}
// Animal is NOT a house pet.

You can have as many else if branches between if and else as you need.

While Loops

Loops let you repeat code a certain number of times or based on certain conditions. You handle condition-based repetition using while loops.

There are two forms of while loops in Dart: while and do-while. The difference is that for while, the loop condition occurs before the code block. In do-while, the condition occurs after. That means that do-while loops ensure the code block runs at least one time.

Testing the While Loop

To try this out, create a variable i initialized to 1:

var i = 1;

Next, use a while loop to print i while incrementing it. Set the condition to i is less than 10:

while (i < 10) {
  print(i);
  i++;
}
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9

Run the code and you’ll see that the while loop prints the numbers 1 to 9.

Trying Out the Do-While Loop

Reset i in DartPad, then add a do-while loop:

i = 1;
do {
  print(i);
  i++;
} while (i < 10);
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9

The results are the same as before. This time, however, the loop body ran once before checking the loop exit condition.

Continue and Break

Dart uses continue and break keywords in loops and elsewhere. Here’s what they do:

  • continue: Skips remaining code inside a loop and immediately goes to the next iteration.
  • break: Stops the loop and continues execution after the body of the loop.

Be careful when using continue in your code. For example, if you take the do-while loop from above, and you want to continue when i equals 5, that could result in an infinite loop depending on where you place the continue statement:

i = 1;
do {
  print(i);
  if (i == 5) {
    continue;
  }            
  ++i;
} while (i < 10);
// 1
// 2
// 3
// 4
// 5
// 5
// 5
// 5
// 5
// 5
// 5
// 5
// 5
// 5
// ...

The infinite loop occurs because, once i is 5, you never increment it again, so the condition always stays true.

If you run this in DartPad, the infinite loop will cause the browser to hang. Instead, use break, so the loop ends after i reaches 5:

i = 1;
do {
  print(i);
  if (i == 5) {
    break;
  }
  ++i;
} while (i < 10);
// 1
// 2
// 3
// 4
// 5

Run the code. Now, the loop ends after five iterations.

For Loops

In Dart, you use for loops to loop a predetermined number of times. for loops consist of initialization, a loop condition and an action. Once again, they’re similar to for loops in other languages.

Dart also offers a for-in loop, which iterates over a collection of objects. You’ll learn more about these later.

To see how a for loop works, create a variable for the sum:

var sum = 0;

Next, use a for loop to initialize a loop counter from i to 1. You’ll then check that i is less than or equal to 10 and increment i after every loop.

Inside the loop, use a compound assignment to add i to the running sum:

for (var i = 1; i <= 10; i++) {
  sum += i;  
}
print("The sum is $sum"); // The sum is 55

Run your code in DartPad to see the sum.

Dart for loop

Collections

Collections are useful for grouping related data. Dart includes several different types of collections, but this tutorial will cover the two most common ones: List and Map.

Lists

Lists in Dart are similar to arrays in other languages. You use them to maintain an ordered list of values. Lists are zero-based, so the first item in the list is at index 0:

List of desserts

Here’s a list of different desserts:

List desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];

You enclose the elements of a list in square brackets: [ ]. You use commas to separate the elements.

At the beginning of the line, you can see that the type is List. You’ll notice there is no type included. Dart infers that list has type List.

Here’s a list of integers:

final numbers = [42, -1, 299792458, 100];

Click numbers in DartPad and you’ll see that Dart recognizes the type as a List of int.

Dart List of int

Working With List Elements

To access the elements of a list, use subscript notation by putting the index number between square brackets after the list variable name. For example:

final firstDessert = desserts[0];
print(firstDessert); // cookies

Since list indices are zero-based, desserts[0] is the first element of the list.

Add and remove elements with add and remove respectively:

desserts.add('cake');
print(desserts); 
// [cookies, cupcakes, donuts, pie, cake]

desserts.remove('donuts');
print(desserts); 
// [cookies, cupcakes, pie, cake]

Run the code to see the results.

Dart list add and remove methods

Earlier, you learned about for loops. Dart’s for-in loop that works especially well with lists. Try it out:

for (final dessert in desserts) {
  print('I love to eat $dessert.');
}
// I love to eat cookies.
// I love to eat cupcakes.
// I love to eat pie.
// I love to eat cake.

You don’t need to use an index. Dart just loops through every element of desserts and assigns it each time to a variable named dessert.

Getting hungry? Well, you can’t have any dessert until you’ve finished your vegetables. :]

Maps

When you want a list of paired values, Map is a good choice. Dart Maps are similar to dictionaries in Swift and maps in Kotlin.

Dart maps

Here’s an example of a map in Dart:

Map<String, int> calories = {
  'cake': 500,
  'donuts': 150,
  'cookies': 100,
};

You surround Maps with curly braces { }. Use commas to separate a map’s elements.

Elements of a map are called key-value pairs, where the key is on the left of a colon and the value is on the right.

You find a value by using the key to look it up, like this:

final donutCalories = calories['donuts'];
print(donutCalories); // 150

The key, 'donuts', is inside the square brackets after the map name. In this case, it maps to a value of 150.

Click donutCalories in DartPad and you’ll see that the inferred type is int? rather than int. That’s because, if a map doesn’t contain the key that you’re looking up, it’ll return a null value.

Dart Map output

Add a new element to a map by specifying the key and assigning it a value:

calories['brownieFudgeSundae'] = 1346;
print(calories);
// {cake: 500, donuts: 150, cookies: 100, brownieFudgeSundae: 1346}

Run that code and you’ll see the map printed in horizontal format with your new dessert at the end.

Functions

Functions let you package multiple related lines of code into a single body. You then summon the function to avoid repeating those lines of code throughout your Dart app.

Dart function

A function consists of the following elements:

  • Return type
  • Function name
  • Parameter list in parentheses
  • Function body enclosed in brackets

Defining Functions

The code you’re turning into a function goes inside the curly brackets. When you call the function, you pass in arguments that match the types of the parameters of the function.

Next, you’ll write a new function in DartPad that will check to see if a given string is banana:

bool isBanana(String fruit) {
  return fruit == 'banana';
}

The function uses return to return a bool. The argument you pass to the function determines the bool.

This function will always return the same value type for any given input. If a function doesn’t need to return a value, you can set the return type to void. main does this, for example.

Working With Functions

You can call the function by passing in a string. You might then pass the result of that call to print:

void main() {
  var fruit = 'apple';
  print(isBanana(fruit)); // false
}

Nesting Functions

Typically, you define functions either outside other functions or inside Dart classes. However, you can also nest Dart functions. For example, you can nest isBanana inside main.

void main() {
  bool isBanana(String fruit) {
    return fruit == 'banana';
  }

  var fruit = 'apple';
  print(isBanana(fruit)); // false
}

You can also change the argument you to a function, then call it again with the new argument:

fruit = 'banana';
print(isBanana(fruit));  // true

The result of calling the function depends entirely on the arguments you pass in.

Optional Parameters

If a parameter to a function is optional, you can surround it with square brackets and make the type nullable:

String fullName(String first, String last, [String? title]) {
  if (title == null) {
    return '$first $last';
  } else {
    return '$title $first $last';
  }
}

In this function, title is optional. It will default to null if you don’t specify it.

Now, you can call the function with or without the optional parameter:

print(fullName('Joe', 'Howard'));
// Joe Howard

print(fullName('Albert', 'Einstein', 'Professor'));
// Professor Albert Einstein

Named Parameters and Default Values

When you have multiple parameters, it can be confusing to remember which is which. Dart solves this problem with named parameters, which you get by surrounding the parameter list with curly brackets: { }.

These parameters are optional by default, but you can give them default values or make them required by using the required keyword:

bool withinTolerance({required int value, int min = 0, int max = 10}) {
  return min <= value && value <= max;
}

value is required, while min and max are optional with default values.

With named parameters, you can pass in arguments in a different order by supplying the parameter names with a colon:

print(withinTolerance(min: 1, max: 5, value: 11)); // false

You can leave off the parameters with default values when calling the function.

print(withinTolerance(value: 5)); // true

Run your code to see your new functions in action.

Named and default parameters

Anonymous Functions

Dart supports first-class functions, meaning that it treats functions like any other data type. You can assign them to variables, pass them as arguments and return them from other functions.

To pass these functions around as values, omit the function name and return type. Since there’s no name, this type of function is called an anonymous function.

Anonymous function

You can assign an anonymous function to a variable named onPressed, like so:

final onPressed = () {
  print('button pressed');
};

onPressed has a value of type Function. The empty parentheses indicate the function has no parameters. Like regular functions, the code inside the curly brackets is the function body.

To execute the code within the function body, call the variable name as if it were the name of the function:

onPressed(); // button pressed

You can simplify functions whose body only contains a single line by using arrow syntax. To do this, remove the curly brackets and add a fat arrow =>.

Here’s a comparison of the anonymous function above and a refactored version:

// original anonymous function
final onPressed = () {
  print('button pressed');
};

// refactored
final onPressed = () => print('button pressed');

That’s much nicer to read.

Using Anonymous Functions

You’ll often see anonymous functions in Flutter, like those above, passed around as callbacks for UI events. This lets you specify the code that runs when a user does something, like pressing a button.

Another common place where you’ll see anonymous functions is with collections. You can give a collection an anonymous function that will perform some task on each element of the collection. For example:

// 1
final drinks = ['water', 'juice', 'milk'];
// 2
final bigDrinks = drinks.map(
  // 3
  (drink) => drink.toUpperCase()
);
// 4
print(bigDrinks); // (WATER, JUICE, MILK)

Let’s look at each step:

  1. Define a list of drinks that have lowercase letters.
  2. .map takes all the list values and returns a new collection with them.
  3. An anonymous function is passed as a parameter. In that anonymous function, you have a drink argument that represents each element of the list.
  4. The body of the anonymous function converts each element to uppercase and returns the value. Since the original list is a list of strings, drink also has type String.

Using an anonymous function and combining it with .map is a convenient way to transform one collection into another.

Note: Don’t confuse the .map method with the Map type.

Run the code to see the resulting collection.

Anonymous functions

Congratulations, you’ve finished the tutorial. You should now have a better understanding of the Dart code you see when learning how to build Flutter apps!

Where to Go From Here?

You can download the code examples using the Download Materials button at the top or bottom of this tutorial.

If you’re ready to dive into Flutter, check out our Getting Started With Flutter tutorial.

For a more comprehensive look at Flutter, read our book, Flutter Apprentice.

You can go beyond the basics and learn more about Object-Oriented Programming and asynchronous programming with our book, Dart Apprentice.

Also, check out the official Dart documentation.

We hope you enjoyed this brief introduction to Dart basics. If you have any questions or comments, please join the forum discussion below.

Average Rating

4.3/5

Add a rating for this content

4 ratings

More like this

Contributors

Comments