Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

Advanced Techniques and Reflection

Save for later
  • 1620 min read
  • 2014-12-02 00:00:00

article-image

 

target


//

Page 1
advanced-techniques-and-reflection-img-0
Advanced Techniques
and Reflection
In this chapter, we will discuss the flexibility and reusability of your code with the
help of advanced techniques in Dart. Generic programming is widely useful and is
about making your code type-unaware. Using types and generics makes your code
safer and allows you to detect bugs early. The debate over errors versus exceptions
splits developers into two sides. Which side to choose? It doesn't matter if you know
the secret of using both. Annotation is another advanced technique used to decorate
existing classes at runtime to change their behavior. Annotations can help reduce
the amount of boilerplate code to write your applications. And last but not least, we
will open Pandora's box through mirrors of reflection. In this chapter, we will cover
the following topics:
Generics
Errors versus exceptions
Annotations
Reflection
Generics
Dart originally came with generics—a facility of generic programming. We have
to tell the static analyzer the permitted type of a collection so it can inform us at
compile time if we insert a wrong type of object. As a result, programs become
clearer and safer to use. We will discuss how to effectively use generics and

Your browser does not support the canvas tag!

minimize the complications associated with them.


Page 2
Advanced Techniques and Reflection

Your browser does not support the canvas tag!

Raw types
Dart supports arrays in the form of the List class. Let's say you use a list to store
data. The data that you put in the list depends on the context of your code. The list
may contain different types of data at the same time, as shown in the following code:
// List of data
List raw = [1, "Letter",
{'test':'wrong'}]; // Ordinary item
double item = 1.23;
void main() {
// Add the item to
array raw.add(item);
print(raw);
}
In the preceding code, we assigned data of different types to the raw
list. When the code executes, we get the following result:
[1, Letter, {test: wrong}, 1.23]
So what's the problem with this code? There is no problem. In our code, we
intentionally used the default raw list class in order to store items of different types.
But such situations are very rare. Usually, we keep data of a specific type in a list.
How can we prevent inserting the wrong data type into the list? One way is to
check the data type each time we read or write data to the list, as shown in the
following code:
// Array of String data
List parts = ['wheel', 'bumper',
'engine']; // Ordinary item
double item = 1.23;
void main() {
if (item is String) {
// Add the item to
array parts.add(item);
}
print(parts);
}

Your browser does not support the canvas tag!

[ 2 ]


Page 3
Chapter 2

Your browser does not support the canvas tag!

Now, from the following result, we can see that the code is safer and works
as expected:
[wheel, bumper, engine]
The code becomes more complicated with those extra conditional statements.
What should you do when you add the wrong type in the list and it throws
exceptions? What if you forget to insert an extra conditional statement? This is
where generics come to the fore.
Instead of writing a lot of type checks and class casts when manipulating a collection,
we tell the static analyzer what type of object the list is allowed to contain. Here is the
modified code, where we specify that parts can only contain strings:
// Array of String data
List<String> parts = ['wheel', 'bumper',
'engine']; // Ordinary item
double item = 1.23;
void main() {
// Add the item to
array parts.add(item);
print(parts);
}
Now, List is a generic class with the String parameter. Dart Editor invokes the
static analyzer to check the types in the code for potential problems at compile time
and alert us if we try to insert a wrong type of object in our collection, as shown in
the following screenshot:

Your browser does not support the canvas tag!

[ 3 ]


Page 4
Advanced Techniques and Reflection

Your browser does not support the canvas tag!

This helps us make the code clearer and safer because the static analyzer checks
the type of the collection at compile time. The important point is that you shouldn't
use raw types. As a bonus, we can use a whole bunch of shorthand methods to
organize iteration through the list of items to cast safer. Bear in mind that the static
analyzer only warns about potential problems and doesn't generate any errors.

advanced-techniques-and-reflection-img-1

Dart checks the types of generic classes only in the check mode.

advanced-techniques-and-reflection-img-2

Execution in the production mode or code compiled to JavaScript

advanced-techniques-and-reflection-img-3

