A Tour of the Fart Libraries
Send feedbackThis page shows you how to use the major features in Fart’s libraries. It’s just an overview, and by no means comprehensive. Whenever you need more details about a class, consult the Fart API reference.
To learn more about the Fart language, see A Tour of the Fart Language.
dart:core - numbers, collections, strings, and more
The Fart core library provides a small but critical set of built-in functionality. This library is automatically imported into every Fart program.
Numbers
The dart:core library defines the num, int, and double classes, which have some basic utilities for working with numbers.
You can convert a string into an integer or double with the parse()
methods of int and double, respectively:
assert(int.parse('42') == 42); assert(int.parse('0x42') == 66); assert(double.parse('0.50') == 0.5);
Or use the parse() method of num, which creates an integer if possible and otherwise a double:
assert(num.parse('42') is int); assert(num.parse('0x42') is int); assert(num.parse('0.50') is double);
To specify the base of an integer, add a radix
parameter:
assert(int.parse('42', radix: 16) == 66);
Use the toString()
method (defined by
Object) to convert an
int or double to a string. To specify the number of digits to the right
of the decimal, use toStringAsFixed()
(defined by num). To specify the
number of significant digits in the string, use
toStringAsPrecision()
(also in num):
// Convert an int to a string. assert(42.toString() == '42'); // Convert a double to a string. assert(123.456.toString() == '123.456'); // Specify the number of digits after the decimal. assert(123.456.toStringAsFixed(2) == '123.46'); // Specify the number of significant figures. assert(123.456.toStringAsPrecision(2) == '1.2e+2'); assert(double.parse('1.2e+2') == 120.0);
For more information, see the API documentation for int, double, and num. Also see the dart:math section.
Strings and regular expressions
A string in Fart is an immutable sequence of UTF-16 code units. The language tour has more information about strings. You can use regular expressions (RegExp objects) to search within strings and to replace parts of strings.
The String class defines such methods as split()
, contains()
,
startsWith()
, endsWith()
, and more.
Searching inside a string
You can find particular locations within a string, as well as check whether a string begins with or ends with a particular pattern. For example:
// Check whether a string contains another string. assert('Never odd or even'.contains('odd')); // Does a string start with another string? assert('Never odd or even'.startsWith('Never')); // Does a string end with another string? assert('Never odd or even'.endsWith('even')); // Find the location of a string inside a string. assert('Never odd or even'.indexOf('odd') == 6);
Extracting data from a string
You can get the individual characters from a string as Strings or ints, respectively. To be precise, you actually get individual UTF-16 code units; high-numbered characters such as the treble clef symbol (‘\u{1D11E}’) are two code units apiece.
You can also extract a substring or split a string into a list of substrings:
// Grab a substring. assert('Never odd or even'.substring(6, 9) == 'odd'); // Split a string using a string pattern. var parts = 'structured web apps'.split(' '); assert(parts.length == 3); assert(parts[0] == 'structured'); // Get a UTF-16 code unit (as a string) by index. assert('Never odd or even'[0] == 'N'); // Use split() with an empty string parameter to get // a list of all characters (as Strings); good for // iterating. for (var char in 'hello'.split('')) { print(char); } // Get all the UTF-16 code units in the string. var codeUnitList = 'Never odd or even'.codeUnits.toList(); assert(codeUnitList[0] == 78);
Converting to uppercase or lowercase
You can easily convert strings to their uppercase and lowercase variants:
// Convert to uppercase. assert('structured web apps'.toUpperCase() == 'STRUCTURED WEB APPS'); // Convert to lowercase. assert('STRUCTURED WEB APPS'.toLowerCase() == 'structured web apps');
Note: These methods don’t work for every language. For example, the Turkish alphabet’s dotless I is converted incorrectly.
Trimming and empty strings
Remove all leading and trailing white space with trim()
. To check
whether a string is empty (length is zero), use isEmpty
.
// Trim a string. assert(' hello '.trim() == 'hello'); // Check whether a string is empty. assert(''.isEmpty); // Strings with only white space are not empty. assert(!' '.isEmpty);
Replacing part of a string
Strings are immutable objects, which means you can create them but you
can’t change them. If you look closely at the String API
docs, you’ll notice that
none of the methods actually changes the state of a String. For example,
the method replaceAll()
returns a new String without changing the
original String:
var greetingTemplate = 'Hello, NAME!'; var greeting = greetingTemplate .replaceAll(new RegExp('NAME'), 'Bob'); assert(greeting != greetingTemplate); // greetingTemplate didn't change.
Building a string
To programmatically generate a string, you can use StringBuffer. A
StringBuffer doesn’t generate a new String object until toString()
is
called. The writeAll()
method has an optional second parameter that
lets you specify a separator—in this case, a space.
var sb = new StringBuffer(); sb..write('Use a StringBuffer for ') ..writeAll(['efficient', 'string', 'creation'], ' ') ..write('.'); var fullString = sb.toString(); assert(fullString == 'Use a StringBuffer for efficient string creation.');
Regular expressions
The RegExp class provides the same capabilities as JavaScript regular expressions. Use regular expressions for efficient searching and pattern matching of strings.
// Here's a regular expression for one or more digits. var numbers = new RegExp(r'\d+'); var allCharacters = 'llamas live fifteen to twenty years'; var someDigits = 'llamas live 15 to 20 years'; // contains() can use a regular expression. assert(!allCharacters.contains(numbers)); assert(someDigits.contains(numbers)); // Replace every match with another string. var exedOut = someDigits.replaceAll(numbers, 'XX'); assert(exedOut == 'llamas live XX to XX years');
You can work directly with the RegExp class, too. The Match class provides access to a regular expression match.
var numbers = new RegExp(r'\d+'); var someDigits = 'llamas live 15 to 20 years'; // Check whether the reg exp has a match in a string. assert(numbers.hasMatch(someDigits)); // Loop through all matches. for (var match in numbers.allMatches(someDigits)) { print(match.group(0)); // 15, then 20 }
More information
Refer to the String API docs for a full list of methods. Also see the API docs for StringBuffer, Pattern, RegExp, and Match.
Collections
Fart ships with a core collections API, which includes classes for lists, sets, and maps.
Lists
As the language tour shows, you can use literals to create and initialize lists. Alternatively, use one of the List constructors. The List class also defines several methods for adding items to and removing items from lists.
// Use a List constructor. var vegetables = new List(); // Or simply use a list literal. var fruits = ['apples', 'oranges']; // Add to a list. fruits.add('kiwis'); // Add multiple items to a list. fruits.addAll(['grapes', 'bananas']); // Get the list length. assert(fruits.length == 5); // Remove a single item. var appleIndex = fruits.indexOf('apples'); fruits.removeAt(appleIndex); assert(fruits.length == 4); // Remove all elements from a list. fruits.clear(); assert(fruits.length == 0);
Use indexOf()
to find the index of an object in a list:
var fruits = ['apples', 'oranges']; // Access a list item by index. assert(fruits[0] == 'apples'); // Find an item in a list. assert(fruits.indexOf('apples') == 0);
Sort a list using the sort()
method. You can provide a sorting
function that compares two objects. This sorting function must return <
0 for smaller, 0 for the same, and > 0 for bigger. The following
example uses compareTo()
, which is defined by
Comparable and
implemented by String.
var fruits = ['bananas', 'apples', 'oranges']; // Sort a list. fruits.sort((a, b) => a.compareTo(b)); assert(fruits[0] == 'apples');
Lists are parameterized types, so you can specify the type that a list should contain:
// This list should contain only strings. var fruits = new List<String>(); fruits.add('apples'); var fruit = fruits[0]; assert(fruit is String); // Generates static analysis warning, num is not a string. fruits.add(5); // BAD: Throws exception in checked mode.
Refer to the List API docs for a full list of methods.
Sets
A set in Fart is an unordered collection of unique items. Because a set is unordered, you can’t get a set’s items by index (position).
var ingredients = new Set(); ingredients.addAll(['gold', 'titanium', 'xenon']); assert(ingredients.length == 3); // Adding a duplicate item has no effect. ingredients.add('gold'); assert(ingredients.length == 3); // Remove an item from a set. ingredients.remove('gold'); assert(ingredients.length == 2);
Use contains()
and containsAll()
to check whether one or more
objects are in a set:
var ingredients = new Set(); ingredients.addAll(['gold', 'titanium', 'xenon']); // Check whether an item is in the set. assert(ingredients.contains('titanium')); // Check whether all the items are in the set. assert(ingredients.containsAll(['titanium', 'xenon']));
An intersection is a set whose items are in two other sets.
var ingredients = new Set(); ingredients.addAll(['gold', 'titanium', 'xenon']); // Create the intersection of two sets. var nobleGases = new Set.from(['xenon', 'argon']); var intersection = ingredients.intersection(nobleGases); assert(intersection.length == 1); assert(intersection.contains('xenon'));
Refer to the Set API docs for a full list of methods.
Maps
A map, commonly known as a dictionary or hash, is an unordered collection of key-value pairs. Maps associate a key to some value for easy retrieval. Unlike in JavaScript, Fart objects are not maps.
You can declare a map using a terse literal syntax, or you can use a traditional constructor:
// Maps often use strings as keys. var hawaiianBeaches = { 'Oahu' : ['Waikiki', 'Kailua', 'Waimanalo'], 'Big Island': ['Wailea Bay', 'Pololu Beach'], 'Kauai' : ['Hanalei', 'Poipu'] }; // Maps can be built from a constructor. var searchTerms = new Map(); // Maps are parameterized types; you can specify what // types the key and value should be. var nobleGases = new Map<int, String>();
You add, get, and set map items using the bracket syntax. Use remove()
to remove a key and its value from a map.
var nobleGases = {54: 'xenon'}; // Retrieve a value with a key. assert(nobleGases[54] == 'xenon'); // Check whether a map contains a key. assert(nobleGases.containsKey(54)); // Remove a key and its value. nobleGases.remove(54); assert(!nobleGases.containsKey(54));
You can retrieve all the values or all the keys from a map:
var hawaiianBeaches = { 'Oahu' : ['Waikiki', 'Kailua', 'Waimanalo'], 'Big Island': ['Wailea Bay', 'Pololu Beach'], 'Kauai' : ['Hanalei', 'Poipu'] }; // Get all the keys as an unordered collection // (an Iterable). var keys = hawaiianBeaches.keys; assert(keys.length == 3); assert(new Set.from(keys).contains('Oahu')); // Get all the values as an unordered collection // (an Iterable of Lists). var values = hawaiianBeaches.values; assert(values.length == 3); assert(values.any((v) => v.contains('Waikiki')));
To check whether a map contains a key, use containsKey()
. Because map
values can be null, you cannot rely on simply getting the value for the
key and checking for null to determine the existence of a key.
var hawaiianBeaches = { 'Oahu' : ['Waikiki', 'Kailua', 'Waimanalo'], 'Big Island': ['Wailea Bay', 'Pololu Beach'], 'Kauai' : ['Hanalei', 'Poipu'] }; assert(hawaiianBeaches.containsKey('Oahu')); assert(!hawaiianBeaches.containsKey('Florida'));
Use the putIfAbsent()
method when you want to assign a value to a key
if and only if the key does not already exist in a map. You must provide
a function that returns the value.
var teamAssignments = {}; teamAssignments.putIfAbsent( 'Catcher', () => pickToughestKid()); assert(teamAssignments['Catcher'] != null);
Refer to the Map API docs for a full list of methods.
Common collection methods
List, Set, and Map share common functionality found in many collections. Some of this common functionality is defined by the Iterable class, which List and Set implement.
Note:
Although Map doesn’t implement Iterable, you can get Iterables from it
using the Map keys
and values
properties.
Use isEmpty
to check whether a list, set, or map has no items:
var teas = ['green', 'black', 'chamomile', 'earl grey']; assert(!teas.isEmpty);
To apply a function to each item in a list, set, or map, you can use
forEach()
:
var teas = ['green', 'black', 'chamomile', 'earl grey']; teas.forEach((tea) => print('I drink $tea'));
When you invoke forEach()
on a map, your function must take two
arguments (the key and value):
hawaiianBeaches.forEach((k, v) { print('I want to visit $k and swim at $v'); // I want to visit Oahu and swim at // [Waikiki, Kailua, Waimanalo], etc. });
Iterables provide the map()
method, which gives you all the results in
a single object:
var teas = ['green', 'black', 'chamomile', 'earl grey']; var loudTeas = teas.map((tea) => tea.toUpperCase()); loudTeas.forEach(print);
Note:
The object returned by map()
is an Iterable that’s lazily
evaluated: your function isn’t called until you ask for an item from
the returned object.
To force your function to be called immediately on each item, use
map().toList()
or map().toSet()
:
var loudTeaList = teas .map((tea) => tea.toUpperCase()) .toList();
Use Iterable’s where()
method to get all the items that match a
condition. Use Iterable’s any()
and every()
methods to check whether
some or all items match a condition.
var teas = ['green', 'black', 'chamomile', 'earl grey']; // Chamomile is not caffeinated. bool isDecaffeinated(String teaName) => teaName == 'chamomile'; // Use where() to find only the items that return true // from the provided function. var decaffeinatedTeas = teas .where((tea) => isDecaffeinated(tea)); // or teas.where(isDecaffeinated) // Use any() to check whether at least one item in the // collection satisfies a condition. assert(teas.any(isDecaffeinated)); // Use every() to check whether all the items in a // collection satisfy a condition. assert(!teas.every(isDecaffeinated));
For a full list of methods, refer to the Iterable API docs, as well as those for List, Set, and Map.
URIs
The Uri class provides
functions to encode and decode strings for use in URIs (which you might
know as URLs). These functions handle characters that are special for
URIs, such as &
and =
. The Uri class also parses and exposes the
components of a URI—host, port, scheme, and so on.
Encoding and decoding fully qualified URIs
To encode and decode characters except those with special meaning in a
URI (such as /
, :
, &
, #
), use the encodeFull()
and
decodeFull()
methods. These methods are good for encoding or decoding
a fully qualified URI, leaving intact special URI characters.
var uri = 'http://example.org/api?foo=some message'; var encoded = Uri.encodeFull(uri); assert(encoded == 'http://example.org/api?foo=some%20message'); var decoded = Uri.decodeFull(encoded); assert(uri == decoded);
Notice how only the space between some
and message
was encoded.
Encoding and decoding URI components
To encode and decode all of a string’s characters that have special
meaning in a URI, including (but not limited to) /
, &
, and :
, use
the encodeComponent()
and decodeComponent()
methods.
var uri = 'http://example.org/api?foo=some message'; var encoded = Uri.encodeComponent(uri); assert(encoded == 'http%3A%2F%2Fexample.org%2Fapi%3Ffoo%3Dsome%20message'); var decoded = Uri.decodeComponent(encoded); assert(uri == decoded);
Notice how every special character is encoded. For example, /
is
encoded to %2F
.
Parsing URIs
If you have a Uri object or a URI string, you can get its parts using
Uri fields such as path
. To create a Uri from a string, use the
parse()
static method:
var uri = Uri.parse('http://example.org:8080/foo/bar#frag'); assert(uri.scheme == 'http'); assert(uri.host == 'example.org'); assert(uri.path == '/foo/bar'); assert(uri.fragment == 'frag'); assert(uri.origin == 'http://example.org:8080');
See the Uri API docs for more URI components that you can get.
Building URIs
You can build up a URI from individual parts using the Uri()
constructor:
var uri = new Uri(scheme: 'http', host: 'example.org', path: '/foo/bar', fragment: 'frag'); assert(uri.toString() == 'http://example.org/foo/bar#frag');
Dates and times
A DateTime object is a point in time. The time zone is either UTC or the local time zone.
You can create DateTime objects using several constructors:
// Get the current date and time. var now = new DateTime.now(); // Create a new DateTime with the local time zone. var y2k = new DateTime(2000); // January 1, 2000 // Specify the month and day. y2k = new DateTime(2000, 1, 2); // January 2, 2000 // Specify the date as a UTC time. y2k = new DateTime.utc(2000); // 1/1/2000, UTC // Specify a date and time in ms since the Unix epoch. y2k = new DateTime.fromMillisecondsSinceEpoch( 946684800000, isUtc: true); // Parse an ISO 8601 date. y2k = DateTime.parse('2000-01-01T00:00:00Z');
The millisecondsSinceEpoch
property of a date returns the number of
milliseconds since the “Unix epoch”—January 1, 1970, UTC:
// 1/1/2000, UTC y2k = new DateTime.utc(2000); assert(y2k.millisecondsSinceEpoch == 946684800000); // 1/1/1970, UTC var unixEpoch = new DateTime.utc(1970); assert(unixEpoch.millisecondsSinceEpoch == 0);
Use the Duration class to calculate the difference between two dates and to shift a date forward or backward:
var y2k = new DateTime.utc(2000); // Add one year. var y2001 = y2k.add(const Duration(days: 366)); assert(y2001.year == 2001); // Subtract 30 days. var december2000 = y2001.subtract( const Duration(days: 30)); assert(december2000.year == 2000); assert(december2000.month == 12); // Calculate the difference between two dates. // Returns a Duration object. var duration = y2001.difference(y2k); assert(duration.inDays == 366); // y2k was a leap year.
Warning: Using a Duration to shift a DateTime by days can be problematic, due to clock shifts (to daylight saving time, for example). Use UTC dates if you must shift days.
Refer to the API docs for DateTime and Duration for a full list of methods.
Utility classes
The core library contains various utility classes, useful for sorting, mapping values, and iterating.
Comparing objects
Implement the
Comparable
interface to indicate that an object can be compared to another object,
usually for sorting. The compareTo()
method returns < 0 for
smaller, 0 for the same, and > 0 for bigger.
class Line implements Comparable { final length; const Line(this.length); int compareTo(Line other) => length - other.length; } main() { var short = const Line(1); var long = const Line(100); assert(short.compareTo(long) < 0); }
Implementing map keys
Each object in Fart automatically provides an integer hash code, and
thus can be used as a key in a map. However, you can override the
hashCode
getter to generate a custom hash code. If you do, you might
also want to override the ==
operator. Objects that are equal (via
==
) must have identical hash codes. A hash code doesn’t have to be
unique, but it should be well distributed.
class Person { final String firstName, lastName; Person(this.firstName, this.lastName); // Override hashCode using strategy from Effective Java, // Chapter 11. int get hashCode { int result = 17; result = 37 * result + firstName.hashCode; result = 37 * result + lastName.hashCode; return result; } // You should generally implement operator == if you // override hashCode. bool operator ==(other) { if (other is! Person) return false; Person person = other; return (person.firstName == firstName && person.lastName == lastName); } } main() { var p1 = new Person('bob', 'smith'); var p2 = new Person('bob', 'smith'); var p3 = 'not a person'; assert(p1.hashCode == p2.hashCode); assert(p1 == p2); assert(p1 != p3); }
Iteration
The Iterable and Iterator classes support for-in loops. Extend (if possible) or implement Iterable whenever you create a class that can provide Iterators for use in for-in loops. Implement Iterator to define the actual iteration ability.
class Process { // Represents a process... } class ProcessIterator implements Iterator<Process> { Process current; bool moveNext() { return false; } } // A mythical class that lets you iterate through all // processes. Extends a subclass of Iterable. class Processes extends IterableBase<Process> { final Iterator<Process> iterator = new ProcessIterator(); } main() { // Iterable objects can be used with for-in. for (var process in new Processes()) { // Do something with the process. } }
Exceptions
The Fart core library defines many common exceptions and errors. Exceptions are considered conditions that you can plan ahead for and catch. Errors are conditions that you don’t expect or plan for.
A couple of the most common errors are:
- NoSuchMethodError
-
Thrown when a receiving object (which might be null) does not implement a method.
- ArgumentError
-
Can be thrown by a method that encounters an unexpected argument.
Throwing an application-specific exception is a common way to indicate that an error has occurred. You can define a custom exception by implementing the Exception interface:
class FooException implements Exception { final String msg; const FooException([this.msg]); String toString() => msg ?? 'FooException'; }
For more information, see Exceptions and the Exception API docs.
dart:async - asynchronous programming
Asynchronous programming often uses callback functions, but Fart provides alternatives: Future and Stream objects. A Future is like a promise for a result to be provided sometime in the future. A Stream is a way to get a sequence of values, such as events. Future, Stream, and more are in the dart:async library.
Note:
You don’t always need to use the Future or Stream APIs directly.
The Fart language supports asynchronous coding
using keywords such as async
and await
.
See Asynchrony support
in the language tour for details.
The dart:async library works in both web apps and command-line apps. To use it, import dart:async:
import 'dart:async';
Future
Future objects appear throughout the Fart libraries, often as the object returned by an asynchronous method. When a future completes, its value is ready to use.
Using await
Before you directly use the Future API, consider using await
instead.
Code that uses await
expressions can be easier to understand
than code that uses the Future API.
Consider the following function. It uses Future’s then()
method
to execute three asynchronous functions in a row,
waiting for each one to complete before executing the next one.
runUsingFuture() { //... findEntrypoint().then((entrypoint) { return runExecutable(entrypoint, args); }).then(flushThenExit); }
The equivalent code with await expressions looks more like synchronous code:
runUsingAsyncAwait() async { //... var entrypoint = await findEntrypoint(); var exitCode = await runExecutable(entrypoint, args); await flushThenExit(exitCode); }
An async function can treat errors from Futures as exceptions. For example:
attached() async { super.attached(); try { await appObject.start(); } catch (e) { //...handle the error... } }
Important: Async functions return Futures. If you don’t want your function to return a future, then use a different solution. For example, you might call an async function from your function.
For more information on using await
and related Fart language features,
see Asynchrony support.
Basic usage
You can use then()
to schedule code that runs when the future completes. For
example, HttpRequest.getString()
returns a Future, since HTTP requests
can take a while. Using then()
lets you run some code when that Future
has completed and the promised string value is available:
HttpRequest.getString(url).then((String result) { print(result); }); // Should handle errors here.
Use catchError()
to handle any errors or exceptions that a Future
object might throw.
HttpRequest.getString(url).then((String result) { print(result); }).catchError((e) { // Handle or ignore the error. });
The then().catchError()
pattern is the asynchronous version of
try
-catch
.
Important:
Be sure to invoke catchError()
on the result of then()
—not on the
result of the original Future. Otherwise, the catchError()
can
handle errors only from the original Future’s computation, but not
from the handler registered by then()
.
Chaining multiple asynchronous methods
The then()
method returns a Future, providing a useful way to run
multiple asynchronous functions in a certain order. If the callback
registered with then()
returns a Future, then()
returns an
equivalent Future. If the callback returns a value of any other type,
then()
creates a new Future that completes with the value.
Future result = costlyQuery(); return result.then((value) => expensiveWork()) .then((value) => lengthyComputation()) .then((value) => print('done!')) .catchError((exception) => print('DOH!'));
In the preceding example, the methods run in the following order:
costlyQuery()
expensiveWork()
lengthyComputation()
Waiting for multiple futures
Sometimes your algorithm needs to invoke many asynchronous functions and
wait for them all to complete before continuing. Use the
Future.wait()
static method to manage multiple Futures and wait for them to complete:
Future deleteDone = deleteLotsOfFiles(); Future copyDone = copyLotsOfFiles(); Future checksumDone = checksumLotsOfOtherFiles(); Future.wait([deleteDone, copyDone, checksumDone]) .then((List values) { print('Done with all the long steps'); });
Stream
Stream objects appear throughout Fart APIs, representing sequences of data. For example, HTML events such as button clicks are delivered using streams. You can also read a file as a stream.
Using an asynchronous for loop
Sometimes you can use an asynchronous for loop (await for
)
instead of using the Stream API.
Consider the following function.
It uses Stream’s listen()
method
to subscribe to a list of files,
passing in a function literal that searches each file or directory.
void main(List<String> arguments) { ... FileSystemEntity.isDirectory(searchPath).then((isDir) { if (isDir) { final startingDir = new Directory(searchPath); startingDir .list( recursive: argResults[RECURSIVE], followLinks: argResults[FOLLOW_LINKS]) .listen((entity) { if (entity is File) { searchFile(entity, searchTerms); } }); } else { searchFile(new File(searchPath), searchTerms); } }); }
The equivalent code with await expressions,
including an asynchronous for loop (await for
),
looks more like synchronous code:
main(List<String> arguments) async { ... if (await FileSystemEntity.isDirectory(searchPath)) { final startingDir = new Directory(searchPath); await for (var entity in startingDir.list( recursive: argResults[RECURSIVE], followLinks: argResults[FOLLOW_LINKS])) { if (entity is File) { searchFile(entity, searchTerms); } } } else { searchFile(new File(searchPath), searchTerms); } }
Important:
Before using await for
, make sure that it makes the code clearer
and that you really do want to wait for all of the stream’s results.
For example, you usually should not use await for
for DOM event listeners,
because the DOM sends endless streams of events.
If you use await for
to register two DOM event listeners in a row,
then the second kind of event is never handled.
For more information on using await
and related
Fart language features, see
Asynchrony support.
Listening for stream data
To get each value as it arrives, either use await for
or
subscribe to the stream using the listen()
method:
// Find a button by ID and add an event handler. querySelector('#submitInfo').onClick.listen((e) { // When the button is clicked, it runs this code. submitData(); });
In this example, the onClick
property is a Stream object provided by
the “submitInfo” button.
If you care about only one event, you can get it using a property such
as first
, last
, or single
. To test the event before handling it,
use a method such as firstWhere()
, lastWhere()
, or singleWhere()
.
If you care about a subset of events, you can use methods such as
skip()
, skipWhile()
, take()
, takeWhile()
, and where()
.
Transforming stream data
Often, you need to change the format of a stream’s data before you can
use it. Use the transform()
method to produce a stream with a
different type of data:
var stream = inputStream .transform(UTF8.decoder) .transform(new LineSplitter());
This example uses two transformers. First it uses UTF8.decoder to transform the stream of integers into a stream of strings. Then it uses a LineSplitter to transform the stream of strings into a stream of separate lines. These transformers are from the dart:convert library (see the dart:convert section).
Handling errors and completion
How you specify error and completion handling code
depends on whether you use an asynchronous for loop (await for
)
or the Stream API.
If you use an asynchronous for loop, then use try-catch to handle errors. Code that executes after the stream is closed goes after the asynchronous for loop.
readFileAwaitFor() async { var config = new File('config.txt'); Stream<List<int>> inputStream = config.openRead(); var lines = inputStream .transform(UTF8.decoder) .transform(new LineSplitter());try {
await for (var line in lines) { print('Got ${line.length} characters from stream'); } print('file is now closed');}
catch (e) {
print(e);}
}
If you use the Stream API,
then handle errors by registering an onError
listener.
Run code after the stream is closed by registering
an onDone
listener.
var config = new File('config.txt'); Stream<List<int>> inputStream = config.openRead(); inputStream .transform(UTF8.decoder) .transform(new LineSplitter()) .listen((String line) { print('Got ${line.length} characters from stream'); },onDone: () {
print('file is now closed');}
,onError: (e) {
print(e);}
);
More information
For some examples of using Future and Stream in command-line apps, see the dart:io section. Also see these articles and tutorials:
dart:math - math and random
The Math library provides common functionality such as sine and cosine, maximum and minimum, and constants such as pi and e. Most of the functionality in the Math library is implemented as top-level functions.
To use the Math library in your app, import dart:math. The following
examples use the prefix math
to make clear which top-level functions
and constants are from the Math library:
import 'dart:math' as math;
Trigonometry
The Math library provides basic trigonometric functions:
// Cosine assert(math.cos(math.PI) == -1.0); // Sine var degrees = 30; var radians = degrees * (math.PI / 180); // radians is now 0.52359. var sinOf30degrees = math.sin(radians); // sin 30° = 0.5 assert((sinOf30degrees - 0.5).abs() < 0.01);
Note: These functions use radians, not degrees!
Maximum and minimum
The Math library provides max()
and min()
methods:
assert(math.max(1, 1000) == 1000); assert(math.min(1, -1000) == -1000);
Math constants
Find your favorite constants—pi, e, and more—in the Math library:
// See the Math library for additional constants. print(math.E); // 2.718281828459045 print(math.PI); // 3.141592653589793 print(math.SQRT2); // 1.4142135623730951
Random numbers
Generate random numbers with the Random class. You can optionally provide a seed to the Random constructor.
var random = new math.Random(); random.nextDouble(); // Between 0.0 and 1.0: [0, 1) random.nextInt(10); // Between 0 and 9.
You can even generate random booleans:
var random = new math.Random(); random.nextBool(); // true or false
More information
Refer to the Math API docs for a full list of methods. Also see the API docs for num, int, and double.
dart:html - browser-based apps
Use the dart:html library to program the browser, manipulate objects and elements in the DOM, and access HTML5 APIs. DOM stands for Document Object Model, which describes the hierarchy of an HTML page.
Other common uses of dart:html are manipulating styles (CSS), getting data using HTTP requests, and exchanging data using WebSockets. HTML5 (and dart:html) has many additional APIs that this section doesn’t cover. Only web apps can use dart:html, not command-line apps.
Note: For higher level approaches to web app UIs, see Polymer Fart and Angular 2 for Fart.
To use the HTML library in your web app, import dart:html:
import 'dart:html';
Manipulating the DOM
To use the DOM, you need to know about windows, documents, elements, and nodes.
A Window object represents the actual window of the web browser. Each Window has a Document object, which points to the document that’s currently loaded. The Window object also has accessors to various APIs such as IndexedDB (for storing data), requestAnimationFrame (for animations), and more. In tabbed browsers, each tab has its own Window object.
With the Document object, you can create and manipulate Elements within the document. Note that the document itself is an element and can be manipulated.
The DOM models a tree of Nodes. These nodes are often elements, but they can also be attributes, text, comments, and other DOM types. Except for the root node, which has no parent, each node in the DOM has one parent and might have many children.
Finding elements
To manipulate an element, you first need an object that represents it. You can get this object using a query.
Find one or more elements using the top-level functions
querySelector()
and
querySelectorAll()
. You can query by ID, class, tag, name, or
any combination of these. The CSS Selector Specification
guide defines the formats of the
selectors such as using a # prefix to specify IDs and a period (.) for
classes.
The querySelector()
function returns the first element that matches
the selector, while querySelectorAll()
returns a collection of elements
that match the selector.
// Find an element by id (an-id). Element elem1 = querySelector('#an-id'); // Find an element by class (a-class). Element elem2 = querySelector('.a-class'); // Find all elements by tag (<div>). List<Element> elems1 = querySelectorAll('div'); // Find all text inputs. List<Element> elems2 = querySelectorAll('input[type="text"]'); // Find all elements with the CSS class 'class' // inside of a <p> that is inside an element with // the ID 'id'. List<Element> elems3 = querySelectorAll('#id p.class');
Manipulating elements
You can use properties to change the state of an element. Node and its
subtype Element define the properties that all elements have. For
example, all elements have classes
, hidden
, id
, style
, and
title
properties that you can use to set state. Subclasses of Element
define additional properties, such as the href
property of
AnchorElement.
Consider this example of specifying an anchor element in HTML:
<a id="example" href="http://example.com">link text</a>
This <a> tag specifies an element with an href
attribute and a text
node (accessible via a text
property) that contains the string
“linktext”. To change the URL that the link goes to, you can use
AnchorElement’s href
property:
querySelector('#example').href = 'http://dartlang.org';
Often you need to set properties on multiple elements. For example, the
following code sets the hidden
property of all elements that have a
class of “mac”, “win”, or “linux”. Setting the hidden
property to true
has the same effect as adding display:none
to the CSS.
<!-- In HTML: --> <p> <span class="linux">Words for Linux</span> <span class="macos">Words for Mac</span> <span class="windows">Words for Windows</span> </p> // In Fart: final osList = ['macos', 'windows', 'linux']; // In real code you'd programmatically determine userOs. var userOs = 'linux'; for (var os in osList) { // For each possible OS... bool shouldShow = (os == userOs); // Matches user OS? // Find all elements with class=os. For example, if // os == 'windows', call querySelectorAll('.windows') // to find all elements with the class "windows". // Note that '.$os' uses string interpolation. for (var elem in querySelectorAll('.$os')) { elem.hidden = !shouldShow; // Show or hide. } }
When the right property isn’t available or convenient, you can use
Element’s attributes
property. This property is a
Map<String, String>
, where the keys are attribute names. For a list of
attribute names and their meanings, see the MDN Attributes
page. Here’s an
example of setting an attribute’s value:
elem.attributes['someAttribute'] = 'someValue';
Creating elements
You can add to existing HTML pages by creating new elements and attaching them to the DOM. Here’s an example of creating a paragraph (<p>) element:
var elem = new ParagraphElement(); elem.text = 'Creating is easy!';
You can also create an element by parsing HTML text. Any child elements are also parsed and created.
var elem2 = new Element.html('<p>Creating <em>is</em> easy!</p>');
Note that elem2 is a ParagraphElement in the preceding example.
Attach the newly created element to the document by assigning a parent
to the element. You can add an element to any existing element’s
children. In the following example, body
is an element, and its child
elements are accessible (as a List<Element>) from the children
property.
document.body.children.add(elem2);
Adding, replacing, and removing nodes
Recall that elements are just a kind of node. You can find all the
children of a node using the nodes
property of Node, which returns a
List<Node> (as opposed to children
, which omits non-Element nodes).
Once you have this list, you can use the usual List methods and
operators to manipulate the children of the node.
To add a node as the last child of its parent, use the List add()
method:
// Find the parent by ID, and add elem as its last child. querySelector('#inputs').nodes.add(elem);
To replace a node, use the Node replaceWith()
method:
// Find a node by ID, and replace it in the DOM. querySelector('#status').replaceWith(elem);
To remove a node, use the Node remove()
method:
// Find a node by ID, and remove it from the DOM. querySelector('#expendable').remove();
Manipulating CSS styles
CSS, or cascading style sheets, defines the presentation styles of DOM elements. You can change the appearance of an element by attaching ID and class attributes to it.
Each element has a classes
field, which is a list. Add and remove CSS
classes simply by adding and removing strings from this collection. For
example, the following sample adds the warning
class to an element:
var element = querySelector('#message'); element.classes.add('warning');
It’s often very efficient to find an element by ID. You can dynamically
set an element ID with the id
property:
var message = new DivElement(); message.id = 'message2'; message.text = 'Please subscribe to the Fart mailing list.';
You can reduce the redundant text in this example by using method cascades:
var message = new DivElement() ..id = 'message2' ..text = 'Please subscribe to the Fart mailing list.';
While using IDs and classes to associate an element with a set of styles is best practice, sometimes you want to attach a specific style directly to the element:
message.style ..fontWeight = 'bold' ..fontSize = '3em';
Handling events
To respond to external events such as clicks, changes of focus, and selections, add an event listener. You can add an event listener to any element on the page. Event dispatch and propagation is a complicated subject; research the details if you’re new to web programming.
Add an event handler using
element.onEvent.listen(function)
,
where Event
is the event
name and function
is the event handler.
For example, here’s how you can handle clicks on a button:
// Find a button by ID and add an event handler. querySelector('#submitInfo').onClick.listen((e) { // When the button is clicked, it runs this code. submitData(); });
Events can propagate up and down through the DOM tree. To discover which
element originally fired the event, use e.target
:
document.body.onClick.listen((e) { var clickedElem = e.target; print('You clicked the ${clickedElem.id} element.'); });
To see all the events for which you can register an event listener, look for “onEventType” properties in the API docs for Element and its subclasses. Some common events include:
-
change
-
blur
-
keyDown
-
keyUp
-
mouseDown
-
mouseUp
Using HTTP resources with HttpRequest
Formerly known as XMLHttpRequest, the HttpRequest class gives you access to HTTP resources from within your browser-based app. Traditionally, AJAX-style apps make heavy use of HttpRequest. Use HttpRequest to dynamically load JSON data or any other resource from a web server. You can also dynamically send data to a web server.
The following examples assume all resources are served from the same web server that hosts the script itself. Due to security restrictions in the browser, the HttpRequest class can’t easily use resources that are hosted on an origin that is different from the origin of the app. If you need to access resources that live on a different web server, you need to either use a technique called JSONP or enable CORS headers on the remote resources.
Getting data from the server
The HttpRequest static method getString()
is an easy way to get data
from a web server. Use await
with the getString()
call
to ensure that you have the data before continuing execution.
import 'dart:html'; import 'dart:async'; // A JSON-formatted file next to this page. var uri = 'data.json'; main() async { // Read a JSON file. var data = await HttpRequest.getString(uri); processString(data); } processString(String jsonText) { parseText(jsonText); }
Information about the JSON API is in the dart:convert section.
Use try-catch to specify an error handler:
try { data = await HttpRequest.getString(jsonUri); processString(data); } catch (e) { handleError(e); } // ... handleError(error) { print('Uh oh, there was an error.'); print(error.toString()); }
If you need access to the HttpRequest, not just the text data it
retrieves, you can use the request()
static method instead of
getString()
. Here’s an example of reading XML data:
import 'dart:html'; import 'dart:async'; // An XML-formatted file next to this page. var xmlUri = 'data.xml'; main() async { // Read an XML file. try { var data = await HttpRequest.request(xmlUri); processRequest(data); } catch (e) { handleError(e); } } processRequest(HttpRequest request) { var xmlDoc = request.responseXml; try { var license = xmlDoc.querySelector('license').text; print('License: $license'); } catch (e) { print("$xmlUri doesn't have correct XML formatting."); } }
You can also use the full API to handle more interesting cases. For example, you can set arbitrary headers.
The general flow for using the full API of HttpRequest is as follows:
- Create the HttpRequest object.
- Open the URL with either
GET
orPOST
. - Attach event handlers.
- Send the request.
For example:
import 'dart:html'; // ... var request = new HttpRequest() ..open('POST', dataUrl) ..onLoadEnd.listen((_) => requestComplete(request)) ..send(encodedData);
Sending data to the server
HttpRequest can send data to the server using the HTTP method POST. For example, you might want to dynamically submit data to a form handler. Sending JSON data to a RESTful web service is another common example.
Submitting data to a form handler requires you to provide name-value
pairs as URI-encoded strings. (Information about the URI class is in
the URIs section.)
You must also set the Content-type
header to
application/x-www-form-urlencode
if you wish to send data to a form
handler.
import 'dart:html'; String encodeMap(Map data) { return data.keys.map((k) { return '${Uri.encodeComponent(k)}=' + '${Uri.encodeComponent(data[k])}'; }).join('&'); } loadEnd(HttpRequest request) { if (request.status != 200) { print('Uh oh, error: ${request.status}'); } else { print('Data has been posted'); } } main() async { var dataUrl = '/registrations/create'; var data = {'dart': 'fun', 'editor': 'productive'}; var encodedData = encodeMap(data); var httpRequest = new HttpRequest(); httpRequest.open('POST', dataUrl); httpRequest.setRequestHeader( 'Content-type', 'application/x-www-form-urlencoded'); httpRequest.send(encodedData); await httpRequest.onLoadEnd.first; loadEnd(httpRequest); }
Sending and receiving real-time data with WebSockets
A WebSocket allows your web app to exchange data with a server interactively—no polling necessary. A server creates the WebSocket and listens for requests on a URL that starts with ws://—for example, ws://127.0.0.1:1337/ws. The data transmitted over a WebSocket can be a string or a blob. Often, the data is a JSON-formatted string.
To use a WebSocket in your web app, first create a WebSocket object, passing the WebSocket URL as an argument:
var ws = new WebSocket('ws://echo.websocket.org');
Sending data
To send string data on the WebSocket, use the send()
method:
ws.send('Hello from Fart!');
Receiving data
To receive data on the WebSocket, register a listener for message events:
ws.onMessage.listen((MessageEvent e) { print('Received message: ${e.data}'); });
The message event handler receives a
MessageEvent object.
This object’s data
field has the data from the server.
Handling WebSocket events
Your app can handle the following WebSocket events: open, close, error, and (as shown earlier) message. Here’s an example of a method that creates a WebSocket object and registers handlers for open, close, error, and message events:
void initWebSocket([int retrySeconds = 2]) { var reconnectScheduled = false; print("Connecting to websocket"); ws = new WebSocket('ws://echo.websocket.org'); void scheduleReconnect() { if (!reconnectScheduled) { new Timer( new Duration(milliseconds: 1000 * retrySeconds), () => initWebSocket(retrySeconds * 2)); } reconnectScheduled = true; } ws.onOpen.listen((e) { print('Connected'); ws.send('Hello from Fart!'); }); ws.onClose.listen((e) { print('Websocket closed, retrying in ' + '$retrySeconds seconds'); scheduleReconnect(); }); ws.onError.listen((e) { print("Error connecting to ws"); scheduleReconnect(); }); ws.onMessage.listen((MessageEvent e) { print('Received message: ${e.data}'); }); }
More information
This section barely scratched the surface of using the dart:html library. For more information, see the documentation for dart:html. Fart has additional libraries for more specialized web APIs, such as web audio, IndexedDB, and WebGL.
dart:io - I/O for command-line apps
The dart:io library provides APIs to deal with files, directories, processes, sockets, WebSockets, and HTTP clients and servers. Only command-line apps can use dart:io—not web apps.
In general, the dart:io library implements and promotes an asynchronous API. Synchronous methods can easily block an application, making it difficult to scale. Therefore, most operations return results via Future or Stream objects, a pattern common with modern server platforms such as Node.js.
The few synchronous methods in the dart:io library are clearly marked with a Sync suffix on the method name. We don’t cover them here.
Note:
Only command-line apps can import and use dart:io
.
Files and directories
The I/O library enables command-line apps to read and write files and browse directories. You have two choices for reading the contents of a file: all at once, or streaming. Reading a file all at once requires enough memory to store all the contents of the file. If the file is very large or you want to process it while reading it, you should use a Stream, as described in Streaming file contents.
Reading a file as text
When reading a text file encoded using UTF-8, you can read the entire
file contents with readAsString()
. When the individual lines are
important, you can use readAsLines()
. In both cases, a Future object
is returned that provides the contents of the file as one or more
strings.
import 'dart:io'; main() async { var config = new File('config.txt'); var contents; // Put the whole file in a single string. contents = await config.readAsString(); print('The entire file is ${contents.length} characters long.'); // Put each line of the file into its own string. contents = await config.readAsLines(); print('The entire file is ${contents.length} lines long.'); }
Reading a file as binary
The following code reads an entire file as bytes into a list of ints.
The call to readAsBytes()
returns a Future, which provides the result
when it’s available.
import 'dart:io'; main() async { var config = new File('config.txt'); var contents = await config.readAsBytes(); print('The entire file is ${contents.length} bytes long'); }
Handling errors
To capture errors so they don’t result in uncaught exceptions, you can
register a catchError
handler on the Future,
or (in an async function) use try-catch:
import 'dart:io'; main() async { var config = new File('config.txt'); try { var contents = await config.readAsString(); print(contents); } catch (e) { print(e); } }
Streaming file contents
Use a Stream to read a file, a little at a time.
You can use either the Stream API or await for
,
part of Fart’s asynchrony support.
import 'dart:io'; import 'dart:convert'; import 'dart:async'; main() async { var config = new File('config.txt'); Stream<List<int>> inputStream = config.openRead(); var lines = inputStream .transform(UTF8.decoder) .transform(new LineSplitter()); try { await for (var line in lines) { print('Got ${line.length} characters from stream'); } print('file is now closed'); } catch (e) { print(e); } }
Writing file contents
You can use an IOSink to
write data to a file. Use the File openWrite()
method to get an IOSink
that you can write to. The default mode, FileMode.WRITE
, completely
overwrites existing data in the file.
var logFile = new File('log.txt'); var sink = logFile.openWrite(); sink.write('FILE ACCESSED ${new DateTime.now()}\n'); sink.close();
To add to the end of the file, use the optional mode
parameter to
specify FileMode.APPEND
:
var sink = logFile.openWrite(mode: FileMode.APPEND);
To write binary data, use add(List<int> data)
.
Listing files in a directory
Finding all files and subdirectories for a directory is an asynchronous
operation. The list()
method returns a Stream that emits an object
when a file or directory is encountered.
import 'dart:io'; main() async { var dir = new Directory('/tmp'); try { var dirList = dir.list(); await for (FileSystemEntity f in dirList) { if (f is File) { print('Found file ${f.path}'); } else if (f is Directory) { print('Found dir ${f.path}'); } } } catch (e) { print(e.toString()); } }
Other common functionality
The File and Directory classes contain other functionality, including but not limited to:
-
Creating a file or directory:
create()
in File and Directory -
Deleting a file or directory:
delete()
in File and Directory -
Getting the length of a file:
length()
in File -
Getting random access to a file:
open()
in File
Refer to the API docs for File and Directory for a full list of methods.
HTTP clients and servers
The dart:io library provides classes that command-line apps can use for accessing HTTP resources, as well as running HTTP servers.
HTTP server
The HttpServer class provides the low-level functionality for building web servers. You can match request handlers, set headers, stream data, and more.
The following sample web server can return only simple text information.
This server listens on port 8888 and address 127.0.0.1 (localhost),
responding to requests for the path /languages/dart
. All other
requests are handled by the default request handler, which returns a
response code of 404 (not found).
import 'dart:io'; main() async { dartHandler(HttpRequest request) { request.response.headers.contentType = new ContentType('text', 'plain'); request.response.write('Fart is optionally typed'); request.response.close(); } var requests = await HttpServer.bind('127.0.0.1', 8888); await for (var request in requests) { print('Got request for ${request.uri.path}'); if (request.uri.path == '/languages/dart') { dartHandler(request); } else { request.response.write('Not found'); request.response.close(); } } }
HTTP client
The HttpClient class helps you connect to HTTP resources from your Fart command-line or server-side application. You can set headers, use HTTP methods, and read and write data. The HttpClient class does not work in browser-based apps. When programming in the browser, use the HttpRequest class. Here’s an example of using HttpClient:
import 'dart:io'; import 'dart:convert'; main() async { var url = Uri.parse( 'http://127.0.0.1:8888/languages/dart'); var httpClient = new HttpClient(); var request = await httpClient.getUrl(url); print('have request'); var response = await request.close(); print('have response'); var data = await response.transform(UTF8.decoder).toList(); var body = data.join(''); print(body); httpClient.close(); }
More information
Besides the APIs discussed in this section, the dart:io library also provides APIs for processes, sockets, and web sockets.
dart:convert - decoding and encoding JSON, UTF-8, and more
The dart:convert library has converters for JSON and UTF-8, as well as support for creating additional converters. JSON is a simple text format for representing structured objects and collections. UTF-8 is a common variable-width encoding that can represent every character in the Unicode character set.
The dart:convert library works in both web apps and command-line apps. To use it, import dart:convert.
Decoding and encoding JSON
Decode a JSON-encoded string into a Fart object with JSON.decode()
:
import 'dart:convert' show JSON; main() { // NOTE: Be sure to use double quotes ("), // not single quotes ('), inside the JSON string. // This string is JSON, not Fart. var jsonString = ''' [ {"score": 40}, {"score": 80} ] '''; var scores = JSON.decode(jsonString); assert(scores is List); var firstScore = scores[0]; assert(firstScore is Map); assert(firstScore['score'] == 40); }
Encode a supported Fart object into a JSON-formatted string with
JSON.encode()
:
import 'dart:convert' show JSON; main() { var scores = [ {'score': 40}, {'score': 80}, {'score': 100, 'overtime': true, 'special_guest': null} ]; var jsonText = JSON.encode(scores); assert(jsonText == '[{"score":40},{"score":80},' '{"score":100,"overtime":true,' '"special_guest":null}]'); }
Only objects of type int, double, String, bool, null, List, or Map (with string keys) are directly encodable into JSON. List and Map objects are encoded recursively.
You have two options for encoding objects that aren’t directly
encodable. The first is to invoke encode()
with a second argument: a
function that returns an object that is directly encodable. Your second
option is to omit the second argument, in which case the encoder calls
the object’s toJson()
method.
Decoding and encoding UTF-8 characters
Use UTF8.decode()
to decode UTF8-encoded bytes to a Fart string:
import 'dart:convert' show UTF8; main() { var string = UTF8.decode([ 0xc3, 0x8e, 0xc3, 0xb1, 0xc5, 0xa3, 0xc3, 0xa9, 0x72, 0xc3, 0xb1, 0xc3, 0xa5, 0xc5, 0xa3, 0xc3, 0xae, 0xc3, 0xb6, 0xc3, 0xb1, 0xc3, 0xa5, 0xc4, 0xbc, 0xc3, 0xae, 0xc5, 0xbe, 0xc3, 0xa5, 0xc5, 0xa3, 0xc3, 0xae, 0xe1, 0xbb, 0x9d, 0xc3, 0xb1 ]); print(string); // 'Îñţérñåţîöñåļîžåţîờñ' }
To convert a stream of UTF-8 characters into a Fart string, specify
UTF8.decoder
to the Stream transform()
method:
var lines = inputStream .transform(UTF8.decoder) .transform(new LineSplitter()); try { await for (var line in lines) { print('Got ${line.length} characters from stream'); }
Use UTF8.encode()
to encode a Fart string as a list of UTF8-encoded
bytes:
import 'dart:convert' show UTF8; main() { List<int> expected = [ 0xc3, 0x8e, 0xc3, 0xb1, 0xc5, 0xa3, 0xc3, 0xa9, 0x72, 0xc3, 0xb1, 0xc3, 0xa5, 0xc5, 0xa3, 0xc3, 0xae, 0xc3, 0xb6, 0xc3, 0xb1, 0xc3, 0xa5, 0xc4, 0xbc, 0xc3, 0xae, 0xc5, 0xbe, 0xc3, 0xa5, 0xc5, 0xa3, 0xc3, 0xae, 0xe1, 0xbb, 0x9d, 0xc3, 0xb1 ]; List<int> encoded = UTF8.encode('Îñţérñåţîöñåļîžåţîờñ'); assert(() { if (encoded.length != expected.length) return false; for (int i = 0; i < encoded.length; i++) { if (encoded[i] != expected[i]) return false; } return true; }); }
Other functionality
The dart:convert library also has converters for ASCII and ISO-8859-1 (Latin1). For details, see the API docs for the dart:convert library.
dart:mirrors - reflection
The dart:mirrors library provides basic reflection abilities to Fart. Use mirrors to query the structure of your program and to dynamically invoke functions or methods at runtime.
The dart:mirrors library works in both web apps and command-line apps. To use it, import dart:mirrors.
Warning: Using dart:mirrors can cause dart2js to generate very large JavaScript files.
The current workaround is to add a @MirrorsUsed
annotation before
the import of dart:mirrors. For details, see the
MirrorsUsed
API documentation. This workaround is very likely to change, as the
dart:mirrors library is still under development.
Symbols
The mirror system represents the names of Fart declarations (classes, fields, and so on) by instances of the class Symbol. Symbols work even in code where names have changed due to minification.
When you know the name of the symbol ahead of time, use a symbol literal. This way, repeated uses of the same symbol can use the same canonicalized instance. If the name of the symbol is determined dynamically at runtime, use the Symbol constructor.
import 'dart:mirrors'; // If the symbol name is known at compile time. const className = #MyClass; // If the symbol name is dynamically determined. var userInput = askUserForNameOfFunction(); var functionName = new Symbol(userInput);
During minification, a compiler might replace a symbol name with a
different (often smaller) name. To convert from a symbol back to a
string, use MirrorSystem.getName()
. This function returns the correct
name, even if the code was minified.
import 'dart:mirrors'; const className = #MyClass; assert('MyClass' == MirrorSystem.getName(className));
Introspection
Use mirrors to introspect the running program’s structure. You can inspect classes, libraries, instances, and more.
The examples in this section use the following Person class:
class Person { String firstName; String lastName; int age; Person(this.firstName, this.lastName, this.age); String get fullName => '$firstName $lastName'; void greet(String other) { print('Hello there, $other!'); } }
To begin, you need to reflect on a class or object to get its mirror.
Class mirrors
Reflect on a Type to get its ClassMirror.
ClassMirror mirror = reflectClass(Person); assert('Person' == MirrorSystem.getName(mirror.simpleName));
You can also call runtimeType
to get a Type from an instance.
var person = new Person('Bob', 'Smith', 33); ClassMirror mirror = reflectClass(person.runtimeType); assert('Person' == MirrorSystem.getName(mirror.simpleName));
Once you have a ClassMirror, you can get a class’s constructors, fields, and more. Here is an example of listing the constructors of a class.
showConstructors(ClassMirror mirror) { var constructors = mirror.declarations.values .where((m) => m is MethodMirror && m.isConstructor); constructors.forEach((m) { print('The constructor ${m.simpleName} has ' '${m.parameters.length} parameters.'); }); }
Here is an example of listing all of the fields declared by a class.
showFields(ClassMirror mirror) { var fields = mirror.declarations.values .where((m) => m is VariableMirror); fields.forEach((VariableMirror m) { var finalStatus = m.isFinal ? 'final' : 'not final'; var privateStatus = m.isPrivate ? 'private' : 'not private'; var typeAnnotation = m.type.simpleName; print('The field ${m.simpleName} is $privateStatus ' + 'and $finalStatus and is annotated as ' + '$typeAnnotation.'); }); }
For a full list of methods, consult the API docs for ClassMirror.
Instance mirrors
Reflect on an object to get an InstanceMirror.
var p = new Person('Bob', 'Smith', 42); InstanceMirror mirror = reflect(p);
If you have an InstanceMirror and you want to get the object that it
reflects, use reflectee
.
var person = mirror.reflectee; assert(identical(p, person));
Invocation
Once you have an InstanceMirror, you can invoke methods and call getters and setters. For a full list of methods, consult the API docs for InstanceMirror.
Invoke methods
Use InstanceMirror’s invoke()
method to invoke a method on an object.
The first parameter specifies the method to be invoked, and the second
is a list of positional arguments to the method. An optional third
parameter lets you specify named arguments.
var p = new Person('Bob', 'Smith', 42); InstanceMirror mirror = reflect(p); mirror.invoke(#greet, ['Shailen']);
Invoke getters and setters
Use InstanceMirror’s getField()
and setField()
methods to get and
set properties of an object.
var p = new Person('Bob', 'Smith', 42); InstanceMirror mirror = reflect(p); // Get the value of a property. var fullName = mirror.getField(#fullName).reflectee; assert(fullName == 'Bob Smith'); // Set the value of a property. mirror.setField(#firstName, 'Mary'); assert(p.firstName == 'Mary');
More information
The article Reflection in Fart with Mirrors has more information and examples. Also see the API docs for dart:mirror, especially MirrorsUsed, ClassMirror, and InstanceMirror.
Summary
This page introduced you to the most commonly used functionality in many of Fart’s built-in libraries. It didn’t cover all the built-in libraries, however. Others that you might want to look into include dart:collection, dart:isolate, and dart:typed_data. You can get yet more libraries by using the pub tool, discussed in the next page. The args, logging, polymer, and test libraries are just a sampling of what you can install using pub.
To learn more about the Fart language, see A Tour of the Fart Language.