📚

そんなコードで大丈夫か?

2024/07/28に公開
1

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)}&param2=${Uri.encodeFull(param2)}');
}
  • ✅Good
void main() {
  final uri = Uri(
    scheme: 'https',
    host: 'www.example.com',
    path: 'page1',
    queryParameters: {
      'param1': '日本語 1',
      'param2': '日本語 2',
    },
  );
  print(uri);
}

結語

大丈夫だ、問題ない。

GitHubで編集を提案

Discussion