Open69

【Dart編】Flutter入門してみた(作業ログ)

FlutterというかDartから入門する

参考元のサイトに従って以下の拡張機能を導入した

- Flutter Tree
- Flutter Widget Snippets
- Awesome Flutter Snippets
- Flutter-Auto-Import
- Error Lens
- Flutter Color

dartの変数宣言からやる

ファイルをコマンドラインから実行する方法は以下みたいにできる

dart main.dart

再代入可能かどうかを判定できるメソッドとか用意されていないかな?

再代入可能かどうかを判定できるメソッドとか用意されていないかな?

dartのリファレンスチラ見したけど無いみたい

runtimeTypeで実行時の変数の型を取得できるみたいだが、これの返り値の型ってStringじゃないの?

C言語のprintf使えていたフォーマットの指定がそのまま使えそうでとっつきやすい

変数宣言の章終わり

null safetyについて写経中

late修飾子なんだけど使い所があまり分からない。
変数だけ宣言しておいて、後からその変数に必ず代入する状況とは?

関数について写経中

引数の指定がかなり柔軟にできて便利(名前付き引数とか省略可能な引数など)
引数が必須かオプションかを明示的に書けるのも便利

クラスについて写経中

コンストラクタにも種類があることに驚き

名前付きコンストラクタ

  • コンストラクタに名前が付けられる
  • 通常のコンストラクタとともに定義可能
  • 名前付きコンストラクタしか定義されていない場合、通常のコンストラクタでインスタンスを生成できない
  • インスタンス変数の初期化の仕方は以下みたいにできる
class Square{
  final double width;
  final double height;

  Square.zero()
    : width = 0,
      height = 0;
}

リダイレクトコンストラクタ

  • 同じクラスの別のコンストラクタを呼び出してインスタンスを生成する
class Square{
  final double width;
  final double height;

  Square(this.width, this.height);
  
  Square.createTrueSquare(double length) : this(length, length);
}

定数コンストラクタ

  • すべてのインスタンス変数にfinal修飾子が定義されているコンストラクタ
  • インスタンス変数を定数扱いできるため、キャッシュ可能になり、パフォーマンスの向上が期待できる
class Square{
  final double width;
  final double height;

  Square(this.width, this.height);
}

ファクトリコンストラクタ

  • インスタンスを返さないコンストラクタ
  • 複雑な初期化処理をファクトリコンストラクタに押し付けて、インスタンスを生成できる
  • コンストラクタの前にfactory修飾子を付けることで使用
  • イニシャライザリスト
  • コンストラクタの中でassertが使えて便利
  • スーパーコンストラクタの呼び出しやインスタンスの初期化処理もできる

インスタンスを生成する際にnewを省略できる。

final point = Point.zero();
// final point = new Point.zero();
  • 演算子のオーバーロード
  • クラスのインスタンス同士での四則演算や比較ができるようになる
  • operatorキーワード + 演算子で定義できる
class Product {
  const Product(this.amount, this.price);
  final int amount;
  final int price;
 
 Product operator +(Product product) =>
      Product(amount + product.amount, price + product.price);
}

オーバーロード可能な演算子は以下のものだけ

<,>,<=,>=,,+,/,~/,*,%,|,^,&,<<,>>,[],[]=,~,==

使う時はインスタンス同士をプリミティブな値同士を計算するように扱える(便利)

final product3 = const Product(10, 300);
final product4 = const Product(20, 500);
final total = product3 + product4;
// total.amount == 30
// total.price == 800
  • ゲッター

    • getキーワードを使用して作成
    • 引数は実装できない
  • セッター

    • setキーワードを使用して作成
    • 引数は1つだけ実装できる
class User {
  User(this._age, this.name);
  int _age;
  final String name;

  // ゲッター(メンバー変数以外も返せる)
  // 引数を実装できない
  String get expose => '$nameさんは$_age歳です';
  // セッター
  // 引数は1つだけ実装できる
  set setAge(int age) => _age = age;
}

