DART 3.0 Updates
Dart 3.0 is a major update to the Dart programming language that introduces several new features and improvements.
1-RECORDS:
Records are a new type of object in Dart 3.0 that are similar to structs in other languages. They allow you to define a fixed set of fields and methods that can be used to represent data in a structured way.
Example: Let’s consider you want to return multiple values from a single Function.
void main(){
final data = studentData();
print(data[0]);
print(data[1]);
}
List<String> studentData(){
return ['Queen', "24", "4.5"];
}
if you want to return [‘Queen’, 24, 4.5] then it will give you error that this has to be a List<dynamic> because I am returning three differnet types of data, if we run this I will get studentData,
but this has some notable issues:
- we don’t know this data varibale data type at compile time.
- The studentData function is less expressive, that means we don’t know whats being return from this function. we just know the List is return.
void main(){
final data = studentData();
print(data[0]);
print(data[1]);
}
List<dynamic> studentData(){
return ['Queen', 24, 4.5];
}
For this case, Dart Introduce RECORDS:
To use records,
- remove this list<dynamic> to this (String, int, double), mention return data types.
- return the same thing to [‘Queen’, 24, 4.5] this (‘Queen’, 24, 4.5)
- and do like this to print each value print(data.$1); print(data.$2);
Now we know the type of values here.
void main(){
final data = studentData();
print(data.$1);
print(data.$2);
}
(String, int, double)studentData(){
return ('Queen', 24, 4.5);
}
// WE CAN DO LIKE THIS FOR BETTER UNDERSTANDING
void main(){
final (string, int, double) = studentData();
print(string);
print(int);
print(double);
}
(String, int, double)studentData(){
return ('Queen', 24, 4.5);
}
You can also give name to their type like this
void main(){
final data = studentData();
print(data.name);
print(data.age);
print(data.cgpa);
}
({String name, int age, double cgpa})studentData(){
return (name: 'Queen',age: 24,cgpa: 4.5);
}
To create Records:
void main(){
final records = ("hey", 34, 66.8);
print(records);
}
// ACCESS THEM USING $1, $2
void main(){
final records = ("hey", 34, 66.8);
print(records.$1);
}
this $1,$2,$3 are getter, but Records are immutable, we only have getter not setter. Also we can set name parameters like below.
void main(){
final records = (message: "hey", 34, percentgae: 66.8);
print(records.$1);
print(records.message);
print(records.percentgae);
}
Records can be nullable:
void main(){
(String, int)? data = ('Queen', 24);
print(data); //OUTPUT: ('Queen', 24)
data = null;
print(data); //OUTPUT: null
}
Summary:
Records are real values, you can store them in a variable, pass them to and from functions and store them in a list.
2-PATTERNS:
Patterns Matching are a new way to match values in Dart that is similar to switch statements in other languages. They allow you to match values based on their type, value, or structure.
Suppose, we have a list numbers which carries 5 values, to get access to these value we do like this below:
void main(){
final numbers = [1,2,3,4,5];
final a= numbers[0];
print(a);
}
with patterns we can do like this just immitating this numbers list
void main(){
final numbers = [1,2,3,4,5];
final [a,b,c,d,e] = numbers;
print('$a $b $c $d $e');
}
//OUTPUT: 1 2 3 4 5
if we have more values, [1,2,3,4,5,6,7,8,9,0] and immitate like [a,b,c,d,e] we will get an error. so in that case we can do like this below:
void main(){
final numbers = [1,2,3,4,5,6,7,8,9,0];
final [a,b,c,d,e,...] = numbers;
print('$a $b $c $d $e');
}
If you want to access all values, these three dots gaave access to remaining values.
void main(){
final numbers = [1,2,3,4,5,6,7,8,9,0];
final [a,b,c,d,e,...j] = numbers;
print('$a $b $c $d $e $j');
}
if you don’t want to access any value like if I don’t want 2nd index value, in that case put _ so it will skip those values:
void main(){
final numbers = [1,2,3,4,5,6,7,8,9,0];
final [a,b,c,_,_,...j] = numbers;
print('$a $b $c $j');
}
Same as follow with the JSON RESPONSE:
void main(){
final data = {
'id': 1,
'name': 'Queen',
'age': 24,
'CGPA': 3.4
};
final {'id': id, 'name': name} = data;
print(id);
print(name);
}
Records + Pattern Matching:
void main(){
var (a,b) = (1,2); //records
print((a,b));
(a,b) = (b,a); //swap
}
//OUTPUT: (1,2)
//OUTPUT: (2,1)
3-CLASS MODIFIERS:
Dart 3.0 introduces several new class modifiers that allow you to control how classes are used and extended.
I have 3 classes and one is abstract calss Vehicles. I created an instances of Toyota(); then added switch statement in which the case will be Honda(); when you run this code you will get nothing, neither error nor result. That is expected because I am not handling the case of Toyota();
void main() {
//object
Vehicles vehicle = Toyota();
switch (vehicle){
case Honda():
print('Honda');
}
}
abstract class Vehicles{}
class Toyota extends Vehicles{}
class Honda implements Vehicles{}
class Civic implements Vehicles{}
1)Sealed Modifier:
I want whenever I put vehicle , i want all the subtypes of vehicle class, so to do that dart 3 introduce a modifier called sealed. Sealed Class is kind of abstract class, it is not instantiated. Sealed classes prevent other classes from extending or implementing outside of the same library.
void main() {
//object
Vehicles vehicle = Toyota();
switch (vehicle){
case Honda():
print('Honda');
case Toyota():
print('Honda');
case Civic():
print('Honda');
}
//OUTPUT: Toyota
}
sealed class Vehicles{}
class Toyota extends Vehicles{}
class Honda implements Vehicles{}
class Civic implements Vehicles{}
2)Final Modifier:
It is similar to sealed class, it cannot be implemented or extended outside of the same library, but inside the same library it can be extended or implemented. The difference between sealed and final modifiers is Vehicle class cannot be instantiated but Aimal class can be instantiated
void main(){
Vehicle(); //error
Animal();
}
sealed class Vehicle{}
final class Animal {}
class Cat extends Animal {} //error
//it need to be final, sealed or base class
final class Cat extends Animal {}
3)Base Modifier:
Cannot impelemented but can be extended outside of the library.
void main(){
Vehicle(); //error
Animal();
}
sealed class Vehicle{}
final class Animal {}
base class Fruits {}
base class Apple extends Fruits{}
when I call the instance of Apple class, the constructor of the base class Fruits gets called. same as with final class.
4) Interface Modifier:
Reverse of base class, base class could only be extended, interface cass cloud only be implmented. so even if you are outside of the library yu can implemented a class. It is also be instantiated
void main(){
Vehicle(); //error
Animal(); //can be instantiated
Human(); //can be instantiated
}
sealed class Vehicle{}
final class Animal {}
base class Fruits {}
interface class Human {}
You can do it like this, but you cannot instantiated it now
void main(){
Vehicle(); //ERROR
Animal(); //can be instantiated
Human(); //can be instantiated
Vegetables(); //ERROR
}
sealed class Vehicle{}
final class Animal {}
base class Fruits {}
interface class Human {}
abstract interface class Vegetables{}
5) Mixin Modifier:
In dart 2:
class Animal{}
class Human with Animal {}
But in DART 3.0
mixin class Animal{}
class Human with Animal {}
Normal class cannot be mixed in now, you have to add a mixin keyword.
4-SWITCH CASE & IF CASE STATEMENT:
In Dart 3.0, switch statements have been modified to support pattern matching. A pattern matching statement takes a value that it matches against and has a series of pairs of patterns and bodies. At runtime, the first pattern that matches the value is selected, and the corresponding body is executed. This unlocks exhaustive pattern matching, which means that you will get a compile-time error when you forget to match a subtype. Using switch, you will get a compile-time error in Dart 3 informing you when you forget to handle a subtype (since the compiler knows all the possible cases).
SWITCH CASE:
No need to put break; because it is optional in dart 3. but for empty statements you need to put break; otherwise it will run default.
void main(){
final data = {
'id': 1,
'name': 'Queen',
'age': 24,
'CGPA': 3.4
};
switch(data){
case {'id': int id, 'name': String name}:
print(id);
print(name);
default:
print('ERROR');
}
}
You can check either or in switch cases:
void main(){
List<String> data = ['FLUTTER', 'queen'];
switch(data){
case ['Flutter' || 'FLUTTER', 'queen' || 'QUEEN']:
print('wow!');
}
}
You can also put when guard to switch case like this below:
void main(){
List<String> data = ['FLUTTER', 'queen'];
String channel = 'subcribe';
switch(data){
case ['Flutter' || 'FLUTTER', 'queen' || 'QUEEN'] when channel == 'subcribe':
print('DOne!');
}
you can add switch case to your Flutter UI to perform some conditional actions:
class MyWidget extends StatelessWidget {
int loginPage=0;
int registerPage=1;
int homePage =2;
@override
Widget build(BuildContext context) {
return Text(
switch(loginPage){
0 => 'Login Please!';
1 => 'Register Please!';
2 => 'Welcome';
_ => 'Error';
},
style: Theme.of(context).textTheme.headlineMedium,
);
}
}
IF CASE STATEMENT:
void main(){
final data = {
'id': 1,
'name': 'Queen',
'age': 24,
'CGPA': 3.4
};
if(data case {'id': int id, 'name': String name}){
print(id);
print(name);
} else{
print('ERROR');
}
}
I hope this article was helpful to you. Thank you for taking the time to read it. Your feedback and suggestions are always welcome.