Last active
March 18, 2021 12:50
-
-
Save kikuchy/fb76d17f6763f5e63471893491d328be to your computer and use it in GitHub Desktop.
null-safety explanation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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