Welcome to the first article in the Dart and Flutter series. Today’s article covers the main differences between the extends
, implements
, and mixin
keywords. We are going to analyze each of them from a technical point of view and then, at the end of the article, you will find a summary table with the principal differences among them. Let’s get started!
Dart is a garbage-collected, OOP, client-optimized language for creating fast apps for any platform. If you are familiar with an object oriented programming language such as Java, C++, C# or Delphi for example, you might find many similarities with Dart. If you want to get started with the development, make sure to check out these sources:
Official Dart website. From here, you will be able to download and install the Dart SDK. At the time of writing this article, the version on the stable
channel is 2.13.1.
Official Flutter website. From here, you will be able to download and install Dart SDK and Flutter together (the most chosen option).
For demo purposes, you can even use Dart without downloading it! DartPad is an open-source tool that lets you write and compile Dart code in any modern browser. Give it a try while reading this article. Before moving on, I’d like to point out why Flutter is backed by Dart:
OOP style: The vast majority of developers have object-oriented programming skills and thus Dart is easy to learn as it shares most of the common OOP patterns. The developer doesn’t have to face a completely new way of coding; he can reuse what he already knows and integrate it with the specific details of Dart.
Performance: In order to guarantee high performance and avoid frame dropping during the execution of the app, there’s the need for a high performance and predictable language. Dart has a powerful memory allocator for short-lived allocations which is perfect for Flutter’s functional-style flow.
Productivity: Flutter allows developers to write Android, iOS, web, and desktop apps with a single codebase that has the same performance, aspect and feeling in each platform.
Google: Both Flutter and Dart are developed by Google which can freely decide what to do with them by listening to the community. If Dart was developed by another company, very likely Google couldn’t evolve the language at the right pace and it wouldn’t have the same freedom of choice in implementing new features.
If you are willing to learn Dart and Flutter, we strongly recommend you read the official Dart Tour guide or, if you’re a paper book person, you can check out the Flutter Complete Reference book.
As in any OOP language, an abstract class cannot be directly instantiated but any of its subtypes can. In Dart, you can use the abstract
keyword only on classes because abstract methods simply don’t have a body. Let’s check out this example together:
1
2
3
4
5
6
7
8
9
10
11
/// This is an abstract class.
abstract class Example {
/// This is an abstract method because it has no body.
void methodOne();
/// This is NOT an abstract method because it has a body.
void methodTwo() {}
}
/// This must be an abstract class too!
abstract class AnotherExample extends Example {}
The AnotherExample
class is abstract too because it doesn’t override all of the abstract methods of the parent. When you want to override one or more methods, simply use the @override
annotation and make sure to use the same signature. Note that in Dart you can also override non-abstract methods but this is considered a bad practice; only abstract methods should be overridden.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// This is an abstract class.
abstract class Example {
/// This is an abstract method because it has no body.
void methodOne();
/// This is an abstract getter.
int get calculate;
/// This is NOT an abstract method because it has a body.
void methodTwo() {}
}
/// This is a concrete class
class AnotherExample extends Example {
@override
void methodOne() {
print('hello!');
}
@override
int get calculate => 1;
}
Getters and setters can be overridden as well. The important thing to keep in mind is that you don’t need to override all of the methods in the superclass when using extends
.
In contrast to other programming languages, Dart doesn’t have an interface
keyword and you have to use classes to create interfaces. Any class is allowed to implement one or more interfaces.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// This is an abstract class but we treat it as an "Interface" because we're going to use
/// 'implements' on this type (rather than 'extends').
abstract class Example {
void methodOne();
void methodTwo() {}
int get calculate;
}
/// This is a concrete class.
class AnotherExample implements Example {
@override
void methodOne() {
print('hello1!');
}
@override
void methodTwo() {
print('hello 2!');
}
@override
int get calculate => 1;
}
When using implements
, you must override every method declared in the superclass. With extends
you could instead override zero or more methods (so you’re not forced to redefine them all). You could also use a regular class as an interface:
1
2
3
4
5
6
7
8
class Example {
void method() => print('Hi');
}
class AnotherExample implements Example {
@override
void method() => print('Hello');
}
Again, since you’re using implements
, you must override all of the superclass methods (even if they weren’t abstract and they had a body). To avoid confusion and keep the code clean, you should always use abstract classes as interfaces. While Dart doesn’t support multiple inheritances it supports multiple interfaces:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class InterfaceOne {
void one();
}
abstract class InterfaceTwo {
void two();
}
class Example implements InterfaceOne, InterfaceTwo {
@override
void one() {}
@override
void two() {}
}
For the sake of completeness, here’s how the above code could have been rewritten in the following, identical way:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class InterfaceOne {
void one() {}
}
class InterfaceTwo {
void two() {}
}
class Example implements InterfaceOne, InterfaceTwo {
@override
void one() {}
@override
void two() {}
}
The only difference is that the classes we’re using as interfaces don’t have the abstract
keyword so one()
and two()
must have a body (it’s empty, but it’s still there). This is not very useful and sometimes it can be misleading on large codebases so try to always use abstract classes when you want to create interfaces.
A mixin is a sort of class that can be “associated” to another class in order to reuse pieces of code without using inheritance. It requires the with
keyword:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mixin Breathing {
void swim() => print("Breathing");
}
mixin Walking {
void walk() => print("Walking");
}
mixin Coding {
void code() => print("print('Hello world!')");
}
/// This class now has the `walk()` method
class Human with Walking {}
/// This class now has the `walk()` and `code()` methods
class Developer with Walking, Coding {}
A class can have an infinite number of mixins. Once you assign a mixin
to a class, it automatically gets access to all of the methods declared within that mixin:
As you can see, those two classes don’t define methods but they still have walk()
and/or code()
because they’re “imported” from the mixins. Generally, a mixin
is very useful when classes with different logics or inheritance models have identical methods that you don’t want to copy/paste. Here’s a simple example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/// A model class that defines some statistics of a football team.
abstract class FootballTeam {
Strategy freeKickStrategy();
int get offSidesCount;
double ballVolume(double radius) {
return 4 / 3 * 3.14 * pow(radius, 3);
}
}
class Team1 extends FootballTeam {}
class Team2 extends FootballTeam {}
// A Flutter widget that displays info of a volleyball pitch.
class VolleyballPitch extends StatelessWidget {
const VolleyballPitch();
double ballVolume(double radius) {
return 4 / 3 * 3.14 * pow(radius, 3);
}
@override
Widget build(BuildContext context) {
// code...
}
}
Here we have two completely different types of classes with no common logic: they only share a single method. Using inheritance wouldn’t work well because a Flutter widget has nothing in common with the football model class. In these cases, the right thing to do is to create a mixin
that provides code sharing with no inheritance.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mixin BallUtils {
double ballVolume(double radius) {
return 4 / 3 * 3.14 * pow(radius, 3);
}
}
/// A model class that defines some statistics of a football team.
abstract class FootballTeam with BallUtils {
Strategy freeKickStrategy();
int get offSidesCount;
}
class Team1 extends FootballTeam {}
class Team2 extends FootballTeam {}
// A Flutter widget that displays info of a volleyball pitch.
class VolleyballPitch extends StatelessWidget with BallUtils {
const VolleyballPitch();
@override
Widget build(BuildContext context) {
// code...
}
}
Tadaa! We have reused a piece of code without inheritance. If you wanted you could also restrict mixins to be applied only on certain types using the on
keyword:
1
2
3
4
5
6
7
8
9
10
11
12
13
mixin BallUtils on Widget {
// code...
}
/// This code DOESN'T work because 'FootballTeam' is not a subtype of 'Widget'
abstract class FootballTeam with BallUtils {
// code...
}
// This works because 'StatelessWidget' is a subtype of 'Widget'
class VolleyballPitch extends StatelessWidget with BallUtils {
// code...
}
Mixins are very useful when you want to avoid code duplication in the same project without using inheritance (which might be logically wrong or just impossible to use).
Congratulations, you’ve made it to the end! That was a quite dense read and you might be more comfortable with a summary table with the key differences between extends
, implements
, and with
. Here it is:
This is the typical OOP inheritance that can be used when you want to add new features in a subclass.
When you use class B extends A {}
you are NOT forced to override every method of class A. Inheritance takes place so you can override as many methods as you want.
You can use extends
only on classes (Dart supports single inheritance).
Interfaces are useful when you don’t want to provide an implementation of methods but just their API. It’s like if the interface was a wall socket and the class was the plug to insert.
When you use class B implements A {}
you must override every method of class A. Inheritance does NOT take place because methods just provide an API and not a concrete implementation.
You can use implements
with one or more classes.
Mixins are useful when you need code sharing without using inheritance.
When you use class B with A {}
you are importing every method of mixin A
into your class B
. Optionally, the usage of a mixin can be constrained to a certain type using the on
keyword.
You can use with
with one or more mixins.
I hope you’ve enjoyed the reading up to now and… this is just the beginning. In the next article we will talk about networking with Dart and Flutter, a very fundamental topic that every developer is often asked to handle. Make sure not to miss it because we’ll cover the following topics:
how networking works in Flutter;
how to use the http
and dio
packages.
The official Dart Tour guide
CHECK OUT TOPCODER FLUTTER FREELANCE GIGS