Dart Interface

Summary: in this tutorial, you’ll learn about the Dart interface and how to define and implement an interface.

Introduction to the Dart interface

An interface is a contract between classes. Unlike other languages, Dart doesn’t have the interface keyword. Instead, all classes are implicit interfaces.

In practice, you often define an abstract class as an interface and implement the interface using other concrete classes.

For example, the following defines an abstract class called Logger that has one method log():

abstract class Logger {
  void log(String message);
}Code language: Dart (dart)

To implement an interface, you use the implements keyword. For example, the following defines the ConsoleLogger class that implements the Logger interface:

class ConsoleLogger implements Logger {
  @override
  void log(String message) {
    print('Log "$message" to the console.');
  }
}Code language: Dart (dart)

When implementing an interface, you need to provide implementations for all methods that the interface has. In this example, the ConsoleLogger provides an implementation for the log() method.

Similarly, the following example defines the FileLogger class that implements the Logger interface:

class FileLogger implements Logger {
  @override
  void log(Pattern message) {
    print('Log "$message" to a file.');
  }
}Code language: Dart (dart)

For simplicity, the log() method of the FileLogger class prints out the message instead of writing it to a file.

Now, both ConsoleLogger and FileLogger classes implement the Logger interface. It means that you can swap them in the application easily.

For example, you can have a class called App that uses the Logger interface. At runtime, you can select whether to use the ConsoleLogger or FileLogger to log a message:

class App {
  Logger? logger;
  App({this.logger});

  void run() {
    logger?.log("App is starting...");
  }
}Code language: Dart (dart)

The following main() function creates a new instance of the App class and passes an instance of the FileLogger to its constructor. In practice, you will use a configuration file and select the Logger type to use, either FileLogger or ConsoleLogger.

In addition, the App class does not depend on any concrete class but the Logger interface. It means that you can define a new Logger type like DatabaseLogger and plug it in without modifying the existing code base.

Put it all together.

abstract class Logger {
  void log(String message);
}

class ConsoleLogger implements Logger {
  @override
  void log(String message) {
    print('Log "$message" to the console.');
  }
}

class FileLogger implements Logger {
  @override
  void log(Pattern message) {
    print('Log "$message" to a file.');
  }
}

class App {
  Logger? logger;
  App({this.logger});

  void run() {
    logger?.log("App is starting...");
  }
}

void main() {
  // can use configuration file to select
  // kind of loggger to use
  var app = App(logger: FileLogger());
  app.run();
}Code language: Dart (dart)

Implementing multiple interfaces

Dart supports only single inheritance. It means that a class can extend a single class. It doesn’t support multiple inheritance, in which a class can extend multiple classes.

However, a class can implement multiple interfaces. Therefore, a class can extend a single class and implements multiple interfaces.

The following example illustrates how to define a class that implements multiple interfaces:

import 'dart:io';

abstract class Reader {
  String? read();
}

abstract class Writter {
  void write(String message);
}

class Console implements Reader, Writter {
  @override
  String? read() {
    print("Enter a string:");
    return stdin.readLineSync();
  }

  @override
  void write(String message) {
    print(message);
  }
}

void main() {
  var console = Console();
  String? input = console.read();
  if (input != null) {
    console.write(input);
  }
}Code language: Dart (dart)

How it works.

First, import a standard package dart:io:

import 'dart:io';Code language: Dart (dart)

Second, define two abstract classes Reader and Writer that acts as interfaces:

abstract class Reader {
  String? read();
}

abstract class Writter {
  void write(String message);
}Code language: Dart (dart)

Third, define the Console class that implements both Reader and Writer interfaces:

class Console implements Reader, Writter {
  @override
  String? read() {
    print("Enter a string:");
    return stdin.readLineSync();
  }

  @override
  void write(String message) {
    print(message);
  }
}Code language: Dart (dart)

To implement multiple interfaces, you specify a comma-separated list of interfaces after the implements keyword.

The read() method uses the sdin.readLineSync() from the dart:io package to prompt users for entering a string in the console.

Finally, use the Console class in the main() function:

void main() {
  var console = Console();
  String? input = console.read();
  if (input != null) {
    console.write(input);
  }
}Code language: Dart (dart)

Summary

  • An interface is a contract between classes.
  • Dart has no interface keyword. Instead, all classes are implicit interfaces.
  • Use an abstract class as an interface.
  • A class can implement multiple interfaces but only can extend a single class.
Was this tutorial helpful ?