前回では、Flutterを用いてAndroid、iOSアプリ開発環境を構築して、いつでも開発できる状態になりました。
👇前回の記事はこちら
次は以下の2つについて学んでいこう✊
①Dartの書き方や基礎、
②Flutterの構造、フレームワーク理解
今回は、①Dartの勉強やコードの検証で使われる” DartPad “を使いDartの文法を学んでいきます。
⚠️今回はかなりの基礎内容となるので、プログラミング経験が少しでもある方は流し読みで問題ありません。
Contents
DartPadとは
✅ web上で組込みライブラリを使用して簡易的な動作検証を行えるツール
対応するライブラリはdart:*(dart: ioは動作しないので注意)
✅ Dart コード、API、HTML、CSSをサポート、ブラウザ上で書いたコードが即時実行可能
✅ dart-coreライブラリが使えるため、以下の基本的な機能が使用可能
- 文字列
- 数値
- コレクション
- 日付
- エラー処理
- URI
つまり、簡単なコードの動きチェックと文法の勉強に最適ですね◎
👇UIはこんな感じ
Hello Worldをコンソールに表示
- void : 返り値(return)がないことの宣言
- main() : 一番最初に実行される関数
- print() : コンソールに表示する
1 2 3 4 |
// 最初に実行するコードはmain関数の中に記述 void main() { print("Hello, world!"); // Hello, world!と表示 } |
変数
✅ 変数宣言 var:型変更不可 ⇔ dynamic:型変更可
✅ final : 変数宣言時、値の変更不可 ⇔ const:コンパイル時、値の変更不可
✅ 特に宣言がない場合は基本的にNullを許さないNull非許容型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
void main() { int number = 5; // 整数 double weight = 3.7; // 小数 String name = "Mike"; // 文字列 String country = 'Japan'; // 文字列 bool judge = False; // もしくはTrue // varは最初の代入時に型推論 var a = 123; a = 321; // a = "Hello"; // エラー: 型の変更はできない // dynamicは何型であってもよい dynamic b = 123; b = 321; b = "Hello"; // finalは値の変更を不可に final c = 123; // c = 321; // エラー: 値の変更はできない // c = "Hello"; // エラー: 値の変更はできない // constも値の変更を不可に( コンパイル時に確定) const d = 123; // d = 321; // エラー: 値の変更はできない // d = "Hello"; // エラー: 値の変更はできない } |
Nul許容型の変数
✅ null値を保持できる変数の宣言は型の最後にクエスチョンマーク(?)をつける
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void main() { int? a; int? b; List<String> c = ['one', 'two', 'three']; List<String>? d; List<String?> e = ['one', null, 'three']; a = null; print("a is $a."); // a is null. print("b is $b."); // b is null. print("c is $c."); // c is ['one', 'two', 'three']. print("d is $d."); // d is null. print("e is $e."); // e is ['one', null, 'three']. } |
call関数について
✅ 通常の関数の呼び出しと同じ挙動をする
1 2 3 4 5 6 7 8 9 10 |
List<int> requestNumber() { // ... 何か数字を取得する処理 int call(int a, int b) => a + b; } void main(){ final test = Test(); // インスタンス化 int value = test(1, -2); // test.callとする必要ない print(value); // -1 } |
✅ 呼び出したい関数オブジェクトが nullable な場合、コードを簡潔に書くことが可能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class ButtonWidget extends StatelessWidget { final VoidCallback? onTap; // callback関数 nullの場合は何もしない。 ...省略 OutlinedButton( onPressed: () => onTap?.call(), // ?.call() で1行で書ける // onPressed: () { // if (onTap != null) { // nullチェックして呼び出す必要はない // onTap!(); // } // } ), ...省略 } |
static修飾子
✅ クラスのプロパティ/メソッドをインスタンス化せずに使用可能
✅ static修飾子を宣言したプロパティ/メソッドを静的変数/静的メソッドと呼ぶ
⇨「静的」: プログラム実行中、ずっと存在する(値を変更できる)
⇨「動的」: プログラム実行中、生成または破棄される
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// クラス内の変数や関数に使用するとインスタンス化せずに使用可能 class Car { static int doors = 4; static void drive() { print("I am driving."); } } void main() { print(Car.doors); // 4 Car.drive(); // I am driving. } // ページ遷移での利用方法 routes: { '/': (context) => SignUpScreen(), '/login': (cotext) => LoginScreen(), } route: { LoginScreen.id: (context) => LoginScreen(), } class LoiginScreen { static String id = '/login' } |
String→int, double, DateTimeに変換
✅ <変更後の型>.parse(<変更したい値>)
1 2 3 4 5 |
void main() { print(int.parse('10000')); // String⇨int変換 print(double.parse('6.6')); // String→double に変換 print(DateTime.parse('2021-08-25 20:30:40')); // String→DateTime に変換 } |
条件分岐(if, switch)
✅ 条件式が長くなる場合はswitchを使う
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
void main() { var a = 1; print("----- if文 -----"); if (a < 1) { print("Hello!"); } else if (a < 2) { print("I'm sleeping.."); }else{ print("Good night!"); } // I'm sleeping print("----- switch文 -----"); switch(a){ case 0: print("Good morning!"); break; case 1: print("Good afternoon!"); break; default: print("Good evening!"); break; } // Good afternoon } |
List
✅ 配列で重複可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void main() { print("----- List -----"); var names = ["Rav4", "Corolla", "Yaris", "Harrier"]; print(names); // [Rav4, Corolla, Yaris, Harrier] print(names[1]); // Corolla print("----- Empty List -----"); var list1 = <int>[]; list1.add(123); list1.add(456); list1.add(789); list1[1] = 999; print(list1); // [123, 999, 789] // 他のリストの書き方 List<int> list2 = []; } |
Set
✅ 要素の重複不可
✅ 空要素を作成時、頭にSetを配置
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void main() { print("----- Set -----"); var set1 = {"Ami", "Kana", "Shiho", "Misako"}; set1.add("Aya"); print(set1); // {Ami, Kana, Shiho, Misako, Aya} set1.add("Miho"); print(set1); // {Ami, Kana, Shiho, Misako, Aya, Miho} print("----- 空のセット -----"); Set set2 = {}; set2.add("kasai"); print(set2); // { kasai } } |
Map
✅ key : value の組み合わせで値格納可能
✅ 空要素を作成時、varで定義可能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void main() { print("----- Map -----"); var map1 = {"Ami":29, "Kana":21, "Shiho":25, "Misako":27}; print(map1); // {Ami: 29, Kana: 21, Shiho:25, Misako:27} print(map1["Misako"]); // 27 map1["Mana"] = 26 map1["Misako"] = 10; print(map1); // {Ami: 29, Kana: 21, Shiho:25, Misako:10, Mana: 26} print("----- 空のMap -----"); var map2 = {}; map2["Risa"] = 18; print(map2); { Risa: 18} } |
Loop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void main() { print("----- for文 -----"); for (int i = 0; i < 10; i++) { print(i); // 0 1 2 3 4 5 6 7 8 9 } print("----- Listを使ったfor文 -----"); var names = ["Ami", "Kana", "Shiho", "Misako"]; for (var name in names) { print(name); // Ami Kana Shiho Misako } print("----- while文 -----"); int i = 0; while (i < 10) { print(i); // 0 1 2 3 4 5 6 7 8 9 i++; } } |
map()メソッド
✅ for文を使わなくても、リストから要素を取り出すことができる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<forEachを使って要素を取り出す方法> // リスト型の変数を定義 List<String> cars = ['Corolla', 'Harrier', 'Aqua']; //メソッドを呼び出す Column( children: buildCars(), ... ), // ウィジェットを格納したリストを返すメソッドを用意 List<Widget> buildCars() { List<Widget> list = []; cars.forEach((String car) { list.add(Text(car)); }); return list; } |
1 2 3 4 5 6 |
<mapメソッドを使って要素を取り出す方法> // リスト型の変数を定義 List<String> cars = ['Corolla', 'Harrier', 'Aqua']; Column( children: cars.map((String car) => Text(car)).toList() ) |
<演習>分岐とループを使い、リストnumsの中の100より大きい数字を2倍してprintする
1 2 3 4 5 6 7 8 |
void main() { var numbers = [100, 5, 0, 200, 15, 8, 300, 12, 400, 2, 500, 9]; for (var number in numbers){ if (number > 100) { print(number * 2); // 400 600 800 1000 }; }; } |
例外処理(try, on, catch, throw, finally)
✅ Exception(例外) : プログラム実行中で発生するエラーで、コードの修正で対処可能なエラーのこと
✅ 例外処理: プログラム実行中にException発生の有無で、処理を分けることができる
✅ try-on: 1つのExceptionに対して有効
✅ try-catch: 複数のExceptionに対して有効
✅ try-on-catch: onブロックで対処できなかった時の処理
✅ throw: 意図的にExceptionを発生させる(throw ‘エラー文’ とすることでcatchブロックの”e”で使用できる)
✅ finally: 例外処理(try, on, catch)の後に実行したい処理 Exceptionの発生有無に関わらず実行する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
void main() { // Usage of try, on, catch↓ String words = 'Hello World'; try{ print(int.parse(words)); } on IntegerDivisionByZeroException{ print("Occur IntegerDivisionByZeroException!"); } catch(e) { print(e); // FormatException: Hello World print("Occur Exception in try block!"); } finally{ print("ここで必ず実行されるコードを記述する"); } // Usage of throw, catch↓ int num = 100; try { if (num > 50) { throw "num is over 50"; } print("num is less than 50"); } catch(e) { print(e); print("現在の数字は$numだよ"); } } |
MutableとImmutable
✅ Mutable: 値の変更可能
✅ Immutable: 値の変更不可
✅ metaパッケージの@immutableアノテーション、メンバに型変更を不可とするfinalを付与することでimmutable化することができる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import 'package:meta/meta.dart'; @immutable class User { const User(this.id, this.name); // <- immutable用コンストラクタ final int id; final String name; } // User user = User() // コンパイル不可 User user = User(1, 'A'); // user.name = 'B' // コンパイル不可 user = User(user.id, 'B'); |
関数
✅ 返り値の型を最初に宣言
✅ 返り値がない場合は先頭にvoid
✅ 引数で渡させる配列&オブジェクトの変数は、呼び出し時には省略可能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// 関数 最初に返り値の型を書く int addFunc1(int a, int b) { return a + b; } // 返り値が無い場合は先頭にvoidと書く void addFunc2(int a, int b) { print(a + b); } // 関数内の処理が一行の場合は、略記が可能 int addFunc3(int a, int b) => a + b; // []で囲まれた引数は省略可能 省略された場合はnullが入る int addFunc4(int a, int b, [int? c, int? d]) { int result = a + b; if (c != null) result += c; if (d != null) result += d; return result; } // { }で囲まれた引数は、呼び出し時に変数名を指定する(省略可能) int addFunc5(int a, int b, {int? c, int? d}) { int result = a + b; if (c != null) result += c; if (d != null) result += d; return result; } void main() { print(addFunc1(2, 3)); // 5 addFunc2(3, 4); // 7 print(addFunc3(4, 5)); // 9 print(addFunc4(1, 2, 3)); // 6 print(addFunc5(1, 2, c:3)); // 6 } |
class
✅ null 不可なフィールド(今回はclass)は、late 型宣言で 遅延設定フィールド に修正する
コンストラクタ:インスタンス化した時に実行されるメソッド
✅ getter, setter: クラス変数をプライベート変数にした際、安全に参照・変更するための記述
⇨プライベート変数は[インスタンス名].[変数名]のようにアクセスし使用することはできない
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// クラスはclassの記述から始まる class Dog1 { // メンバ変数 String? name; // コンストラクタ Dog1(String name) { this.name = name; } // メソッド String sayName(){ return "I'm $name."; } } class Dog2 { // メンバ変数 String? name; int age; String? _message; // コンストラクタ(略記) Dog2(this.name, this.age); // セッター(クラスのプライベート変数_messageを変更可能にする) set greeting(String text) => _message = "$name: $text"; // ゲッター(プライベート変数を参照のみ可能にする) String get introduction => "Name: $name, Age:$age $name:$message"; } void main() { print("----- Dog1クラス -----"); Dog1 dog1 = Dog1("Ive"); // I'm Ive. print(dog1.sayName()); print("----- Dog2クラス -----"); Dog2 dog2 = Dog2("Mickey", 300); dog2.greeting = "HaHaHa!"; print(dog2.introduction); // Name:Mickey Age:300 Mickey:HaHaHa! |
Dartのコンストラクタについて
基本的なクラス構成
1 2 3 4 5 |
class Car { static final String car = 'corolla'; // static:クラス変数を定義 late String color; // staticなしでインスタンス変数 クラス変数と同名は指定不可 Car({this.color = 'Red'}); // コンストラクタの記述 this.変数名でメンバにアクセス } |
❶生成的コンストラクタ(Generative Constructors)
✅ 一般的なコンストラクタ「new」キーワードでインスタンスを生成する際に処理される
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Car { static final String car = 'corolla'; late String color; late int number; Car(color, number) { this.color = color; this.number = number; } } void main() { Car car = new Car('Red', 2); print(car.color); // => Red print(car.number); // => 2 } |
✅ Automatic field initialization: 引数を取るコンストラクタで引数の名称を「this.フィールド名」とすると代入処理を記述せずにフィールドを初期化することができる
1 2 3 4 5 6 7 8 9 10 11 12 |
class Car { static final String car = 'corolla'; late String color; late int number; Car(this.color, this.number); } void main() { Car car = new Car('Red', 2); print(car.color); // => Red print(car.number); // => 2 } |
✅ Named Constructors: コンストラクタのオーバーライドは不可→コンストラクタに任意の名前を付与し通常のコンストラクタから呼び出す方法をとる(コンストラクタ.任意名称)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Car { late String name; late String full_name; Car({this.name = 'corolla'}); Car.full_name() { this.full_name = '${this.name} sports'; } } void main() { Car full = Car.full_name(); full.name = 'Yaris'; print(full.name); // => Yaris print(full.full_name); // => Yaris sports } |
✅ Redirecting Constructors: 別のコンストラクタへ処理をリダイレクトすることができる
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Car { var name; Car() : this.anonymous(); Car.anonymous() { this.name = 'Corolla'; } Car.name(this.name); } main() { var car = new Car(); print(car.name); // Corolla } |
✅ Initializer Lists: コンストラクタの後に、コロンに続けてフィールドの初期化処理をする。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Car { var name; var age; Car() : this.anonymous(); Car.anonymous() : this.name = 'Corolla', this.age = 56; Car.name(this.name); } main() { var dog = new Car(); print(dog.name); // Corolla } |
❷ファクトリ(factory)
✅ インスタンスを生成しないコンストラクタ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Car { String name; static var _instance; // 返すインスタンスはクラス変数にする factory Car(String name){ if (_instance == null) { _instance = new Car._internal(name); } return _instance; } Car._internal(this.name); } void main() { Car car1 = new Car('Yaris Sports'); Car car2 = new Car('Yaris Cross'); print(car1.name); // => 'Yaris Sports' print(car2.name); // => 'Yaris Cross' print(car1 == car2); // => true、同じインスタンスを返している } |
❸定数コンストラクタ
✅ コンパイル時に定数オブジェクトをインスタンス化したい場合に使用(グローバルスコープに定数オブジェクトを定義したい時)
✅ finalとconstの違い↓
- final: 代入が1度(宣言時)のみ、値が参照値の場合も変更不可
- const: コンパイル時に値が決まる定数(暗黙的にfinalとなる)
1 2 3 4 5 6 7 8 9 10 11 12 |
class Car { final name; const Car(this.name); } final foo = const Car('Harrier'); void main(){ final Car hoge = new Car('Rav4'); print(foo.name); // => Harrier print(hoge.name); // => Rav4 } |
class継承(extends)
✅ extends : クラスの継承ができる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
class Dog { String name; // コンストラクタ Dog(this.name); void sayName(){ print("I'm $name."); } } // クラスの継承 class Shiba extends Dog { int_ age; // コンストラクタ Shiba(String name, int age) : super(name){ this.age = age; } void sayAge(){ print("$name: $age"); } } void main() { print("----- クラスの継承 -----"); Shiba shiba = Shiba("Pochi", 7); shiba.sayName(); // I'm Pochi. shiba.sayAge(); // Pochi: 7 } |
ミックスイン(mixin)
✅ mixin : インスタンスは生成できない
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
class Cat { String name; Cat(this.name); void sayName(){ print("I'm $name."); } } // ミックスイン mixin Cry { // コンストラクタは記述できない void cry() { print("Nya-!"); } } mixin Sleep { void sleep() { print("zzz..."); } } class Ive extends Cat with Cry, Sleep { int? age; Ive(String name, int age) : super(name){ this.age = age; } void sayAge(){ print("$name: $age"); } } void main() { Ive ive = Ive("Ive-chan", 12); ive.sayName(); // I'm Ive ive.sayAge(); // Ive: 12 ive.cry(); // Nya-! ive.sleep(); // zzz... } |
Abstract class
✅ Abstract classは単体では使うことはできず、必ず継承される
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// 抽象クラス abstract class Dog { String name; Dog(this.name); void sayName(){ print("I'm $name."); } // 抽象メソッド 継承先で必ず実装 void jump(); } class Shiba extends Dog { int? age; Shiba(String name, int age) : super(name){ this.age = age; } void sayAge(){ print("$name: $age"); } void jump(){ print("Pyon!"); } } void main() { // dog = Dog("Pochi"); // 抽象クラスはそのままではインスタンス化できない Shiba shiba = Shiba("Pochi", 7); shiba.sayName(); // I'm Pochi shiba.sayAge(); // Pochi:7 shiba.jump(); // Pyon! } |
暗黙的インターフェース(impliment)
✅ imprementの記述により、そのclassのインターフェースを全て実装する必要がある
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
class Dog { String name; Dog(this.name); void sayName(){ print("I'm $name."); } } class Jump { void jump() { print("Pyon!"); } } class Sleep { void sleep() { print("zzz..."); } } // Dartのクラスは、クラスの生成と同時に暗黙的にインターフェイス(外部とのやり取り)を生成する // implementの記述により、ShibaクラスはJumpクラスおよびSleepクラスのインターフェイスを全て実装する必要が生じる // これにより、ShibaクラスはJumpクラスおよびSleepクラスのインターフェイスを備えることになる class Shiba extends Dog implements Jump, Sleep { int? age; Shiba(String name, int age) : super(name){ this.age = age; } void sayAge(){ print("$name: $age"); } // 実装しないとエラー void jump() { print("Pyon Pyon!"); } // 実装しないとエラー void sleep() { print("zzzzzz..."); } } void main() { Shiba shiba = Shiba("Pochi", 7); shiba.sayName(); // I'm Pochi. shiba.sayAge(); // Pochi: 7 shiba.jump(); // Pyon Pyon! shiba.sleep(); //zzzzzz... } |
以上、Dart文法の超基礎の部分を大まかに見ていきました。
プログラミングを始めて触るよって人じゃないなら簡単な内容だったかなと思います 。
次回はDart文法のジェネリクス、例外処理、非同期処理などちょい応用の部分について
解説していきたいと思います。
それでは今回はこの辺で、ばいばい👋
○Flutter/Dartをこれから勉強していきたい方
○アプリ開発に興味がある方
○筆者と一緒に勉強をしていこうって思っている方