Programming code on a laptop screen in a dark editor

Dart Classes for Beginners: Constructors Made Simple

You started with three loose variables. A title, an author, a page count, all sitting side by side, and every function you wrote had to take all three and hope you passed them in the right order. Then you needed a second book, so now you have six variables, and the whole thing is one typo away from a bug. This is the moment Dart classes earn their place. A class lets you bundle those three pieces into one thing called a Book, so you pass around a single object instead of juggling loose parts. By the end of this post you will be able to define a Dart class, create objects from it, write a constructor to set those objects up, use the this shorthand that shrinks your constructor to one line, and add named constructors for the different ways you build things.

What a Dart class actually is

A class is a blueprint. It says what every book has, a title, an author, and a page count, and once you write it you can stamp out as many actual books as you want from that one design. Here is the smallest Book class that does something useful.

class Book {
  String title;
  String author;
  int pages;

  Book(String title, String author, int pages) {
    this.title = title;
    this.author = author;
    this.pages = pages;
  }
}

void main() {
  var dune = Book('Dune', 'Frank Herbert', 412);
  print(dune.title);   // Dune
  print(dune.author);  // Frank Herbert
  print(dune.pages);   // 412
}

Read that from the bottom up. Down in main, Book('Dune', 'Frank Herbert', 412) builds one book and hands it back, and you store it in dune. Up top, the class lists three fields and then a constructor, the special function named exactly after the class, whose whole job is to take the values you passed and copy them onto the new object. So this.title = title means set this book’s title to the title that came in. After that, dune.title, dune.author, and dune.pages give you the pieces back. One thing beginners coming from Java or C# ask right away: no, you do not write new Book(...). Dart dropped new years ago, so Book(...) on its own builds the object. And you are not stuck with one. Call the constructor again with different values and you get a second, completely separate book that knows nothing about the first.

The error every Dart beginner sees first

Before the fun part, the wall almost everyone hits. You sit down, type out the fields, and Dart lights up red before you have written a single line of logic.

class Book {
  String title;   // Error: must be initialized
  int pages;      // Error: must be initialized
}

The message reads Non-nullable instance field 'title' must be initialized. Dart is not being difficult. Since null safety landed, a String is a promise that there is really a string there, not nothing. A field with no value and no way to get one breaks that promise, so Dart stops you at compile time instead of letting a null surprise you at runtime. You have three honest ways to fix it. Give the field a constructor that sets it, which is what we just did and usually what you want. Give it a default value right where you declare it, like String title = 'Untitled';. Or, if you truly cannot set it until later, mark it late and promise Dart you will assign it before anyone reads it. Most of the time the constructor is the right answer, because the whole reason you are building a book is that you already have a title and author to put in it. If null safety itself still feels fuzzy, the Dart null safety post walks through the nullable and late pieces slowly.

Developer workspace with a laptop showing program code
A class keeps a book’s title, author, and page count together in one object.

The this shorthand that shrinks your constructor

Look back at that first constructor body. Three lines that all say the same boring thing: take the parameter, put it on the field. Dart’s authors got tired of writing that too, so the language has a shorthand built for exactly this case.

class Book {
  String title;
  String author;
  int pages;

  Book(this.title, this.author, this.pages);
}

void main() {
  var dune = Book('Dune', 'Frank Herbert', 412);
  print('${dune.title} by ${dune.author}');  // Dune by Frank Herbert
}

That one line, Book(this.title, this.author, this.pages);, does everything the three-line body did. When you put this. in front of a constructor parameter, Dart takes the value that comes in and assigns it straight to that field for you. No body is needed, so the constructor ends in a semicolon instead of curly braces. The two versions compile to the same thing. Given the choice, and you almost always have the choice, take the short one. It is less to type, less to get wrong, and every Dart developer reads it at a glance. The one time you keep the longer body is when you need to do real work while building the object, like clamping a negative page count to zero or trimming whitespace off the title. Then you put this. on the parameters to handle the copying and add a body in curly braces for the extra work. For the plain copy-these-values case, which is most cases, the shorthand is the whole point.

Named constructors for different ways to build

Sometimes you build the same kind of object in more than one way. A book usually has an author, but say you are cataloging old manuscripts and half of them are anonymous. You do not want to type ‘Unknown’ by hand every single time, and you cannot write two constructors that are both named Book. Dart’s answer is the named constructor: a second constructor with a label after a dot.

class Book {
  String title;
  String author;
  int pages;

  Book(this.title, this.author, this.pages);

  Book.unknownAuthor(this.title, this.pages) : author = 'Unknown';

  @override
  String toString() => '$title by $author ($pages pages)';
}

void main() {
  var dune = Book('Dune', 'Frank Herbert', 412);
  var mystery = Book.unknownAuthor('Beowulf', 200);

  print(dune);     // Dune by Frank Herbert (412 pages)
  print(mystery);  // Beowulf by Unknown (200 pages)
}

Book.unknownAuthor('Beowulf', 200) reads almost like a sentence, and it is doing real work. The part after the colon, : author = 'Unknown', is an initializer list. It runs before the constructor body and sets author to a fixed value, while this.title and this.pages grab the two arguments you did pass. So you hand over a title and a page count, and the constructor fills in the author for you. You can add as many named constructors as you have ways of building the thing, Book.empty() for a placeholder, Book.fromJson(...) for data coming off the network, each one a different door into the same class. The toString method at the bottom is a bonus worth knowing. Dart calls it automatically when you print an object, so instead of the useless Instance of 'Book' you get a line you actually wrote. Override it once and every print(book) anywhere in your program reads cleanly. In CIS225 this is usually the point where classes click for students, not the syntax itself, but the moment they see that one class can hand them a book several different ways and still keep all the book-ness in one place.

Stack of books representing objects built from a Dart class
Each object you build from a class is one more book on the shelf.

Your next step

Here is the short version worth keeping. A Dart class bundles related data into one type, and a constructor is the function that fills a new object with real values. Non-nullable fields have to be set, so give them a constructor, a default value, or late. Use the this shorthand to copy parameters onto fields in a single line, and drop back to a full body only when you need to do actual work. Reach for named constructors when there is more than one sensible way to build the object, and override toString so your objects print like something you can read.

The way to make this stick is to build one. Write a Student class with a name, an id, and a grade, give it the shorthand constructor, then add a named constructor Student.newEnrollment that takes just a name and id and sets the grade to zero. Print one of each and watch the fields land where you put them. Once objects feel natural, the Dart functions post is worth a second look, since a constructor is really just a function with a special job, and the Dart sets post shows why your own classes eventually need their own == and hashCode. For the full reference, the Dart language guide on classes lists every kind of constructor Dart offers. Open your editor and stamp out a book.

Leave a Comment

Your email address will not be published. Required fields are marked *