1
2
|
String text = 'initial';
final list = [1,2,3];
|
constructor with initial value of fields
1
2
3
4
5
6
7
8
9
10
11
|
void main(List<String> arguments) {
MyClass myClass = MyClass('initial');
//"new" keyword is redundant
}
class MyClass {
String someField;
//constructor
//this refers to the current instance itself
MyClass(this.someField);
}
|
constructor with named parameters
1
2
3
4
5
6
7
8
|
void main(List<String> arguments) {
MyClass myClass = MyClass(someField: 'initial');
}
class MyClass {
String someField;
MyClass({required this.someField});
}
|
constructor with lazy initialization
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void main(List<String> arguments) {
MyClass myClass = MyClass(firstName: 'Cherry', lastName: 'Lu');
}
class MyClass {
//promise to initiate the variable later
late String name;
MyClass({required String firstName,
required String lastName}){
name = '$firstName $lastName';
}
}
|
★constructor with a initializer list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
void main(List<String> arguments) {
MyClass myClass = MyClass(firstName: 'Cherry', lastName: 'Lu');
}
class MyClass {
String name;
//initializer list is a , separated list of
//expressions that can access constructor
//parameters and can assign to instance fields,
//even final instance fields.
MyClass({required String firstName,
required String lastName}) : name = '$firstName $lastName';
}
|
★const constructor
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void main(List<String> arguments) {
MyClass myClass1 = const MyClass('Cherry');
MyClass myClass2 = const MyClass('Cherry');
//const constructor make sure the object can be instantiated only once
//so the result will be true.
print(myClass1 == myClass2);
}
class MyClass {
//fields referred from a const constructor must be final
final String name;
const MyClass(this.name);
}
|
Nothing special~
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void main(List<String> arguments) {
MyClass myClass = MyClass('Cherry');
print(myClass.getName());
}
class MyClass {
String name;
MyClass(this.name);
bool getName() {
return name.contains('C');
}
}
|
1
2
3
4
5
6
7
8
9
|
void main(List<String> arguments) {
int myAge = MyClass.age;
MyClass.method();
}
class MyClass {
static const age = 20;//static field
static void method(){ }//static method
}
|
_
means private access modifier
- No class private fields but only package private fields in dart
- files are packages
- use
import
to import other files(packages)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
void main(List<String> arguments) {
MyClass(1,2);
final myClass = MyClass.namedConstructor(public: 1, private: 2);
myClass._private; //This is fine! There's no class private fields!
}
class MyClass {
int public;
int _private;
MyClass(this.public, this._private);
MyClass.namedConstructor({ //multiple constructors
required this.public, //named parameters
required int private,
}) : _private = private;
//★have to initiate here because
//named parameter can't start with '_'
}
|
1
2
3
4
|
class NonInstantiable {
//unaccessible from outside of the class
NonInstantiable._();
NonInstantiable._namedConstructor();
|
1
|
void _privateMethod(){ }
|
- Properties are like some nice-looking methods which are doing lighter work than normal methods.
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
27
28
29
30
31
32
33
34
35
36
|
void main(List<String> arguments) {
final user = User(firstName: 'Cherry', lastName: 'Lu', email: 'c@l.com');
final user2 = User(firstName: 'Cherry', lastName: 'Lu', email: 'whdgsuf');
print(user.fullName + ' ' + user.email); //Cherry Lu c@l.com
print(user2.fullName + ' ' + user2.email); //Cherry Lu Invalid Email
}
class User {
final String firstName;
final String lastName;
String? _email; //nullable & private
User({
required this.firstName,
required this.lastName,
required String email,
}) {
//can't use initializer list
//because this.email is not a actual field but a property
//'this.email' refers to the 'set' property
this.email = email;
}
String get fullName => '$firstName $lastName';
//'??' means if null
String get email => _email ?? 'Invalid Email';
set email(String value) {
if (value.contains('@')) {
_email = value;
} else {
_email = null;
}
}
}
|
- Referential Equality: E.g. Objects that are instantiated with a constant constructor will be referentially equal to each other [link: const constructor].
- Value Equality: Defined by overriding
equality
(the behavior of equal operator ==
), depending on if the specific fields are equal.
cmd+.
->generate equality
use extension dart data class generator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@override
bool operator ==(Object other) {
//this: operator on the left side, other: right side
//identical: check referential equality
if (identical(this, other)) return true;
return other is User && //type check
other.firstName == firstName && //fields check
other.lastName == lastName;
}
//for quick look up on map
@override
int get hashCode => firstName.hashCode ^ lastName.hashCode;
|
- In dart, every single class extends
Object
- Extending a class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class User {
final String _firstName;
final String _lastName;
User(
this._firstName,this._lastName,
);
String get fullName => '$_firstName $_lastName';
void signOut() {print('Signing out...');}
}
//Need to declaring a zero argument constructor in 'User',
//or declaring a constructor in Admin that explicitly invokes a constructor in 'User'
class Admin extends User {
//As long as the super call is correct,
//it'll be fine to do anything with the sub constructor
Admin(String firstName, String lastName) : super(firstName, lastName);
...
}
|
- Overriding properties
1
2
|
@override
String get fullName => 'Admin: ${super.fullName}';
|
- Overriding methods
1
2
3
4
5
|
@override
void signOut() {
print('Performing admin-specific sign out steps');
super.signOut(); //how to be precautious at missing the super call??
}
|
- ★Guarantee super method to be called
- pubspec.yaml: add library
meta
to regular dependencies
- Super Class: add annotation
@mustCallSuper
to the super method
1
2
|
@mustCallSuper
void signOut() {print('Signing out...');}
|
- ★PolyMorphism
- Use
as
to cast child type to its super type.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
void main(List<String> arguments) {
final admin = Admin('Cherry', 'Lu');
//By casting, the special fields defined only in Admin class
//can not be accessed anymore.
final user = admin as User;
//But, call the common methods will still invoke the one in the child class.
print(user.fullName);//Admin: Cherry Lu
print(user is Admin);//true
print(user is! Admin);//false
if (user is Admin) {
//Special fields defined only in Admin class will be accessible here!!!!
}
}
|
- Dart has a
factory
keyword for factory constructors
- Factory is able to return current/subclass instance
- Normal constructor is only able to return an instance of the current class
1
2
3
4
5
6
7
8
9
|
//inside of the previous User class
factory User.admin() {
return Admin('Cherry', 'Lu');
}
...
//call from the main function
void main(List<String> arguments) {
final user = User.admin();
}
|
- Use
abstract
keyword to prevent a class from being instantiated.
- Abstract class is allowed to have constructors.
- An abstract class cannot be instantiated directly(with
new
), but can be instantiated from its subclasses.
- Abstract classes can have abstract methods.
- Once an abstract class has any abstract methods or properties, its subclasses must override all of them.
- In dart, any kinds of classes can be implemented as an interface. Therefore there's no such a
Interface
keyword.
- By
implementing
a regular class, we are actually implementing the implicit interface --the overall class members without implementations, of the class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class User {
final String _name;
User(this._name);
String get upperName => _name.toUpperCase();
void loggedIn() {
print('Logged in.');
}
}
//Ignore the original implementations and always need to provide new ones
class Admin implements User {
@override
// TODO: implement _name
// Every fields must have a getter
String get _name => throw UnimplementedError();
@override
void loggedIn() {
// TODO: implement loggedIn
}
@override
// TODO: implement upperName
String get upperName => throw UnimplementedError();
}
|
- The implementation classes can always be cast into the 'interface class'
1
|
Admin() as User;//unnecessary
|
- Although implementing a regular class is allowed, one should always implement an abstract class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
abstract class DataReader<T> {//generics in class
T readData(); //generic return type
void myMethod(T arg); //generic arguments
}
class IntegerDataReader implements DataReader<int> {
@override
int readData() {
return 1234;
}
@override
void myMethod(int arg) {
print(arg);
}
}
|
- Dart does not support multiple inheritance.
- Mixin type of class allows other classes to access it's members by using the 'with' keyword, without building an 'is-a' inheritance relationship with that class.
- Mixin type is like it allows copying of its members.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
mixin Action {
void run() {
print("I'm running");
}
}
class LivingThing {}
class Thing {}
//Both animals and roberts can run
//An animal is a living thing but not an action
class Animal extends LivingThing with Action {}
//A robert is a thing but not an action
class Robert extends Thing with Action {
@override
void run() {
print("I'm fully charged!");
super.run(); //Even override and super works here
}
}
|
Call method:
1
2
3
4
5
6
7
|
void main(List<String> arguments) {
final rabbit = new Animal();
final alphaRun = new Robert();
rabbit.run(); //I'm running
alphaRun.run(); //I'm fully charged!¥nI'm running
}
|
- Extension methods helps us to add functionalities to the classes that we don't own
extends method:
1
2
3
4
5
6
7
8
9
10
|
void main(List<String> arguments) {
final x = 'hello'.duplicate();
print(x);//hellohello
}
//Extension on String class
extension StringDuplication on String {
String duplicate() {
return this + this; //this = current instance of String
}
}
|
extends property
1
2
3
4
5
6
7
8
|
void main(List<String> arguments) {
final x = 'hello'.duplicated;
print(x);//hellohello
}
extension StringDuplication on String {
String get duplicated => this + this;
}
|
- Use
part
keyword to link package private classes from different files(packages).
file1.dart: has a part named file2:
1
2
3
4
5
|
part 'file2.dart'
//...
class _privatePackageClass {
//...
}
|
file2.dart: is a part of file1.dart
now it's possible to access file1's private class from file2.
part of
must be the only directive in a part
- In many cases, directly changing values of instance fields is not allowed.
- Generating
copyWith
method to make a copy of the original instance for safe updating.
- Simply generate
DataClass
or only copyWith
method by pressing cmd + .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@immutable
class User {
final String name;
const User({
required this.name,
});
User copyWith({
String? name, //could be null(no updating)
}) {
return User(
name: name ?? this.name, //if null keep the original value
);
}
}
|
output:
1
2
3
4
5
6
7
8
|
void main(List<String> arguments) {
User user1 = const User(name: 'Cherry');
User userUpdated = user1.copyWith(name: 'Lu');
print(user1.name); //Cherry
print(userUpdated.name); //Lu
print(user1 == userUpdated); //false
}
|
- Freezed:
Code generation for immutable classes that has a simple syntax/API without compromising on the features. (make 3-7-1 code more readable?)
- Dependencies
- dev_dependencies: freezed, build_runner
- dependencies: freezed_annotation
- How to Use
1
2
3
4
5
6
7
8
9
|
@freezed //annotation
class User with _$User {
//only called by freezed generated class
const User._();
const factory User({
String? name, //nullable
}) = _User; // redirect to the generated class
}
|
- Designate a
part
to hold the generated class
1
|
part 'target_file_name.dart';
|
1
|
dart run build_runner build --delete-conflicting-outputs
|
1
2
3
4
5
6
7
8
9
10
11
|
void main(List<String> arguments) {
const userOrigin = User(name: 'Cherry');
final userCopy = userOrigin.copyWith(name: 'CC');
final userNull = userOrigin.copyWith(name: null);
print('userOrigin: ${userOrigin.name}');//Cherry
print('userCopy: ${userCopy.name}');//CC
print('userNull: ${userNull.name}');//null
print('Are they the same? : ${userOrigin == userCopy}');//false
}
|
- ★Create snippets
- Press
F1
=> Choose Configure User Snippets
=> Choose dart.json
- Define snippets like
fdataclass
for create a data class
- Original Union: Checking what the type of an object actually is may not be exhaustive
1
2
3
4
5
6
|
//what-already-know: object is an instance of superClass which is extended by subClass1, subClass2...
if (object is subClass1) {
//
} else if (object is subClass2) {
//...
} //there could be subClass3,4...and we just don't know them yet
|
- Freezed Union: Has the previous exhausted checks built-in for multiple extended classes.
- How to Use
- Create the target class(snippet:
funion
)
1
2
3
4
5
6
7
8
|
@freezed //annotation
class Result with _$Result {
const Result._();
//In the case of Union , there are always multiple classes so every factory for them must a name
//otherwise it is going to create a DataClass
const factory Result.success(int value) = _Success;//an union case
const factory Result.failure(String errorMessage) = _Failure;
}
|
- Designate a
part
to hold the generated class
1
|
part 'target_file_name.dart';
|
1
|
dart run build_runner build --delete-conflicting-outputs
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
void main(List<String> arguments) {
//This result is actually returned by our api so
//we don't know what exactly it is.
const actualResult = Result.success(200);
//'when' clause help us to exhaustively check every single
//possible type of the result.
print(actualResult.when(success: (value) {
return 'Successfully called API. Response code is: $value';
}, failure: (errorMessage) {
return 'An error occurred. ErrorMessage: $errorMessage';
}));
//console output: Successfully called API. Response code is: 200
}
|
- Use
maybeWhen
to ignore certain cases
1
2
3
4
5
|
print(actualResult.maybeWhen(
orElse: () => '', //when it's not a Failure do this
failure: (errorMessage) {
return 'An error occurred. ErrorMessage: $errorMessage';
}));
|
- Use
map
/maybeMap
to pass whole generated objects to the functions
1
2
3
4
5
6
7
8
9
10
|
//TResult map<TResult extends Object?>({required TResult Function
//(_Success) success, required TResult Function(_Failure) failure})
print(actualResult.map(
success: (_success) { //
return 'Successfully called API. Response code is: ${_success.value}';
}, failure: (_failure) {
return 'An error occurred. ErrorMessage: ${_failure.errorMessage}';
},
),
);
|
- try-catch-finally
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//catch all
void main(List<String> arguments) {
try {
final myInt = int.parse('abcdefg');
} catch (e) {
print(e);
} finally {
//always runs
}
}
//partially catch
void main(List<String> arguments) {
try {
final myInt = int.parse('abcdefg');
} on FormatException catch (e) {
print(e);
//the 'catch (e)' part can be removed if there's nothing to do here
}
}
|
- dart can throw anything as an exception to crash the app but don't do this!
1
2
3
4
5
6
7
|
...
throw 'a fake exception' //don't do this!
throw MyCustomException; //correct.
...
class MyCustomException implements Exception { }
class MyCustomError extends Error { }
|
- By doing async calls within an method, the method itself becomes asynchronous as well, which must return an
Future
object as its result.
- How to use
- async-await (recommended)
1
2
3
4
5
6
7
8
9
10
11
|
//need 'http' library
Future<void> main(List<String> arguments) async {
try {
final result = await Client().get(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
);
print(result.body);
} catch(e) {
//..
}
}
|
1
2
3
4
5
6
7
8
|
void main(List<String> arguments) {
Client()
.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
)
.then((response) => print(response.body))
.catchError((e) => print(e));
}
|
- listen to periodic stream
1
2
3
4
5
6
7
8
9
|
Future<void> main(List<String> arguments) async {
final periodStream = Stream.periodic(const Duration(seconds: 1));
final subscription = periodStream.listen((event) {
print('A second has passed');
});
await Future.delayed(const Duration(seconds: 3));
subscription.cancel(); //await for 3s to before cancel
}
|
- Stream Generators and Operators
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
Future<void> main(List<String> arguments) async {
createMessageStream().listen((event) => print(event));
//console output:
//Message1...
//Message2...
//Message3...
}
//async* is an async generator, which returns Streams
//Streams are like one level above Future cause
//Future handles an one time async event but Streams handles multiple ones
Stream<String> createMessageStream() async* {
//cannot use return because it terminates the method.
//yield is able to return something without terminating the execution
yield 'Message1...';//should match the return type String
await Future.delayed(const Duration(seconds: 1));
yield 'Message2...';
await Future.delayed(const Duration(seconds: 1));
yield 'Message3...';
await Future.delayed(const Duration(seconds: 1));
}
|
- Stream is like a collection of async objects that alive in the streams over a period of time.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//1.use map on it(just like using it on a collection)
createMessageStream()
.map((message) => message.toUpperCase())
.listen((event) => print(event));
// then the previous out put will become to:
//MESSAGE1...
//MESSAGE2...
//MESSAGE3...
//2.use filter on it
createMessageStream()
.map((message) => message.toUpperCase())
.where((message) => message.contains('1'))
.listen((event) => print(event));
//only Message1... will be printed!
|