ゲッターでメンバ変数以外を返すような実装は混乱しそうだから、
極力ゲッターではメンバ変数を返すように実装したい

  • インターフェース
  • メソッドだけが記述された抽象クラスか通常クラスをimprementimplementsすることでインターフェースを実装できる
  • 通常クラスそのものをimprementimplementsできるのは驚き
  • 継承(extends)
  • 通常のクラスも抽象クラスも継承できる
  • 継承元の変数やメソッドをオーバライドしているものには@overrideアノテーションをつける
  • overrideの型を制限(covariantキーワード)
  • 継承先のoverride時にconvariantキーワードを使うことでサブクラスに限定して書き換えができる

うまく使えば引数に誤ったクラスのインスタンスを渡すバグを減らせそう

  • noSuchMethod
  • インターフェースの実装を省略したメソッドを使おうとしたら代わりに転送されるメソッド
class ImplBase implements Base {
  // noSuchMethod
  // インターフェースの実装を省略した場合に転送されるメソッド
  
  dynamic noSuchMethod(Invocation invocation) {
    if (invocation.memberName == const Symbol('find')) {
      return [].cast<String>();
    }
    throw 'No such method';
  }
}

final test = ImplBase();
ImplBase.find();
// []
ImplBase.save();
// 例外がスローされる

これいつ使うのか分からん
(インターフェースで提供されたにもかかわらず一部だけ実装しないみたいなことってあるのか?)

  • 列挙型
  • 定数の定義をする際に便利
enum UserAccountStatus {
  Disabled,
  Enabled,
  Suspend,
}
  • 継承、Mixin、インターフェースとして実装できない
  • インスタンス化もできない
  • クラスの拡張(extension)
extension StringParsing on UserAccountStatus {
  String tailString() {
    return toString().split('.').last;
  }
}
  • ライブラリとか利用する時に、ライブラリ本体のコードは変更したくないけど、挙動をちょっとだけ変更したいときに有用かも?
  • ライブラリそのものをラップしたクラスを作れば済む話では?
  • mixin
  • クラスで再利用なコードを追加
  • phpのtraitみたいなもの
mixin Auth{
  bool_singedIn=false;
  void siginedIn(){
    _singedIn=true;
  }
  void signedOut({
    _singedIn=false;
  }
  bool get isAuthenticated=>_singedIn;}
}

class User with Auth{
  void logout(){
    signedOut();
  }
}

void main(){
  final user=User();
  // mixinで実装されているコード
  user.siginedIn();
  assert(user.isAuthenticated==true);
  // Userクラスで実装されているコード
  user.logout();
  assert(user.isAuthenticated==false);
}

Util系の処理とかmixinで追加してあげるのが主な使い道かと思う

  • 静的メソッド・変数
  • staticキーワードを付けて定義する
  • phpと使い勝手は同じ

ジェネリクス

  • 配列などの要素やインスタンス生成時に任意の型に制限すること

型を指定しないListはなんでも代入できてしまう

final anyList = ['alice', 1, []];
print(anyList); // [alice, 1, []] 

型を指定すればListの中の要素の型を指定できる

final List<String> stringList = ['alice', 'bob'];
print(stringList); // [alice, bob]

ジェネリクス型のクラスを定義して使うこともできる

class Somethig<T>{
  Somethig(this.foo);
   final T foo;
}

// インスタンスを生成するタイミングでSomethigクラスのfooの型を指定できる
final onlyInt = Somethig<int>(2);
final onlyString = Somethig<String>('alice');

ジェネリクス型を限定することができる

abstract BaseDataStore {}
class DataStore implements BaseDataStore {}
class Somethig<T extends BaseDataStore>{
  Somethig(this.foo);
  final T foo;
}

// BaseDataStore以外を指定しているためエラー
final invalid = Somethig<String>('alice');

final valid = Somethig<BaseDataStore>(DataStore());

関数にもジェネリクスを指定できる

T returnArgument<T>(T t) {
  return t;
}

final text = returnArgument<String>('Hello');
print(text); // Hello
final number = returnArgument<int>(1);
print(number); // 1

アンダースコアから始まるキーワードは、

  • 同じファイル内のクラスからはアクセス可能
  • 同じファイル外からはアクセス不可能
    になっている

これは、アンダースコアから始まるキーワードはライブラリ内だけで公開されており、
dartではファイル = ライブラリといった扱いになっているためである

参照側にpart、被参照側にpart ofディレクティブを使用することで、
別ファイルのクラスのアンダースコアから始めるキーワードにもアクセスできるようになる

