Hide chapters

Dart Apprentice: Fundamentals

Dart Apprentice: Fundamentals

Section 1: 16 chapters
Show chapters Hide chapters

8. Classes
Written by Jonathan Sande

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

So far in this book, you’ve used built-in types such as int, String and bool. You’ve also seen one way to make custom types using enum. In this chapter, you’ll learn a more flexible way to create types by using classes.

Note: Because there’s quite a bit to learn about classes and object-oriented programming (OOP) in Dart, you’ll come back to the subject again in the following chapters as well as in the book Dart Apprentice: Beyond the Basics.

Classes are like architectural blueprints that tell the system how to make an object, where an object is the actual data stored in the computer’s memory. If a class is the blueprint, you could say the object is like the house the blueprint represents. For example, the String class describes its data as a collection of UTF-16 code units, but a String object is something concrete like 'Hello, Dart!'.

All values in Dart are objects that are built from a class. This includes the values of basic types like int, double and bool. That’s different from other languages like Java, where basic types are primitive. For example, if you have x = 10 in Java, the value of x is 10 itself. But Dart doesn’t have primitive types. Even for a simple int, the value is an object that wraps the integer. You’ll learn more about this concept later.

Classes are a core component of object-oriented programming. They’re used to combine data and functions inside a single structure.

MyClass { myProperty = ; MyClass(); myMethod() { print(myProperty); } } class var void 'Hello, Dart!' // constructor data functions functions data data

The functions exist to transform the data. Functions inside a class are known as methods, whereas constructors are special methods you use to create objects from the class. You’ll learn about properties and methods in this chapter and about constructors in Chapter 9, “Constructors”.

It’s time to get your hands dirty. Working with classes is far more instructive than reading about them!

Defining a Class

To start creating types, you’ll make a simple User class with id and name properties. This is just the kind of class you’re highly likely to create in the future for an app that requires users to log in.

Write the following simple class at the top level of your Dart file. Your class should be outside of the main function, either above or below it.

