📚
そんなコードで大丈夫か?
StatefulWidget
initState()
が完了してからcontext
を読み取る
- ❌Bad
class _MyWidgetState extends State<MyWidget> {
int _counter = 0;
void initState() {
super.initState();
final width = MediaQuery.of(context).size.width;
print(width); // 💥dependOnInheritedWidgetOfExactType<MediaQuery>() or dependOnInheritedElement() was called before _MyWidgetState.initState() completed
}
Widget build(BuildContext context) {
return Center(child: Text('$_counter'));
}
}
- ✅Good
class _MyWidgetState extends State<MyWidget> {
int _counter = 0;
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(
(_) {
final width = MediaQuery.of(context).size.width;
print(width);
},
);
}
Widget build(BuildContext context) {
return Center(child: Text('$_counter'));
}
}
setState
を実行する前にWidgetの有無を確認する
- ❌Bad
class _MyWidgetState extends State<MyWidget> {
int _counter = 0;
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () {
Future.delayed(
const Duration(seconds: 1),
() => setState(() => _counter++), // 💥setState() called after dispose()
);
Navigator.pop(context);
},
child: Text('$_counter'),
),
);
}
}
- ✅Good
class _MyWidgetState extends State<MyWidget> {
int _counter = 0;
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () {
Future.delayed(
const Duration(seconds: 1),
() {
if (context.mounted) {
setState(() => _counter++);
}
},
);
Navigator.pop(context);
},
child: Text('$_counter'),
),
);
}
}
List
適切なAPIを使う
- ❌Bad
void main() {
print([].first); // 💥Bad state: No element
print([].last); // 💥Bad state: No element
}
- ✅Good
void main() {
print([].firstOrNull); // null
print([].lastOrNull); // null
}
Indexを安全に渡す
- ❌Bad
void main() {
print([1, 2][3]); // 💥Index out of range
}
- ✅Good①
extension type SafeList<E>(List<E> _) {
E? operator [](int index) => index < length ? this[index] : null;
}
void main() {
print(SafeList([1, 2])[3]); // null
}
- ✅Good②
extension SafeListExt on List {
E? tryGet<E>(int index) => index < length ? this[index] : null;
}
void main() {
print([1, 2].tryGet(3)); // null
}
型変換
暗黙的なキャストを活用する
- ❌Bad
void main() {
dynamic a = 1;
print(a as String); // 💥TypeError: type 'int' is not a subtype of type 'String'
}
- ✅Good
void main() {
dynamic a = 1;
if (a is String) {
print(a);
} else {
print(a.runtimeType); // int
}
}
URL
最初からUri形式にして利用する
- ❌Bad①
void main() {
// 漢字やスペースを正しく解析されなかったり、XSSなどのサイバー攻撃を受けたりする可能性がある
final param = '日本語 1';
print('https://www.example.com/page1?param=$param');
}
- ❌Bad②
void main() {
// 上記リスクを回避できたが、コードが長くて読みにくい
final param1 = '日本語 1';
final param2 = '日本語 2';
print('https://www.example.com/page1?param1=${Uri.encodeFull(param1)}¶m2=${Uri.encodeFull(param2)}');
}
- ✅Good
void main() {
final uri = Uri(
scheme: 'https',
host: 'www.example.com',
path: 'page1',
queryParameters: {
'param1': '日本語 1',
'param2': '日本語 2',
},
);
print(uri);
}
結語
大丈夫だ、問題ない。
Discussion
👏