loses all the type information.
Using generics
Let's discuss how to make the transition to using generics in our code with some
real-world examples. Assume that we have the following AssemblyLine class:
part of assembly.room;
// AssemblyLine.
class AssemblyLine {
// List
of
items
on
line. List _items = [];
// Add
[item]
to
line.
add(item) {
_items.add(item);
}
// Make
operation
on
all
items
in
line. make(operation) {
_items.forEach((item)
{
operation(item);
});
}
}
Also, we have a set of different kinds of cars, as shown in the following code:
part of assembly.room;
// Car
abstract class Car
{ // Color

Your browser does not support the canvas tag!

[ 4 ]


Page 5
Chapter 2

Your browser does not support the canvas tag!

String color;
}
// Passenger car
class PassengerCar extends Car {
String toString() => "Passenger Car";
}
// Truck
class Truck extends Car { String
toString() => "Truck";
}
Finally, we have the following assembly.room library with a main method:
library assembly.room;
part 'assembly_line.dart';
part 'car.dart';
operation(car) {
print('Operate ${car}');
}
main() {
// Create passenger assembly line
AssemblyLine passengerCarAssembly = new AssemblyLine();
// We can add passenger car
passengerCarAssembly.add(new PassengerCar());
// We
can
occasionally
add
Truck
as
well
passengerCarAssembly.add(new Truck());
// Operate
passengerCarAssembly.make(operation);
}
In the preceding example, we were able to add the occasional truck in the
assembly line for passenger cars without any problem to get the following result:
Operate Passenger Car
Operate Truck
This seems a bit farfetched since in real life, we can't assemble passenger cars
and trucks in the same assembly line. So to make your solution safer, you need to
make theAssemblyLine type generic.

Your browser does not support the canvas tag!

[ 5 ]


Page 6
example
be
of
bounded
Advanced Techniques and Reflection

Your browser does not support the canvas tag!

Generic types
In general, it's not difficult to make a type generic. Consider the following
theAssemblyLine class:
part of assembly.room;
// AssemblyLine.
class AssemblyLine <E extends Car> {
// List
of
items
on
List<E> _items = [];
// Add [item] to line.
add(E item) {
_items.insert(0, item);
}
// Make
operation
on
all
items
line. make(operation) {
_items.forEach((E
{ operation(item);
});
}
}
In the preceding code, we added one type parameter, E, in the declaration of the
AssemblyLine class. In this case, the type parameter requires the original one to
subtype of Car. This allows the AssemblyLine implementation to take advantage
without the need for casting a class. The type parameter E is known as a
type parameter. Any changes to the assembly.room library will look like this:
library assembly.room;
part 'assembly_line.dart';
part 'car.dart';
operation(car) {
print('Operate ${car}');
}
main() {
// Create passenger assembly line
[ 6 ]
of
line.
in
item)
a

Your browser does not support the canvas tag!

Car


Page 7
Chapter 2

Your browser does not support the canvas tag!

AssemblyLine<PassengerCar> passengerCarAssembly
= new AssemblyLine<PassengerCar>();
// We can add passenger car
passengerCarAssembly.add(new PassengerCar());
// We
can
occasionally
add
truck
as
well
passengerCarAssembly.add(new Truck());
// Operate
passengerCarAssembly.make(operation);
}
The static analyzer alerts us at compile time if we try to insert the Truck argument in
the assembly line for passenger cars, as shown in the following screenshot:

advanced-techniques-and-reflection-img-4

After we fix the code in line 17, all looks good. Our assembly line is now safe. But if
you look at the operation function, it is totally different for passenger cars than it is
for trucks; this means that we must make the operation generic as well. The static
analyzer doesn't show any warnings and, even worse, we cannot make the operation
generic directly because Dart doesn't support generics for functions. But there is
a solution.

Your browser does not support the canvas tag!

[ 7 ]


Page 8
Operation
Function
the
Operation<Truck>("paint");
Advanced Techniques and Reflection

Your browser does not support the canvas tag!

Generic functions
Functions, like all other data types in Dart, are objects, and they
data type Function. In the following code, we will create an
as an implementation of Function and then apply generics to it as usual:
part of assembly.room;
// Operation for specific type of car
class Operation<E extends Car> implements Function {
// Operation
final String name;
// Create
new
operation
[name] Operation(this.name);
// We
call
our
here call(E car) {
print('Make ${name} on ${car}');
}
}
The gem in our class is the call method. As Operation implements
has acall method, we can pass an instance of our class as a function in
method of the assembly line, as shown in the following code:
library assembly.room;
part 'assembly.dart';
part 'car.dart';
part 'operation.dart';
main() {
// Paint
operation
for
passenger
Operation<PassengerCar> paint = new
Operation<PassengerCar>("paint");
// Paint operation for Trucks
Operation<Truck> paintTruck = new
// Create passenger assembly line
Assembly<PassengerCar> passengerCarAssembly =
new Assembly<PassengerCar>();
// We can add passenger car
passengerCarAssembly.add(new PassengerCar());
// Operate
only
with
passenger
passengerCarAssembly.make(paint);
// Operate with mistake
passengerCarAssembly.make(paintTruck);
}
[ 8 ]
have the
class
name
with
function
and
make
car