class User {
  int id = 0;
  String name = '';

This creates a class named User. It has two properties: id is an int with a default value of 0, and name is a String with the default value of an empty string.

The member variables of a class are generally called fields. But when the fields are public and visible to the outside world, you can also call them properties. The Encapsulation section below will show you how to simultaneously use public properties and private fields.

Note: Depending on the situation, null may be a better default than 0 or ''. But because nullable types and null safety won’t be fully covered until Chapter 11, “Nullability”, this chapter will simply use reasonable defaults for properties.

Creating an Object From a Class

As mentioned above, the value you create from a class is called an object. Another name for an object is instance, so creating an object is sometimes called instantiating a class.

final user = User();

The Optional Keyword New

Before version 2.0 of Dart came out, you had to use the new keyword to create an object from a class. At that time, creating a new instance of a class would have looked like this:

final user = new User();

Assigning Values to Properties

Now that you have an instance of User stored in user, you can assign new values to this object’s properties using dot notation. To access the name property, type user dot name and then give it a value: = 'Ray'; = 42;
void main() {
  final user = User(); = 'Ray'; = 42;

class User {
  int id = 0;
  String name = '';

Printing an Object

You can print any object in Dart. But if you try to print user now, you don’t get quite what you hoped for. Add the following line at the bottom of the main function and run the code:

Instance of 'User'
String toString() {
  return 'User(id: $id, name: $name)';
User(id: 42, name: Ray)

Adding Methods

Now that you’ve learned to override methods, you’ll move on and add your methods to the User class. But before you do, there’s a little background information that you should know.

Understanding Object Serialization

Organizing related data into a class is super useful, especially when you want to pass that data around as a unit within your app. One disadvantage, though, shows up when you’re saving the object or sending it over the network. Files, databases and networks only know how to handle simple data types, such as numbers and strings. They don’t know how to handle anything more complex, like your User data type.

Adding a JSON Serialization Method

You’re going to add another method to your class now that will convert a User object to JSON format. It’ll be like what you did in toString.

String toJson() {
  return '{"id":$id,"name":"$name"}';

Cascade Notation

When you created your User object above, you set its parameters like so:

final user = User(); = 'Ray'; = 42;
final user = User() = 'Ray' = 42;

Objects as References

Objects act as references to the instances of the class in memory. That means if you assign one object to another, the other object simply holds a reference to the same object in memory — not a new instance.

class MyClass {
  var myProperty = 1;
final myObject = MyClass();
final anotherObject = myObject;
print(myObject.myProperty);    // 1
anotherObject.myProperty = 2;
print(myObject.myProperty);    // 2


One of the core tenets of object-oriented programming is known as encapsulation. This is the principle of hiding the internal data and logic in a class from the outside world. Doing so has a few benefits:

Hiding the Internals

You can make a variable private in Dart by prefixing the name with an underscore.

class Password {
  String _plainText = 'pass123';


A getter is a special method that returns the value of a private field variable. It’s the public face of a private variable. If you’ve done any Java or C++ programming, you’ve probably seen getter methods with names like getColor or getWidth. Following this naming conversion, your Dart class would look like so:

class Password {
  String _plainText = 'pass123';

  String getPlainText() {
    return _plainText;
final myPassword = Password();
final text = myPassword.getPlainText();
class Password {
  String _plainText = 'pass123';

  String get plainText => _plainText;
final myPassword = Password();
final text = myPassword.plainText;
print(text); // pass123

Getters Don’t Set

Try adding the following line at the bottom of main:

myPassword.plainText = '123456';
There isn’t a setter named 'plainText' in class 'Password'.
Try correcting the name to reference an existing setter or declare the setter.

Calculated Properties

You can also create getters that aren’t backed by a dedicated field value but are calculated when called.

String get obfuscated {
  final length = _plainText.length;
  return '*' * length;
final myPassword = Password();
final text = myPassword.obfuscated;


Use a setter if you want to change the internal data in a class, Dart has a special set keyword for this to go along with get.

set plainText(String text) => _plainText = text;
final myPassword = Password();
myPassword.plainText = r'Pa$$vv0Rd';
final text = myPassword.plainText;
print(text); // Pa$$vv0Rd

Using Setters for Data Validation

Replace the plainText setter that you wrote above with the following version:

set plainText(String text) {
  if (text.length < 6) {
    print('Passwords must have 6 or more characters!');
  _plainText = text;
final shortPassword = Password();
shortPassword.plainText = 'aaa';
final result = shortPassword.plainText;
Passwords must have 6 or more characters!

No Need to Overuse Getters And Setters

You don’t always need to use getters and setters explicitly. If all you’re doing is shadowing some internal field variable, you’re better off just using a public variable.

class Email {
  String _value = '';

  String get value => _value;
  set value(String value) => _value = value;
class Email {
  String value = '';
final email = Email();
email.value = '';
final emailString = email.value;


Before moving on, here’s a challenge to test your knowledge of classes. It’s best if you try to solve it yourself, but a solution is available with the supplementary materials for this book if you get stuck.

Challenge 1: Rectangles

  • Create a class named Rectangle with properties for _width and _height.
  • Add getters named width and height.
  • Add setters for these properties that ensure you can’t give negative values.
  • Add a getter for a calculated property named area that returns the area of the rectangle.

Key Points

  • Classes package data and functions inside a single structure.
  • Variables in a class are called fields, and public fields or getter methods are called properties.
  • Functions in a class are called methods.
  • You can customize how an object is printed by overriding the toString method.
  • Classes have getters and setters, which you can customize without affecting how the object is used.

Where to Go From Here?

This chapter touched briefly on JSON as a standard way to serialize objects. You’ll certainly be using JSON in the future, so you can visit to learn more about this format and why it’s gained so much traction as a standard.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2023 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Professional subscription.

Unlock now