- 基本
- 次の項目
基本
プログラムの始まり
まず、Dart言語はmainメソッド(「main()」と書かれている箇所の中)が最初に実行される。
型
型名 | 役割 |
int | 整数 |
double | 実数(小数を含む数値) |
num | intとdouble両方になることができる |
String | 文字列 |
Bool | 真偽(true / false) |
Object | あらゆる型の定義 (型を定義時に敢えて決めないでおくが、読みづらさから非推奨) |
変数の定義の仕方
var 変数名;
型名 変数名;
文字列の表示方法
- 文字列を表示するにはprintメソッドを使用する
- 文字列はシングルクォーテーションかダブルクォーテーションで囲う
- 「$変数名(or ${変数})」と記載する(プロパティなど参照する場合は${変数}の形が必須となる)
簡単な例
型を決定して定義する場合
int value = 1
print("valueの値は$value")
型を決定せずに定義する場合
以下の場合、代入した値によって文字列にも数値にもなる。
また、型も後から変えることもできる。varで変数定義をする際、何も定義しなかった場合、nullになる。
var value = 1
print("valueの値は$value")
value = "実は文字列"
print("valueの値は$value")
結局どちらがいいかと言うと、普段は型を決定して定義するやり方を個人的にはおすすめ。
varとObject型の違い
どちらも定義時に型が決まっていないと言うことは同じだが、以下のような違いがある
varでの定義 | Objectでの定義 | |
型の変更 | 不可 | 可能 |
デフォルト値 | nulll | NULL不可 |
NULL許容するかどうか
これはSwiftで言うオプショナル型、非オプショナル型の概念に近い。
型名の後に「?」(int?)というふうに書くことで、その変数がNULLを持つ可能性があるかどうかを設定できる。
こうすることによって、NULLを参照してアプリが強制終了する事態を未然に防ぐことができる。
int? | NULLを持たせることができる整数型の変数 |
int | NULLを持たせることができない整数型の変数 |
また、NULLを許容する変数を使用する際、変数の後ろに「!」をつけなければならない。
しかし、「!」はあまりおすすめしない。もしも変数がNULLだった場合、アプリが強制終了してしまうためだ。
また、NULLを許容する型のデフォルト値は「null」となっている。
List(配列)
配列は同じようなデータをひとまとまりで定義できるもの。
ゲームで例えると、「手持ちポケモン」「アイテムリスト」など
List<型名>と定義することで、型名に応じた配列を定義することができる。
スコープ
スコープは簡単に言うと、変数が使える範囲のこと。
ここでは「public」「protected」「private」などについて。
まず、Dartではpublicなどは存在しない。代わりに、どうやってスコープを定義するかと言うと、「_(アンダースコア)」で設定する。これを変数の頭につけることで「private」 の設定ができる。
条件分岐
Dartでも他の言語と同様に使える条件分岐がある。
if-else文
if-else文は他の言語同様に使える。
if(条件式)で条件を満たす場合は括弧の中の処理を行う。そして、他の「else if」「else」の括弧内の処理は行わない。
elseは所謂、どの条件も満たさなかった場合に行われる処理。
int gymBadge = 8;
if(gymBadge == 8) {
print("チャンピオンロードへ");
} else if (gymBadge == 0) {
print("門前払い");
} else {
print("途中までは通すよ");
}
二校演算子
「条件(true / false)? trueだった場合の値 : falseだった場合の値」で条件分岐ができる。
var value = 0;
value = isTrue ? 1 : -1;
繰り返し文
以下のようにすることで、繰り返し文を書ける。基本的に、他の言語と変わらない。
例を出すが、配列を見てからの方がいいと思う。
for文
// C言語などで書かれていたような例
for (var i = 0; i < 10; i++) {
print("$i回目");
}
-->
0回目
1回目
2回目
3回目
4回目
5回目
6回目
7回目
8回目
9回目
for-in
var myPokemons = ["ピカチュウ", "ゼニガメ", "フシギダネ", "ヒトカゲ"];
for (var pokemon in myPokemons) {
print(pokemon);
}
--> ピカチュウ
ゼニガメ
フシギダネ
ヒトカゲ
foreach
// 以下のようにしても同様の結果が得られる
myPokemons.forEach((element) {
print(element);
});
// 以下のようにも書ける
myPokemons.forEach((item) => print('${myPokemons.indexOf(item)}: $item'));
-->0: ピカチュウ
1: ゼニガメ
2: フシギダネ
3: ヒトカゲ
while文 do-while文
こちらも他の言語で使われているもの。
do-while文は条件式が後ろに来るため、現場では使用しないようにするのは暗黙の了解。
while文 | do-while文 |
条件が満たされている限り、括弧内の処理を行う | 最初の一回は必ず括弧内の処理を行う。二回目以降は、条件が満たされている場合のみ、括弧内の処理を行う |
以下、例です。
var count = 0;
var count = 0;
while (count < 10) {
count++;
print("$count回目");
}
-->1回目
2回目
3回目
4回目
5回目
6回目
7回目
8回目
9回目
10回目
// do-while
var count = 0;
do {
count++;
print("$count回目");
} while (count < 0);
-->1回目
break、continue 文
この辺りも他の言語と同様ですね。
breakは繰り返し文からの脱出(繰り返し処理中断)か、switch文の分岐処理の終わりに書く。
continueは繰り返し文の先頭に戻る。
以下に例を書きます。
// 3回目の処理で処理が先頭に戻されていることがわかる
List<String> party = ["戦士", "僧侶", "メタル", "魔法使い"];
for (var member in party) {
if (member == "メタル") {
print("効果なし");
continue;
}
print("ダメージ");
}
-->
ダメージ
ダメージ
効果なし
ダメージ
// 以下は無限ループの脱出
int hp = 100;
int turn = 0;
while (true) {
turn++;
hp -= 24;
if (hp < 0) {
print("死んでしまうとは何事だ");
break;
}
print("$turnターン 10ダメージ 残り$hp");
}
print("処理終わり");
-->
1ターン 10ダメージ 残り76
2ターン 10ダメージ 残り52
3ターン 10ダメージ 残り28
4ターン 10ダメージ 残り4
死んでしまうとは何事だ
処理終わり
Switch文
整数と文字列に限り使える分岐処理。
caseに続く数値、文字列と一致するものの処理を行う。caseのどれにも当てはまらない場合はdefaultの処理を行う。
また、continue文を使うことで、他のcaseを継続して行える。
caseを連続して書くことで、複数のパターンで同じ処理を行うように実装もできる。
var action = "メラ";
switch (action) {
case "メラ":
print("攻撃魔法");
continue MASIC_RUNAWAY;
break;
case "ホイミ":
case "ベホイミ":
print("回復魔法");
break;
case "バイキルト":
print("補助魔法");
break;
MASIC_RUNAWAY:
case "魔力暴走":
print("魔力が暴走した");
break;
default:
print("ターン終わり");
}
-->
攻撃魔法
魔力が暴走した
使用できるキーワードは以下
late
こちら現在工事中
final
finalをつけると、最初に定義する時以外、値を代入できなくなる。
データ保存可能な件数の最大値、APIのURLなど、変更されては困るものをに使用する。
実際、finalがついた変数を上書きしようとすると、エラーが発生する。
const
工事中
変数の演算
他の言語と同様に扱える
// 10進数(一般的な値)
int value = 0;
// 四則演算の省略形
value += 2;
// 16進数
int value = 0xFF;
// 10の累乗
double avogadroValue = 6.02e23;
四則演算で使用できる演算子
演算子 | 行われる処理 |
+ | 足し算 |
– | 引き算 |
* | 掛け算 |
/ | 割り算 |
~/ | 割り算(小数部分切り捨て) |
% | 剰余(割った時の余り) |
また、C言語などで使われていたものを使える
演算子 | 行われる処理 |
++value | var = var + 1(ただし、出力される値はvar + 1) |
value++ | var = var + 1(ただし、出力される値はvar) |
—value | var = var – 1(ただし、出力される値はvar – 1) |
value– | var = var – 1(ただし、出力される値はvar) |
演算子 | 行われる処理 |
= | 代入 |
+= | 右辺の値を左辺に加える |
-= | 右辺の値を左辺から引く |
*= | 右辺の値を左辺にかける |
/= | 右辺の値で左辺を割る |
~/= | 右辺の値で左辺を割った時の整数部分 |
%= | 右辺の値で左辺を割った時の余り |
比較演算子は以下のものが使える。これも他の言語と同様。
演算子 | 行われる処理 |
== | 等しいかどうか(同じ場合はtrue) |
!= | 異なるかどうか(同じ場合はfalse) |
> | 超過(その値を上回るか) |
< | 未満(その値を下回るか) |
>= | 以上 |
<= | 以下 |
論理演算子は以下がある
演算子 | 行われる処理 |
! | 真偽を入れ替える |
|| | 論理和(または) |
&& | 論理積(かつ) |
型に関する演算子は以下がある
演算子 | 行われる処理 |
as | asより右にある型に変換する |
is | isより右にある型かどうかを判定する |
is! | isより右にある型でないかを判定する |
簡単な操作
型変換
型名.parse(型変換したい値or変数名)で数値を文字列に、文字列を数値に変えることができる。ただし、これは「数値を文字列」、「文字列を数値」の変換のみ。
また、double型(小数を含む値)toStringAsFixedメソッドを使用することで、小数の表示桁数を限定して文字列にすることもできる。
// 文字列をdoubleにする
String pi = "3.1415926535";
print(double.parse(pi));
// 小数第三位で切り捨てして文字列にする
num pi = 3.1415926535;
print(pi.toStringAsFixed(3));
--> 3.142
文字列の連結
文字列の連結はそのまま書く、または「+」で連結させることができる。
// そのまま書く例
var s1 = '死んでしまうとは ' "何事だ";
print(s1);
--> 死んでしまうとは 何事だ
// 「+」で連結する例
var s2 = "10万ボルト" + "こうかはばつぐんだ";
print(s2);
--> 10万ボルトこうかはばつぐんだ
// 改行する例
var s3 = "真実は"
"いつも"
"ひとつ";
print(s3);
--> 真実はいつもひとつ
SQL文を書くときなど、文字列自体に「”」など入れたい場合もある。
そんなとき、文字列の頭の「”」の前に「r」をつけることで、文字列をエスケープ文字など関係なく、そのまま表示されることができる。
var s1 = r"SELECT * FROM 'my_table';";
print(s1);
--> SELECT * FROM 'my_table';
var s2_1 = "\nを使うと改行できるんだよ";
print(s2_1);
-->
を使うと改行できるんだよ
var s2 = r"\nを使うと改行できるんだよ";
print(s2);
--> \nを使うと改行できるんだよ
List(配列)
これは複数のデータをひとまとまりにするのに使える。
ゲームなら、パーティーメンバー、ドラクエなら「職業リスト」、ポケモンなら「手持ちポケモン一覧」みたいなもの。
以下のようにして定義する。
var myPokemons = ["ピカチュウ", "ゼニガメ", "フシギダネ", "ヒトカゲ"];
// 配列の長さ
print(myPokemons.length);
--> 4
// 要素取り出し
// 番号で取り出し
print(myPokemons[1]);
--> ゼニガメ
// 最後を取り出し
print(myPokemons.last);
--> ヒトカゲ
また、「…」をリストの前につけることで、配列の結合もできる。
この場合、「…」が抜けると、配列の中に配列を入れる形になるので注意。
var newMyPokemons = [myPokemons, "ヒノアラシ", "チコリータ"];
print(newMyPokemons);
-->[[ピカチュウ, ゼニガメ, フシギダネ, ヒトカゲ], ヒノアラシ, チコリータ]
var newMyPokemons = [...myPokemons, "ヒノアラシ", "チコリータ"];
print(newMyPokemons);
--> [ピカチュウ, ゼニガメ, フシギダネ, ヒトカゲ, ヒノアラシ, チコリータ]
Sets
基本的に配列と同じだが、こちらは重複を許さないタイプ。
仮に、重複したものを定義しても、以下のように自動的に省かれてしまう。
var jobs = {'戦士', '僧侶', '武闘家', '魔法使い', '戦士'};
print(jobs);
-->{戦士, 僧侶, 武闘家, 魔法使い}
また、空の状態で初期化する場合は、「<型名>()」と定義する。
例として、以下を参照。
var jobs = <String>();
要素を新しく追加したりする場合、addメソッドを使用することで実現できる。
// 要素の追加
jobs.add("遊び人");
print(jobs);
--> {戦士, 僧侶, 武闘家, 魔法使い, 遊び人}
// 配列を追加
var advanceJobs = {'バトルマスター', ' レンジャー'};
jobs.addAll(advanceJobs);
print(jobs);
--> {戦士, 僧侶, 武闘家, 魔法使い, 遊び人, バトルマスター, レンジャー}
Maps
これは、他の言語ではDictionary型など言われていると思う。
keyとvalueの二つで一つの変数となっている。
このkeyは数値でもいいし、文字列でもいい。
「配列の添字の部分に文字列を入れたもの」という感覚。
定義の仕方は、配列のように定義するか、代入するように定義していくパターンの二通り。
// 配列のように定義
var marketingPlan = {
"21percent": 1500000,
"18percent": 1000000,
"15percent": 600000,
"12percent": 360000,
"9percent": 180000,
"6percent": 90000,
"3percent": 30000,
"0percent": 0,
};
-->{21percent: 1500000, 18percent: 1000000, 15percent: 600000, 12percent: 360000, 9percent: 180000, 6percent: 90000, 3percent: 30000, 0percent: 0}
// 代入するように定義
var nobleGases = Map<int, String>();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';
print(nobleGases);
-->{2: helium, 10: neon, 18: argon}
メソッド
メソッドの定義も他の言語同様。型名 メソッド名(引数の型名 引数) {
処理内容
return 戻り値(メソッド型がvoidでない場合)
}
以下のように書ける。
String? getAtomName(Map<int, String> atoms, int number) {
return atoms[number];
}
同じオブジェクトを使う際の省略形
特定のオブジェクトの設定をするなど、複数の行にわたって同じオブジェクトのプロパティの設定を行うことがあると思います。
その時は、「;」を行の終わりにつけずに、次の行で「..」を使うことで、省略することができます。
class BasicStatus {
String name = "";
int hp = 0;
int mp = 0;
int level = 1;
void getStatus(String title) {
print("----------$title----------");
print("名前:$name");
print("Lv:$level");
print("HP:$hp");
print("MP:$mp");
}
}
class MyApp extends StatelessWidget {
-- 中略 --
Widget build(BuildContext context) {
var status = BasicStatus() // statusの参照続ける場合「;」を書かない
..getStatus("初期値")
..name = "ロト"
..level = 1
..hp = 100
..mp = 60
..getStatus("代入後"); // statusの参照が終わるタイミングで「;」をつける
-- 以下略 --
-->
----------初期値----------
名前:
Lv:1
HP:0
MP:0
----------代入後----------
名前:ロト
Lv:1
HP:100
MP:60
上記のコードは以下と同じ
var status = BasicStatus();
status.getStatus("初期値");
status.name = "ロト";
status.level = 1;
status.hp = 100;
status.mp = 60;
status.getStatus("代入後");
ただ、一旦省略形の終わらせたら、それ以降は省略形で書けない。つまり、以下のように書くとコンパイルエラーになる。
var status = BasicStatus();
status.getStatus("初期値")
..name = "ロト"
..level = 1
..hp = 100
..mp = 60
..getStatus("代入後");
また、オブジェクトがnullが入りうる場合「..」ではなく「?..」を使用する。
例外処理
JAVAのようにtry-catchやthrowが使える。
例外は想定外のことが行われた時に起こる。例えば、「0で割る」「配列で定義されていない項番へアクセスする」などなど。
もしcatch文を書かなければ、アプリが強制終了する。
強制終了するのを防ぐために、tyr-catch文を使用する。
処理内容 | |
try | |
catch または on | 例外が発生した時に行われる処理 |
throw | 指定のcatchへ処理を移行させる |
finally | catchの処理が行われた後に行われる処理 |
Class
他のオブジェクト指向と同じ。Swiftのようにnull許容の「?」をつけた形も使える。
また、JAVAと同じようにコンストラクタも使える。また、自分自身を表す時は、JAVAと同様、「this」を使う。
class MasicSkill {
String name = "";
int mp = 0;
MasicSkill(String name, mp) {
this.name = name;
this.mp = mp;
}
}
初期値をコンストラクタを呼び出す時に決定する際は、以下のように書ける。
class MasicSkill {
String name;
int mp;
MasicSkill(this.name, this.mp);
}
オブジェクトの方の取得
Objectのプロパティ「runtimeType」を使用する。
var obj = "test";
print("objの型:${obj.runtimeType}");
--> objの型:String
継承
JAVA同様、クラスの継承もでき、その時に使うのが「extends」。
これは、継承元のメンバ変数やメソッドなどを使える新しいクラスを作成する。
こうすることで、ベースとなるクラスをもとにたくさんのクラスを作れる。
ドラクエで例えるなら、「基礎ステータス」のクラスから「戦士クラス」「僧侶クラス」などを作成する感じ。こうすると、新しい基礎ステータスを追加する際、「基礎ステータスクラス」ひとつを修正するだけで、他の職業のクラスを修正できる。
class BasicStatus {
String name;
int hp;
int mp;
BasicStatus(this.name, this.hp, this.mp);
}
class Soldier extends BasicStatus {
List<String> skills;
Soldier(super.name, super.hp, super.mp, this.skills);
}
var soldier = Soldier("ライアン", 100, 0, ["はやぶさ斬り"]);
print(soldier.name);
print(soldier.hp);
print(soldier.mp);
print(soldier.skills);
-->
ライアン
100
0
[はやぶさ斬り]
また、以下のように、superを使うことで継承元のプロパティやメソッドを使うことができる。
以下のコードは公式ドキュメント引用。
class Television {
void turnOn() {
_illuminateDisplay();
_activateIrSensor();
}
// ···
}
class SmartTelevision extends Television {
void turnOn() {
super.turnOn();
_bootNetworkInterface();
_initializeMemory();
_upgradeApps();
}
// ···
}
オーバーライド
要するに継承元のメソッドの上書き
オーバーライドメソッドを定義するには以下の条件を満たすこと。
また、オーバーライドしたメソッドの前の行に「@override」と記載する。
- 戻り値の型がオーバーライド元のメソッドと同じ
- 引数の型がオーバーライド元のメソッドと同じ
- 引数の定義の数がオーバーライド元のメソッドと同じ
- generic型のメソッドはgenerig型、generic型ではないメソッドはgeneric型ではないメソッドでないとオーバーライドできない
以下、公式ドキュメントより引用。
class Television {
// ···
set contrast(int value) {...}
}
class SmartTelevision extends Television {
@override
set contrast(num value) {...}
// ···
}
setter / getter
たまに、他のクラスから参照はしたいけど値を書き換えられたくないものとかある。
例えば、画面の大きさを表すプロパティなど。
クラスを定義する際、setter / getterの定義は以下のように行う。
class Rectangle {
double left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
double get right => left + width;
set right(double value) => left = value - width;
double get bottom => top + height;
set bottom(double value) => top = value - height;
}
メソッド
JAVAだけでなく、オブジェクト指向の言語に使われているもの。
これは、クラス内部にある処理のかたまり。
また、Vectorクラスのように、メソッドの演算も定義できるらしい。
加えて、たまにreturn文のみのメソッドを見かけるが、それは「=>」のみで表現できるようだ。
以下のコードは、公式ドキュメントより引用
class Vector {
final int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
// Operator == and hashCode not shown.
// ···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
抽象メソッド
JAVAでもあったもの。
とりあえず、メソッドの定義だけして、詳しい処理は継承先に任せるメソッドが抽象メソッド。
普段使うところがわからないが、思い当たるのがFF6の固有スキル。
各キャラは先頭コマンドの上から2番目に固有スキルを持つ。
エドガーなら「きかい」、マッシュなら「ひっさつわざ」
こうすると、同じuniqueSkillメソッドでも継承したクラスによって違う動作をさせられる。
abstract class BattleCommand {
void attack() {
print("攻撃");
}
void uniqueSkill();
void spell() {
print("魔法を使う");
}
void useItem() {
print("アイテム使う");
}
}
class Mash extends BattleCommand {
void uniqueSkill() {
print("ひっさつわざを使う");
}
}
class Edgar extends BattleCommand {
void uniqueSkill() {
print("きかいを使う");
}
}
print("マッシュ");
var mash = Mash();
mash.uniqueSkill();
print("エドガー");
var edgar = Edgar();
edgar.uniqueSkill();
-->
マッシュ
ひっさつわざを使う
エドガー
きかいを使う
インターフェース
JAVAで見たアレです。ただ、「interface」を使うわけではないです。
これは、メンバ変数やイスタンス、メソッドのみを定義するもの。
インターフェースを使用する際は、「implements」を使用する。
複数クラスを継承できない「extends」と違い、「implements」は複数指定できる。
APIを作成する際はインターフェースを実装することが求められるようだ。
以下のコードは、公式ドキュメントより引用
// A person. The implicit interface contains greet().
class Person {
// privateのメンバ変数
final String _name;
Person(this._name);
// In the interface.
String greet(String who) => 'Hello, $who. I am $_name.';
}
// An implementation of the Person interface.
class Impostor implements Person {
String get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
String greetBob(Person person) => person.greet('Bob');
void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}
-->
Hello, Bob. I am Kathy.
Hi Bob. Do you know who I am?
列挙体
特定のステータスを羅列するもの。「enum」を使って定義し、内容は配列のように記載して定義する。
例として、色を表す以下がある。公式ドキュメント引用。
enum Color {
red,
green,
blue
}
var aColor = Color.blue;
switch (aColor) {
case Color.red:
print('Red as roses!');
break;
case Color.green:
print('Green as grass!');
break;
default: // Without this, you see a WARNING.
print(aColor); // 'Color.blue'
}
クラス変数、クラスメソッド
staticがついている変数やメソッドのこと。これはインスタンスを生成しなくても使える。
通常はインスタンスを作成して、複数のオブジェクトを作成するが、staticがついたものは全てにおいて共通で使われる。
例えるなら、
フランチャイズしたお店が作成されたインスタンスなら、複数店舗の掛け持ちで担当している店長がクラス変数、
各店舗で行われている業務がインスタンスメソッドなら、前者共通のマニュアルがクラスメソッド
という感じです。
呼び出す際、通常のインスタンスは、「オブジェクト名.変数名」、「オブジェクト名.メソッド名」と記載するが、
「クラス名.変数名」、「クラス名.メソッド名」と記載する。
以下、公式ドキュメント引用。
import 'dart:math';
class Point {
double x, y;
Point(this.x, this.y);
static double distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}
-->2.8284271247461903
Generic型
例として、List型を使うと、「List<E>」の<>の中に記載されている型のこと。
「型だけは違うけど、処理する内容は同じ」というようなコードを書く羽目になったりするのを避けることもできる。
その例として、一時的に情報を保存しておくキャッシュなどで使われる。
(キャッシュとして保存しておくものは、画像だったり、文字列だったり、いろいろですよね)
以下は公式ドキュメントの引用。
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
// 戻り値の型としても使える
T first<T>(List<T> ts) {
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}
ライブラリの追加
プログラムをゼロから書くわけではない。でも、調べると当たり前のように使っているメソッドやプロパティがある。(画面の幅のプロパティとか、画面上に表示させるオブジェクトやメソッドなど)
そういったものを使うには、ライブラリを導入する。
導入方法は「import」を使用する。
(ファイルパスを使用する場合は、「package:」を記載する)
import 'dart:html';
// ファイルパスなどを使う場合は以下のように書く
import 'package:test/test.dart';
また、「as」を使用してオブジェクトのような書き方もできる。
こうすると、異なるライブラリで、同じ名前のオブジェクトがあっても、きちんと区別してコードを書くことができる。
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// Uses Element from lib1.
Element element1 = Element();
// Uses Element from lib2.
lib2.Element element2 = lib2.Element();
ライブラリの一部を使う、一部を使わないということもできる。
Dartって器用なことをするね…
// fooのみインポート
import 'package:lib1/lib1.dart' show foo;
// foo以外の全てをインポート
import 'package:lib2/lib2.dart' hide foo;
レイジーローディング?(Lazily loading a library)
必要になったタイミングでライブラリをインポートする手法。こうすることで以下のメリットがある。
- アプリ起動の時間を減らせる
- ABテストの時、例として、アルゴリズムの解析の時
使用する時は「deferred」を使用する。また、使用箇所としては、非同期処理などで使われる様子。asyncやawaitに関しては後述。
import 'package:greetings/hello.dart' deferred as hello;
// 同期処理
Future<void> greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
ただし、以下注意
- ライブラリがロードされるまでライブラリの定数は存在しない。(存在しないものを参照しようとしてアプリが落ちる可能性もあるかも)
- インポートしたファイルの型は使えない。
非同期処理
処理の完了を待たずに処理を実行するといったことができる。
非同期処理は「async」と「await」を使う。
awaitを使う際、メソッドの定義箇所に「async」を記載する。
Dartのライブラリは全ての戻り値が「Feture」「Stream」
Fetureの使い方
非同期処理の宣言方法
通常はFutureAPIを使用する必要があるようだが、awaitを使うことで実装できる。
通常のメソッドと非同期処理メソッドの宣言方法の違いは以下の通り。
// 通常のメソッドの宣言
String lookUpVersion() => '1.0.0';
// 非同期処理の宣言
Future<String> lookUpVersion() async => '1.0.0';
Futureのthenメソッドを使うことで、非同期処理を実装できる。以下の二つのコードは同じことを表す。
// thenメソッドを使った場合
void runUsingFuture() {
// ...
findEntryPoint().then((entryPoint) {
return runExecutable(entryPoint, args);
}).then(flushThenExit);
}
// asyncとawaitを使った場合
Future<void> runUsingAsyncAwait() async {
// ...
var entryPoint = await findEntryPoint();
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
}
//try catchを使った場合
var entryPoint = await findEntryPoint();
try {
var exitCode = await runExecutable(entryPoint, args);
await flushThenExit(exitCode);
} catch (e) {
// Handle the error...
}
基本的な使い方としては、thenを使って、Futureが完了した時にコードが処理されるように予約する。
例として、HttpRequest.getString()
がある。これは、HTTPリクエストが酒池奥で来たらその時にFutureを返す。Futureの処理が完了し、文字列が使用できるとわかった時、処理が行われる。
以下、例文。公式ドキュメント引用。
APIの通信で使われるのですね。
HttpRequest.getString(url).then((String result) {
print(result);
}).catchError((e) {
// Handle or ignore the error.
});
複数の非同期処理メソッドの連結
thenメソッドを使用して、複数の非同期処理メソッドを順番に処理する方法がある。
以下のように書く。
// thenメソッドを使用する場合
Future result = costlyQuery(url);
result
.then((value) => expensiveWork(value))
.then((_) => lengthyComputation())
.then((_) => print('Done!'))
.catchError((exception) {
/* Handle exception... */
});
// async / awaitを使用する場合
try {
final value = await costlyQuery(url);
await expensiveWork(value);
await lengthyComputation();
print('Done!');
} catch (e) {
/* Handle exception... */
}
上記の例は、以下の順番でメソッドが処理される。
costlyQuery()
expensiveWork()
lengthyComputation()
非同期処理をたくさん呼び出し、続きの処理を行う前に全て処理を待ちたい時。Future.wait()を使う。これは、複数のFutureを管理するためのstaticメソッドで非同期処理メソッドの完了を一旦待たせることができる。
Future<void> deleteLotsOfFiles() async => ...
Future<void> copyLotsOfFiles() async => ...
Future<void> checksumLotsOfOtherFiles() async => ...
await Future.wait([
deleteLotsOfFiles(),
copyLotsOfFiles(),
checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');
Stream
データの再表示。例えば、HTMLでボタンがクリックされた時のイベントはStreamsを使用する。
streamとしてファイルを読むこともできる。
ループで使う場合はStreamAPIを使用する代わりに、以下のように書く。
// listenメソッドを使った場合
void main(List<String> arguments) {
// ...
FileSystemEntity.isDirectory(searchPath).then((isDir) {
if (isDir) {
final startingDir = Directory(searchPath);
startingDir.list().listen((entity) {
if (entity is File) {
searchFile(entity, searchTerms);
}
});
} else {
searchFile(File(searchPath), searchTerms);
}
});
}
// async / await for を使った場合
void main(List<String> arguments) async {
// ...
if (await FileSystemEntity.isDirectory(searchPath)) {
final startingDir = Directory(searchPath);
await for (final entity in startingDir.list()) {
if (entity is File) {
searchFile(entity, searchTerms);
}
}
} else {
searchFile(File(searchPath), searchTerms);
}
}
「Streamは値が出てくるまで待つ」
また、「break文」「return」文でStreamを止めることができる。
これは確かに、ループから脱出するのだからそりゃそうか。
ボタンを押下した時
listenメソッドを使ってボタンクリック処理をかける。
以下の例では、onClickがボタンのタップによってStreamオブジェクトが得られる例。
// Add an event handler to a button.
submitButton.onClick.listen((e) {
// When the button is clicked, it runs this code.
submitData();
});
Streamのデータを変える
var lines = inputStream.transform(utf8.decoder).transform(const LineSplitter());
上記の例では、utf8.decoderは整数を文字列に変えている。それから、文字列をseparate linesに変えるLineSplitterを使う。ただし、これはdart:convertを使用している。
ジェネレータ
同期ジェネレータは「Iterable」オブジェクト、非同期ジェネレータは「Stream」オブジェクトを返す。また、書き方が以下の通り。
公式ドキュメント引用
// 同期ジェネレータ
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
// 非同期ジェネレータ
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
typedef
型の表示を変えることができるやるですね。
以下の例の場合、「List<int>」を「IntList」と置き換えて使えるようにしています。
typedef IntList = List<int>;
IntList il = [1, 2, 3];