Your browser does not support the canvas tag!

car


Page 9
Chapter 2

Your browser does not support the canvas tag!

In the preceding code, we created the paint operation to paint the passenger
cars and thepaintTruck operation to paint trucks. Later, we created the
passengerCarAssembly line and added a new passenger car to the line via the add
method. We can run the paint operation on the passenger car by calling the make
method of the passengerCarAssembly line. Next, we intentionally made a mistake
and tried to paint the truck on the assembly line for passenger cars, which resulted in
the following runtime exception:
Make paint on Passenger Car
Unhandled exception:
type 'PassengerCar' is not a subtype of type 'Truck' of 'car'.
#0 Operation.call (…/generics_operation.dart:10:10)
#1 Assembly.make.<anonymous
closure>(…/generics_assembly.dart:16:15
)
#2 List.forEach (dart:core-patch/growable_array.dart:240)
#3 Assembly.make (…/generics_assembly.dart:15:18)
#4 main (…/generics_assembly_and_operation_room.dart:20:28)
This trick with the call method of the Function type helps you make all the aspects
of your assembly line generic. We've seen how to make a class generic and function
to make the code of our application safer and cleaner.

advanced-techniques-and-reflection-img-5

The documentation generator automatically adds information about

advanced-techniques-and-reflection-img-6

generics in the generated documentation pages.
To understand the differences between errors and exceptions, let's move on to
the next topic.
Errors versus exceptions
Runtime faults can and do occur during the execution of a Dart program. We can
split all faults into two types:
Errors
Exceptions

Your browser does not support the canvas tag!

[ 9 ]


Page 10
Advanced Techniques and Reflection

Your browser does not support the canvas tag!

There is always some confusion on deciding when to use each kind of fault, but you
will be given several general rules to make your life a bit easier. All your decisions
will be based on the simple principle of recoverability. If your code generates a fault
that can reasonably be recovered from, use exceptions. Conversely, if the code
generates a fault that cannot be recovered from, or where continuing the execution
would do more harm, use errors.
Let's take a look at each of them in detail.
Errors
An error occurs if your code has programming errors that should be fixed by the
programmer. Let's take a look at the following main function:
main() {
// Fixed length list List
list = new List(5);
// Fill list with values
for (int i = 0; i < 10; i++)
{ list[i] = i;
}
print('Result is ${list}');
}
We created an instance of the List class with a fixed length and then tried to fill it
with values in a loop with more items than the fixed size of the List class. Executing
the preceding code generates RangeError, as shown in the following screenshot:

advanced-techniques-and-reflection-img-7

This error occurred because we performed a precondition violation in our code
when we tried to insert a value in the list at an index outside the valid range.
Mostly, these types of failures occur when the contract between the code and the
calling API is broken. In our case, RangeError indicates that the precondition was
violated. There are a whole bunch of errors in the Dart SDK such as CastError,
RangeError, NoSuchMethodError, UnsupportedError, OutOfMemoryError, and
StackOverflowError. Also, there are many others that you will find in the errors.
dart file as a part of the dart.core library. All error classes inherit from the Error
class and can return stack trace information to help find the bug quickly. In the
preceding example, the error happened in line 6 of the main method in the
range_error.dart file.

Your browser does not support the canvas tag!

[ 10 ]


Page 11
Chapter 2

Your browser does not support the canvas tag!