外部ファイルをimportする際に、

  • 特定のクラスだけ読み込みたいときはshow
import 'class_a.dart' show Lib;
  • 特定のクラスだけ読み込みたくないときはhide
import 'class_b.dart' hide Lib;

非同期処理

  • javascriptのPromise型みたいなことができる

asyncに対するStreamの処理としての処理はaysnc for? await for?どっち?

ジェネレータ

  • Iteratable<E>を返す関数
  • sync*をつけてyieldで呼び出しもとへ値を返す
  • ジェネレータはyieldが出現するまで処理を進め、yieldで指定した値を呼び出しもとに返しジェネレータの処理を中断する
// 同期ジェネレータ
Iterable<int> naturalsTo(int n) sync* {
  var k = 0;

  while (k < n) {
    yield k++;
  }
}

void main() {
  naturalsTo(2).forEach((value) {
    print(value);
  });

非同期ジェネレータ

  • Stream<T>を返す関数
  • async*をつけてyieldで呼び出し元へ値を返す
  • 呼び出し元はlistenメソッドでyieldされた値を受け取る
// 非同期ジェネレータ
Stream<int> assynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) {
    yield k++;
  }
}

void main() {
  assynchronousNaturalsTo(10).listen((event) {
    print(event);
  });
}

再帰的ジェネレータ

  • yield*を使って別の関数へ処理を委任する
  • 委任先でyieldした結果を返す
// 再帰的ジェネレータ
Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

void main() {
  naturalsDownFrom(6).forEach((element) {
    print(element);
  });
}

call()

  • インスタンスを関数に読んだときに実行される関数を定義できる
  • callはクラスに一つしか定義できない
  • phpでいうところの__invoke()
class CallableClass{
    const CallableClass(this.username);
    final String username;

    String call(String suffix){
      return suffix + username;
    }

}

void main(){
  const username = CallableClass('alice');
  print(username('Ms.'));
}

Isorate

  • メインのイベントループのスレッドとは別にイベントループのスレッドを起動して処理を分離すること
  • スレッド間はメッセージでやり取りを行う

別のイベントループを開始するには?

  • Isolate.spwanで関数を指定する
  • 指定できる関数はトップレベル関数静的なメソッドのどちらかだけ

メッセージのやり取りはどう行うか?

  • ReceivePortのインスタンスを作成して、sendPortプロパティをIsorateに渡す
  • 送信: sendPortのsendメソッドを使う
  • 受信: ReceivePortのlistenメソッドを使う

Typedefs

  • 型や関数の定義に別名をつける
  • 再利用しやすくなり、修正が容易になる(再利用を考えていないものにつけると冗長になるため注意)
typedef SomeCallback = String Function(int i);

class Some {
  const Some(this.callback);
  final SomeCallback callback;
}

メタデータ

  • クラスやメソッドに@ではじまる任意の情報をつけられる(例:@Deprecated)
  • 静的解析などに利用される
  • メタデータはreflecteeを使って取得できる
import 'dart:mirrors';

class Schema{
  const Schema(this.table);
  final String table;
}

@Schema('users')
class DBUser{}

void main(){
  final mirror = reflect(DBUser());
  // 付与したメタデータを取得できる
  final tableName = mirror.type.metadata.first.reflectee.table;
  print(tableName);
}

Mapを要素に持つListのソート(NullSafety対応)

void main() {
  var list =  [
    {'date': '2021-10-01 23:59:59', 'body': 'body_1'},
    {'date': '2021-10-03 00:00:00', 'body': 'body_3'},
    {'date': '2021-10-04 00:00:00', 'body': 'body_4'},
    {'date': '2021-10-02 09:00:00', 'body': 'body_2'}
  ];

  print(list.runtimeType);

  // sortは破壊的変更を行うため注意
  list.sort((a, b) {
    if (a['date'] != null && b['date'] != null) {
      // a['date']とb['date']がnullではないので、null可能性を排除する
      // 降順でソートであるためcompareToの返り値にマイナスをつける
      return -a['date']!.compareTo(b['date']!);
    } else {
      return 0;
    }
  });

  print('id降順ソート');
  print(list);
  print(list[0]['body']);
}

作成者以外のコメントは許可されていません