Object-Orientated Language Features
Modules
Unlike Java and C#, Dart allows you to declare multiple objects within a single Dart file.
This has made our example code a single cut-n-paste!
Private Classes, Variables & Methods
Unlike Java, Dart doesn't have the keywords public, protected, and private to specify the visibilities of fields or properties. If a class name, instance variable or method starts with an underscore, it's private and cannot be accessed outside the Dart file in which it is declared.
You should replace:
class ContactInfo {
private String name;
private String phone;
}
with
class ContactInfo {
String _name;
String _phone;
}
Constructors
Default Constructor
If you do not specify a constructor, a default constructor will be created for you without arguments. If you do specify a constructor, the default constructor won’t be created for you.
Constructor Syntax Shortcut
If you want to set the value of an instance variable in a constructor, you can use the ‘this.[instance variable name]’ to set it in the constructor signature.
Example Code
class Name{
String firstName;
String lastName;
Name(this.firstName, this.lastName);
}
main(){
Name name = new Name('mark','smith');
print(name.firstName);
print(name.lastName);
}
Output
mark
smith
New Keyword
Dart doesn’t need you to use the ‘new’ keyword when invoking constructors. However, you can keep it if you want.
Example Code
void main() {
Car car = Car("BMW","M3");
print(car.getBadge());
Car car2 = new Car("BMW","M3");
print(car2.getBadge());
}
class Car{
String _make;
String _model;
Car(this._make, this._model){}
String getBadge(){
return _make + " - " + _model;
}
}
Output
BMW - M3
BMW - M3
Named Constructors
Dart allows named constructors and I have found them very useful indeed if you want to instantiate the same class in different ways. Named constructors (if named correctly) can also improve code readability & intent.
Example
A good example of a Flutter class that uses multiple named constructors is EdgeInsets:
- fromLTRB
- all
- only
- symmetric
- fromWindowPadding
Example Code
class ProcessingResult{
bool _error;
String _errorMessage;
ProcessingResult.success(){
_error = false;
_errorMessage = '';
}
ProcessingResult.failure(this._errorMessage){ //shortcut
this._error = true;
}
String toString(){
return 'Error: ' + _error.toString() + ' Message: ' + _errorMessage;
}
}
void main() {
print(ProcessingResult.success().toString());
print(ProcessingResult.failure('it broke').toString());
}
Output
Error: false Message:
Error: true Message: it broke
Constructor Parameters
Constructors can accept different kinds of parameters, similar to methods.
Factory Constructors
You can use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class. The factory keyword allows you to return a variable at the end of the constructor. This is useful when you want the constructor to return an instance from a variable or a cache.
Example Code
class Printer{
static final Printer _singleton = Printer._construct();
factory Printer(){
return _singleton;
}
Printer._construct(){
print('private constructor');
}
printSomething(String text){
print(text);
}
}
void main() {
Printer().printSomething("this");
Printer().printSomething("and");
Printer().printSomething("that");
}
Output
Note how the constructor was only invoked once.
private constructor
this
and
that
Instance Variables
Unspecified Visibility
You don’t have to specify the visibility of instance variables and if you don’t then they are made public.
class Name {
String firstName;
String lastName;
}
Default Values
The default values of instance variables are null.
Constructor and Method Parameters
Flutter is very flexible in regard to constructor & method parameters. There are several different kinds:
- Positional Required
- Positional Optional
- Named
1. Parameters - Positional Required
These are declared first.
These are required.
Constructor with required parameters:
class Car{
String _make;
String _model;
Car(this._make,this._model){}
}
2. Parameters - Positional Optional
These are declared second. You can make parameters optional, by using the square brackets. If an optional parameter is not supplied, it has a null value.Example Code
void main() {
Car car1 = Car("Nissan","350Z");
Car car2 = Car("Nissan");
}
class Car{
String _make;
String _model;
Car(this._make,[this._model]){
print('${_make} ${_model}');
}
}
Output
Nissan 350Z
Nissan null
3. Parameters - Named
All named parameters are optional.These are declared last.
You can make parameters named, by using the curly brackets.
If a named parameter is not supplied, it has a null value.
Example Code
void main() {
Car car1 = Car("Nissan", model:"350Z", color: "yellow");
Car car2 = Car("Nissan", color:"red");
Car car3 = Car("Nissan");
}
class Car{
String make;
String model;
String color;
Car(this.make,{this.model,this.color}){
print('${make}${getOptional(model)}${getOptional(color)}');
}
String getOptional(String str) {
return str == null ? "" : " " + str;
}
}
Output
Nissan 350Z yellow
Nissan red
Nissan
Required Decorator
You can add the ‘@required’ decorator to named parameters to make them required.
This is not a part of Dart, but it is part of Flutter. Therefore, it won’t work with Dartpad.
Example Code
We define a constructor for SelectButton that requires both ‘text’ and ‘onTap’ named parameters.
SelectButton({@required this.text, @required this.onTap});
If you declare a named parameter as ‘@required’ and the developer writes code that does not supply that parameter:
SelectButton(text: "YES"),
then the following compilation error occurs:
warning: The parameter 'onTap' is required. (missing_required_param at [yes_no] lib/main.dart:58)
Interfaces
Dart uses implicit interfaces.
Example Code
abstract class IsSilly {
void makePeopleLaugh();
}
class Clown implements IsSilly {
void makePeopleLaugh() {
// Here is where the magic happens
}
}
class Comedian implements IsSilly {
void makePeopleLaugh() {
// Here is where the magic happens
}
}
Further Reading
https://www.dartlang.org/guides/language/language-tour - implicit-interfaces
Constants vs Finals
You can use either to tell Dart that the variable value is not going to change. However there are some subtle differences between the two:
const
This sets the variables value at compile time. Its value cannot be changed at run time. If you declare multiple constants with the same value, Dart ends up internally using the same object for all. Objects inside a const cannot be changed. For example, if you have a const collection, everything in it must also be const, recursively.
Use constants for variables who’s value you know at the time of writing the code & that will never change.
final
This sets the variables value at run time. Its value can be assigned once. A final variable or field must have an initializer. Once assigned a value, a final variable's value cannot be changed afterwards.
Objects inside a final can possibly be changed. For example, if you have a final field containing a collection, that collection can still be mutable.
Use finals for variables who’s value you know at the time of initialization & that will never change once set.
Other
Method Cascades
Method cascades can help with the brevity of your code.
Example Code
class Logger {
void log(dynamic v){
print(DateTime.now().toString() + ' ' + v);
}
}
main(){
// Without method cascades
new Logger().log('program started');
new Logger().log('doing something');
new Logger().log('program finished');
// With method cascades
new Logger()
..log('program started')
..log('going something')
..log('program finished');
}
Output
2018-12-30 09:28:39.686 program started
2018-12-30 09:28:39.686 doing something
2018-12-30 09:28:39.686 program finished
2018-12-30 09:28:39.686 program started
2018-12-30 09:28:39.686 going something
2018-12-30 09:28:39.686 program finished
toString
You can use the ‘toString’ method to convert an object to its string representation. This is like other languages.
However, Dart also gives you some other similar convenience methods:
- toStringAsFixed – converts object to double then outputs double with specified number of digits after decimal point. Quite useful!
- toStringAsPrecision – converts object to double then outputs double with specified precision.