Skip to content

Instantly share code, notes, and snippets.

@kikuchy
Last active March 18, 2021 12:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kikuchy/fb76d17f6763f5e63471893491d328be to your computer and use it in GitHub Desktop.
Save kikuchy/fb76d17f6763f5e63471893491d328be to your computer and use it in GitHub Desktop.
null-safety explanation
import "package:flutter/material.dart";
final VoidCallback? nullableFunction = null;
final Map<String, String>? nullableMap = null;
void main() {
// これはnon-null
final int a = 1;
// なのでこれは静的型検査でエラーになる
// final int b = null;
// nullableであることを示すには型名に ? をつける
final int? c = null;
// ! はnon-null型へのキャスト
final int d = c!;
// ?.で「nullでなければメソッド呼び出し」
c?.isEven;
// 似たような文法が他にも
c?..isFinite..isEven;
nullableFunction?.call();
nullableMap?["hoge"];
// 地味にMap<X, T>の [] の戻り値が T? になっているのでMapを使っている既存コードのマイグレーションは大変かも
<String, String>{"hoge": "fuga"}["hoge"]?.length;
// Dart 2.12まではNullが全ての型のサブタイプだったが、Dart 2.12からはNeverが全ての型のサブタイプになる
// Neverはコードが到達しないことを示すのに使われる
Never throwError() {
throw StateError("");
}
final int e = throwError();
}
enum Happy {
tada,
raisedHands,
smile,
}
extension HappyEx on Happy {
String get label {
switch (this) {
case Happy.tada:
return "🎉";
case Happy.raisedHands:
return "🙌";
case Happy.smile:
return "😁";
}
// 余談: enumを使ったswitch-caseの解析ロジックがが新しくなったらしく、この書き方をしてもunreturned functionのエラーが出なくなった?
}
}
class SomeWidget extends StatefulWidget {
final int duration;
final List<Happy?> values;
SomeWidget({
// required がアノテーションではなくキーワードに
required this.duration,
// optional parameterの型がnon-nullならデフォルト値が必要
// List<Happy?> values, //これはエラー
List<Happy?> values = const [Happy.tada],
// nullableなら初期値は省略できる (nullで初期化される)
Key? key,
}): values = values,
super(key: key);
SomeWidgetState createState() => SomeWidgetState();
}
class SomeWidgetState extends State<SomeWidget> with SingleTickerProviderStateMixin {
// 初期化式つきのlateは、最初に呼び出されたときに初期化が走る(遅延初期化)
late final animationController1 = AnimationController(
vsync: this,
duration: Duration(milliseconds: widget.duration),
);
// 初期化式なしのlateは、最初に呼び出されるまでの間に初期化を済ませないといけない
late final AnimationController animationController2;
void initState() {
super.initState();
animationController2 = AnimationController(
vsync: this,
duration: Duration(milliseconds: widget.duration),
);
}
Widget build(BuildContext context) {
// static メソッドの of で取得できるうちのいくつかは、non-null版のofと、nullable版のmaybeOfに分かれていたりする
final screenWidth = MediaQuery.maybeOf(context)?.size?.width;
return Row(children: [
// ListやStreamからnull値を省いてList<T?>をList<T>にするにはwhereTypeを使えば良い
for (final v in widget.values.whereType<Happy>())
Text(v.label),
for (final v in widget.values)
if (v != null)
// Flow Analysisにより v は Happy と推論される
Text(v.label),
for (final v in widget.values)
// 三項演算子でも同様
(v != null) ? Text(v.label) : Text("nullでした"),
]);
}
}
// 余談: Kotlinチックなこういうスニペットを用意しておくと、
// (hoge != null) ? method(hoge) : null こういう式を
// hoge?.let((it) => method(it)) みたいに簡略化できて便利
extension Let<T> on T {
R let<R>(R Function(T) block) => block(this);
}
// 余談: Swiftチックにnullable型をOptionalモナドと考えるならmapの方がしっくりくるかも
// (hoge != null) ? method(hoge) : null こういう式を
// hoge.map(($0) => method($0)) みたいに簡略化できて便利
// 実際にはモナドではないので注意(このmapも実質flatMap)
extension OptionalMap<T> on T? {
R? map<R>(R? Function(T) block) {
final self = this;
if (self == null) return null;
return block(self);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment