Dart Patterns and Exceptions
# Patterns and Exceptions
Patterns are a new Dart feature that includes the ability of destructuring. Patterns were added in version 3.0 of Dart. Patterns are a syntactic category in the Dart language, like statements and expressions. A pattern represents the shape of a set of values that it may match against actual values.
In general, a pattern may match a value, destructure a value, or both, depending on the context and shape of the pattern.
First, pattern matching allows you to check whether a given value:
- Has a certain shape.
- Is a certain constant.
- Is equal to something else.
- Has a certain type.
Then, pattern destructuring provides you with a convenient declarative syntax to break that value into its constituent parts. The same pattern can also let you bind variables to some or all of those parts in the process.
# Pattern Matching and Destructuring
Patterns can be used for matching. A common example would be with a switch-case statement. You can match against a constant pattern
, which is what you are used to doing by providing a single value.
switch(number) {
case 1:
print('matches the integer 1');
}
2
3
4
There are also logical patterns
with &&
or ||
.
switch (color) {
Color.red || Color.blue || Color.yellow => true,
_ => false
};
2
3
4
You can match a List too:
List<String?> row = ['user', null];
switch (row) {
case ['user', var name!]: // ...
// 'name' is a non-nullable string here.
}
2
3
4
5
For a full list of Pattern types see this reference
# Destructuring
Destructuring works in a similar way to how it works in JavaScript. Part of the difference is how datatypes are usually added.
Here is an example with a variable, of type record
, where its contents are typed as num
and Object
. Then, with destructuring, a pattern is matched along with the ability to cast the variables to
int
and String
. The num
becomes an int
and the Object
becomes a String
. The types have to make sense, and become more specific.
(num, Object) record = (1, 's');
var (i as int, s as String) = record;
2
You can destructure a List just like you would destructure an Array in JavaScript.
var numList = [1, 2, 3, 4, 5];
// List pattern [a, b, c] destructures the elements from numList...
// and the underscores each mean to skip and not accept a value
var [a, _, _, d, e] = numList;
2
3
4
You can destructure in a for..in loop.
Map<String, int> hist = {
'a': 23,
'b': 100,
};
//destructure a Map
for (var MapEntry(key: key, value: count) in hist.entries) {
print('$key occurred $count times');
}
2
3
4
5
6
7
8
Object patterns match against named object types, allowing you to destructure their data using the getters the object’s class already exposes.
To destructure an instance of a class, use the named type, followed by the properties to destructure enclosed in parentheses:
final Foo myFoo = Foo(one: 'one', two: 2);
var Foo(:one, :two) = myFoo;
print('one $one, two $two');
2
3
Map and list patterns work well for destructuring key-value pairs in JSON data:
var json = {
'user': ['Lily', 13]
};
var {'user': [name, age]} = json;
2
3
4
# Exceptions and Errors
Dart provides Exception
and Error
types, as well as numerous predefined subtypes. You can, of course, define your own exceptions. However, Dart programs can throw any non-null object—not just
Exception
and Error
objects—as an exception. Exceptions are errors indicating that something unexpected happened.
The Error
class creates Error objects thrown in the case of a program failure. An Error object represents a program failure that the programmer should have avoided. Examples include calling a
function with invalid arguments, or even with the wrong number of arguments, or calling it at a time when it is not allowed. These are not errors that a caller should expect or catch — if they occur,
the program is erroneous, and terminating the program may be the safest response.
The Exception
class creates objects that are intended to convey information to the user about a failure, so that the error can be addressed programmatically. It is intended to be caught, and it
should contain useful data fields. There is the base Exception
class, but additionally there are more specific types including FormatException
, IOException
, TimeoutException
, and
NullRejectionException
.
You can create your own classes that implements Exception
so you have custom exceptions that pertain to your app.
class FellDownException implements Exception {
FellDownException(String message);
}
2
3
# Try Catch Throw
Just like in JavaScript there is a throw
keyword that you can use to trigger an error state which will pause the current thread (or isolate
) and, if you have a try catch
be handled by it.
try {
//run some code attempt here
throw 'A basic error message';
}on FormatException {
//handle any FormatException
}on Exception catch(ex){
//handle a general Exception
}catch(ex, st){
//handle anything else that was not handled yet
print('Exception details: \n$ex');
print('Stack trace: \n$st')
}finally{
//run this code after the try plus any triggered `on` or `catch`
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Unlike Javascript, Dart can create a chain of Exception handling statements. Each specific Exception will use the on
keyword.
You can also use on Exception catch()
to handle any general exception that was not specifically mentioned in the preceding on
statements.
The simple catch()
statement after on Exception catch()
handles any exception not previously caught, as well as, anything else that was thrown (String, Object, etc).
At the very end you can add a finally
statement. This is a grouping of code that runs whether the code in try worked or failed.