We can catch errors in our code, but because the code was badly implemented,
we should rather fix it. Errors are not designed to be caught, but we can throw
them if a critical situation occurs. A Dart program should usually terminate when
an error occurs.
Exceptions
Exceptions, unlike errors, are meant to be caught and usually carry information
about the failure, but they don't include the stack trace information. Exceptions
happen in recoverable situations and don't stop the execution of a program. You can
throw any non-null object as an exception, but it is better to create a new exception
class that implements the marker interface Exception and overrides the toString
method of the Object class in order to deliver additional information. An exception
should be handled in a catch clause or made to propagate outwards. The following is
an example of code without the use of exceptions:
import 'dart:io';
main() {
// File URI
Uri uri = new
Uri.file("test.json"); // Check uri
if (uri != null) { //
Create the file
File file = new File.fromUri(uri);
// Check whether file exists
if (file.existsSync()) {
// Open file
RandomAccessFile random = file.openSync();
// Check random
if (random != null) {
// Read file
List<int> notReadyContent =
random.readSync(random.lengthSync());
// Check not ready content
if (notReadyContent != null) {
// Convert
to
String
String content = new
String.fromCharCodes(notReadyContent);
// Print results
print('File content: ${content}');
}
// Close file
random.closeSync();
}

Your browser does not support the canvas tag!

[ 11 ]


Page 12
Advanced
Here
As
code.
making
and
exceptions
Print
Techniques and Reflection

Your browser does not support the canvas tag!

} else {
print ("File doesn't exist");
}
}
}
is the result of this code execution:
File content: [{ name: Test, length: 100 }]
you can see, the error detection and handling leads to
Worse yet, the logical flow of the code has been lost,
understand it. So, we transform our code to use
import 'dart:io';
main() {
RandomAccessFile
random; try {
// File URI
Uri uri = new Uri.file("test.json");
// Create the file
File file = new
File.fromUri(uri); // Open file
random = file.openSync();
// Read file
List<int> notReadyContent =
random.readSync(random.lengthSync());
// Convert to String
String content = new
String.fromCharCodes(notReadyContent); //
print('File content: ${content}');
} on ArgumentError catch(ex) {
print('Argument error exception');
} on UnsupportedError catch(ex) {
print('URI cannot reference a file');
} on FileSystemException catch(ex) {
print
("File
doesn't
accessible"); } finally {
try {
random.closeSync();
}
on
FileSystemException
print("File can't be close");
}
}
}
[ 12 ]
a confusing spaghetti
it difficult to read
as follows:
results
exist
or

Your browser does not support the canvas tag!

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
catch(ex)
{


Page 13
Chapter 2

Your browser does not support the canvas tag!

The code in the finally statement will always be executed independent of
whether the exception happened or not to close the random file. Finally, we
have a clear separation of exception handling from the working code and we
can now propagate uncaught exceptions outwards in the call stack.
The suggestions based on recoverability after exceptions are fragile. In our
example, we caught ArgumentError and UnsupportError in common with
FileSystemException. This was only done to show that errors and
exceptions have the same nature and can be caught any time. So, what is the
truth? While developing my own framework, I used the following principle:
If I believe the code cannot recover, I use an error, and if I think it can
recover, I use an exception.
Let's discuss another advanced technique that has become very popular and that
helps you change the behavior of the code without making any changes to it.
Annotations
An annotation is metadata—data about data. An annotation is a way to keep
additional information about the code in the code itself. An annotation can have
parameter values to pass specific information about an annotated member. An
annotation without parameters is called a marker annotation. The purpose of a
marker annotation is just to mark the annotated member.
Dart annotations are constant expressions beginning with the @ character. We
can apply annotations to all the members of the Dart language, excluding
comments and annotations themselves. Annotations can be:
Interpreted statically by parsing the program and evaluating the constants
via a suitable interpreter
Retrieved via reflection at runtime by a framework

advanced-techniques-and-reflection-img-8

The documentation generator does not add annotations to the

advanced-techniques-and-reflection-img-9

generated documentation pages automatically, so the information
about annotations must be specified separately in comments.

Your browser does not support the canvas tag!

[ 13 ]


Page 14
Advanced Techniques and Reflection

Your browser does not support the canvas tag!

Built-in annotations
There are several built-in annotations defined in the Dart SDK interpreted by the
static analyzer. Let's take a look at them.
Deprecated
The first built-in annotation is deprecated, which is very useful when you need to mark
a function, variable, a method of a class, or even a whole class as deprecated and that it
should no longer be used. The static analyzer generates a warning whenever a marked
statement is used in code, as shown in the following screenshot:

advanced-techniques-and-reflection-img-10

Override
Another built-in annotation is override. This annotation informs the static analyzer
that any instance member, such as a method, getter, or setter, is meant to override
the member of a superclass with the same name. The class instance variables as
well as static members never override each other. If an instance member marked
with override fails to correctly override a member in one of its superclasses, the
static analyzer generates the following warning:

Your browser does not support the canvas tag!

[ 14 ]


Page 15
Let's
Proxy
The last annotation is proxy. Proxy is a well-known pattern used when
call a real class's methods through the instance of another class.
that we have the following Car class:
part of cars;
// Class Car
class Car {
int _speed = 0;
// The car speed
int get speed => _speed;
// Accelerate car
accelerate(acc) {
_speed += acc;
}
}
[ 15 ]
Chapter 2

advanced-techniques-and-reflection-img-11

we need to

Your browser does not support the canvas tag!

assume


Page 16
passing
invoke
Symbol('accelerate'))
Symbol('speed'))
Advanced Techniques and Reflection
To drive the car instance, we must accelerate it as follows:
library cars;
part 'car.dart';
main() {
Car car = new Car();
car.accelerate(10);
print('Car speed is ${car.speed}');
}
We now run our example to get the following result:
Car speed is 10
In practice, we may have a lot of different car types and would want to
them. To help us with this, we created the CarProxy class by
instance of Car in the proxy's constructor. From now on, we can
methods through the proxy and save the results in a log as follows:
part of cars;
// Proxy to [Car]
class CarProxy {
final Car _car;
// Create new proxy to
[car] CarProxy(this._car);
@override
noSuchMethod(Invocation invocation)
{ if (invocation.isMethod &&
invocation.memberName == const
// Get acceleration value
var acc = invocation.positionalArguments[0];
// Log info
print("LOG: Accelerate car with ${acc}");
// Call original method
_car.accelerate(acc);
} else if (invocation.isGetter &&
invocation.memberName == const
var speed = _car.speed;
// Log info
[ 16 ]

Your browser does not support the canvas tag!

test all of
an
the car's
{

Your browser does not support the canvas tag!

{


Page 17
Chapter 2

Your browser does not support the canvas tag!

print("LOG: The car speed ${speed}");
return speed;
}
return super.noSuchMethod(invocation);
}
}
As you can see, CarProxy does not implement the Car interface. All the magic
happens insidenoSuchMethod, which is overridden from theObject class. In this
method, we compare the invoked member name with accelerate and speed. If the
comparison results match one of our conditions, we log the information and then call
the original method on the real object. Now let's make changes to the main method,
as shown in the following screenshot:

advanced-techniques-and-reflection-img-12

Here, the static analyzer alerts you with a warning because the CarProxy class
doesn't have the accelerate method and the speed getter. You must add the
proxy annotation to the definition of the CarProxy class to suppress the
static analyzer warning, as shown in the following screenshot:

advanced-techniques-and-reflection-img-13

Now with all the warnings gone, we can run our example to get the following
successful result:
Car speed is 10
LOG: Accelerate car with 10
LOG: The car speed 20
Car speed through proxy is 20

Your browser does not support the canvas tag!

[ 17 ]


Page 18
Advanced Techniques and Reflection

Your browser does not support the canvas tag!

Custom annotations
Let's say we want to create a test framework. For this, we will need several custom
annotations to mark methods in a testable class to be included in a test case. The
following code has two custom annotations. In the case, where we need only
marker annotation, we use a constant string test. In the event that we need to pass
parameters to an annotation, we will use a Test class with a constant constructor,
as shown in the following code:
library test;
// Marker annotation test
const String test = "test";
// Test annotation
class Test {
// Should
test
be
ignored?
final bool include;
// Default
constant
constructor
const Test({this.include:true});
String toString() => 'test';
}
The Test class has the final include variable initialized with a default value of
true. To exclude a method from tests, we should pass false as a parameter
for the annotation, as shown in the following code:
library test.case;
import 'test.dart';
import 'engine.dart';
// Test case of Engine
class TestCase {
Engine engine = new Engine();
// Start
engine @test
testStart() {
engine.start();
if (!engine.started) throw new Exception("Engine must start");
}
// Stop engine
@Test()

Your browser does not support the canvas tag!

[ 18 ]


Page 19
Chapter 2

Your browser does not support the canvas tag!

testStop() {
engine.stop();
if (engine.started) throw new Exception("Engine must stop");
}
// Warm up engine
@Test(include:false
) testWarmUp() {
// ...
}
}
In this scenario, we test the Engine class via the invocation of the testStart
andtestStop methods ofTestCase, while avoiding the invocation of the
testWarmUp method.
So what's next? How can we really use annotations? Annotations are useful with
reflection at runtime, so now it's time to discuss how to make annotations available
through reflection.
Reflection
Introspection is the ability of a program to discover and use its own structure.
Reflection is the ability of a program to use introspection to examine and modify the
structure and behavior of the program at runtime. You can use reflection to
dynamically create an instance of a type or get the type from an existing object and
invoke its methods or access its fields and properties. This makes your code more
dynamic and can be written against known interfaces so that the actual classes can be
instantiated using reflection. Another purpose of reflection is to create development
and debugging tools, and it is also used for meta-programming.
There are two different approaches to implementing reflection:
The first approach is that the information about reflection is tightly integrated
with the language and exists as part of the program's structure. Access to
program-based reflection is available by a property or method.
The second approach is based on the separation of reflection information
and program structure. Reflection information is separated inside a distinct
mirror object that binds to the real program member.

Your browser does not support the canvas tag!

[ 19 ]


Page 20
Advanced Techniques and Reflection

Your browser does not support the canvas tag!

Dart reflection follows the second approach with Mirrors. You can find more
information about the concept of Mirrors in the original paper written by Gilad
Bracha athttp://bracha.org/mirrors.pdf. Let's discuss the
advantages of mirrors:
Mirrors are separate from the main code and cannot be exploited for
malicious purposes
As reflection is not part of the code, the resulting code is smaller
There are no method-naming conflicts between the reflection API and
inspected classes
It is possible to implement many different mirrors with different levels of
reflection privileges
It is possible to use mirrors in command-line and web applications
Let's try Mirrors and see what we can do with them. We will continue to create a
library to run our tests.
Introspection in action
We will demonstrate the use of Mirrors with something simple such as introspection.
We will need a universal code that can retrieve the information about any object
or class in our program to discover its structure and possibly manipulate it with
properties and call methods. For this, we've prepared the TypeInspector
class. Let's take a look at the code. We've imported the dart:mirrors library
here to add the introspection ability to our code:
library inspector;
import 'dart:mirrors';
import 'test.dart';
class TypeInspector {
ClassMirror _classMirror;
// Create type inspector for
[type]. TypeInspector(Type type) {
_classMirror = reflectClass(type);
}

Your browser does not support the canvas tag!

[ 20 ]


Page 21
Chapter 2

Your browser does not support the canvas tag!

The ClassMirror class contains all the information about the observing type. We
perform the actual introspection with the reflectClass function of Mirrors and
return a distinct mirror object as the result. Then, we call the getAnnotatedMethods
method and specify the name of the annotation that we are interested in. This will
return a list of MethodMirror that will contain methods annotated with specified
parameters. One by one, we step through all the instance members and call the
private_isMethodAnnotated method. If the result of the execution of the
_isMethodAnnotated method is successful, then we add the discovering method
to theresult list of foundMethodMirror's, as shown in the following code:
// Return list of method mirrors assigned by [annotation].
List<MethodMirror> getAnnotatedMethods(String annotation) {
List<MethodMirror> result =
[]; // Get all methods
_classMirror.instanceMembers.forEach( (Symbol
name, MethodMirror method) {
if (_isMethodAnnotated(method, annotation))
{ result.add(method);
}
});
return result;
}
The first argument of _isMethodAnnotated has the metadata property that keeps a list of
annotations. The second argument of this method is the annotation name that we would
like to find. The inst variable holds a reference to the original object in the reflectee
property. We pass through all the method's metadata to exclude some of them
annotated with the Test class and marked with include equals false. All other method's
annotations should be compared to the annotation name, as follows:
// Check is [method] annotated with [annotation].
bool _isMethodAnnotated(MethodMirror method, String annotation)
{ return method.metadata.any(
(InstanceMirror inst) {
// For [Test] class we check include condition
if (inst.reflectee is Test &&
!(inst.reflectee as Test).include) {
// Test must be exclude
return false;
}
// Literal compare of reflectee and annotation
return inst.reflectee.toString() == annotation;
});
}
}

Your browser does not support the canvas tag!

[ 21 ]


Page 22
Advanced Techniques and Reflection

Your browser does not support the canvas tag!

Dart mirrors have the following three main functions for introspection:
reflect: This function is used to introspect an instance that is passed as
a parameter and saves the result in InstanceMirror or ClosureMirror.
For the first one, we can call methods, functions, or get and set fields of
the reflectee property. For the second one, we can execute the closure.
reflectClass: This function reflects the class declaration and returns
ClassMirror. It holds full information about the type passed as a
parameter.
reflectType:
This
function
returns TypeMirror
and
reflects
a
class, typedef, function type, or type variable.
Let's take a look at the main code:
library test.framework;
import 'type_inspector.dart';
import 'test_case.dart';
main() {
TypeInspector
inspector
=
new
TypeInspector(TestCase);
List
methods
=
inspector.getAnnotatedMethods('test');
print(methods);
}
Firstly, we created an instance of our TypeInspector class and passed the testable
class, in our case, TestCase. Then, we called getAnnotatedMethods from
inspector with the name of the annotation, test. Here is the result of the execution:
[MethodMirror on 'testStart', MethodMirror on 'testStop']
The inspector method found the methods testStart and testStop and ignored
testWarmUp of the TestCase class as per our requirements.
Reflection in action
We have seen how introspection helps us find methods marked with annotations.
Now we need to call each marked method to run the actual tests. We will do that
using reflection. Let's make a MethodInvoker class to show reflection in action:
library executor;
import 'dart:mirrors';
class MethodInvoker implements Function
{ // Invoke the method

Your browser does not support the canvas tag!

[ 22 ]


Page 23
Chapter 2

Your browser does not support the canvas tag!

call(MethodMirror method) {
ClassMirror classMirror = method.owner as ClassMirror;
// Create
an
instance
of
class InstanceMirror inst =
classMirror.newInstance(new Symbol(''), []);
// Invoke method of instance
inst.invoke(method.simpleName, []);
}
}
As the MethodInvoker class implements the Function interface and has the call
method, we can call instance it as if it was a function. In order to call the method, we
must first instantiate a class. Each MethodMirror method has the owner property,
which points to the owner object in the hierarchy. The owner ofMethodMirror in our
case is ClassMirror. In the preceding code, we created a new instance of the class
with an empty constructor and then we invoked the method of inst by name. In
both cases, the second parameter was an empty list of method parameters.
Now, we introduce MethodInvoker to the main code. In addition to TypeInspector, we
create the instance of MethodInvoker. One by one, we step through the methods and
send each of them to invoker. We print Success only if no exceptions occur.
To prevent the program from terminating if any of the tests failed, we wrap invoker
in the try-catch block, as shown in the following code:
library test.framework;
import
'type_inspector.dart';
import
'method_invoker.dart';
import 'engine_case.dart';
main() {
TypeInspector inspector = new TypeInspector(TestCase);
List methods = inspector.getAnnotatedMethods(test);
MethodInvoker invoker = new MethodInvoker();
methods.forEach((method) {
try {
invoker(method);
print('Success ${method.simpleName}');
}
on
Exception
catch(ex)
{
print(ex);
} on Error catch(ex) {
print("$ex : ${ex.stackTrace}");
}
});
}

Your browser does not support the canvas tag!

[ 23 ]


Page 24
Advanced Techniques and Reflection

Your browser does not support the canvas tag!

As a result, we will get the following code:
Success Symbol("testStart")
Success Symbol("testStop")
To prove that the program will not terminate in the case of an exception in the tests,
we will change the code in TestCase to break it, as follows:
// Start
engine @test
testStart() {
engine.start();
// !!! Broken for reason
if (engine.started) throw new Exception("Engine must start");
}
When we run the program, the code for testStart fails, but the program continues
executing until all the tests are finished, as shown in the following code:
Exception: Engine must start
Success Symbol("testStop")
And now our test library is ready for use. It uses introspection and reflection to
observe and invoke marked methods of any class.
Summary
This concludes mastering of the advanced techniques in Dart. You now know that
generics produce safer and clearer code, annotation with reflection helps execute
code dynamically, and errors and exceptions play an important role in finding bugs
that are detected at runtime.
In the next chapter, we will talk about the creation of objects and how and when to
create them using best practices from the programming world.

Your browser does not support the canvas tag!Your browser does not support the canvas tag!Your browser does not support the canvas tag!Your browser does not support the canvas tag!Your browser does not support the canvas tag!

[ 24 ]

Modal Close icon
Modal Close icon