Flutter/Dart開発におけるTips

A futuristic digital workspace showcasing Flutter/Dart development tips. The scene features a sleek laptop with code snippets on the screen, surrounded by floating icons representing Dart programming, Flutter widgets, and debugging tools. A holographic interface displays tips such as 'Use StatefulWidgets wisely' and 'Optimize widget tree'. The background has a modern tech aesthetic with glowing blue and purple hues.

現在makeshopのAPIを使ったモバイルアプリ開発をしています。単純なところですがバグがあったので対処していたのですが、どうもChatGPT o1にしても、Claude3.7 Sonnetにしても、コードは問題ないと言ってきます。可能性を指摘してもだめでした。

最終的に解決したのは、Flutterのスペシャリストに聞いたらたしかに、できるはずなのにですが、回避策を教えてもらってあっさり解決。解決したのをChatGPT o1に教えてあげても「通常起こりにくいです」と正しさをアピールされたけど、まあいいかw

ということで、発生した内容をまとめました。ご参考までに。

1. 発生した問題の概要

1.1 エラー内容

  • Dart コード実行時に以下のようなエラーが発生rustコピーする編集するtype 'List<String>' is not a subtype of type 'int' of 'value' もしくは実行時に「Map 内部へ型が異なるデータを入れようとしている」といった型不一致エラー。

1.2 原因仮説

  1. Dart が「あるキーに最初に設定された型」を元に推論し、後から別の型を入れようとして衝突
    • 具体的には pagelimit に int をセットしたあと、同じ MapList<String> を追加しようとした
    • 解析段階または実行時に「int型用のMapと思ったらリストが来た」という矛盾を検知してエラーが出た可能性
  2. どこかで const / final の扱いが厳密に働いていた
    • Dart の constオブジェクトをイミュータブル化する
    • final は「変数自体を再代入しない」ものの、内部オブジェクトが可変かどうかは定義次第
    • 場合によっては内部を変更できない状態になっているか、または静的解析で「型が合わない」と見なされた
  3. API スキーマやモデル定義の型と違うものを渡していた
    • GraphQL スキーマでは [String!] を期待しているが、Dart 上で [int] と解釈してしまったり、逆に [String] を入れようとしたのに Dart が int だと思っているフィールドに突っ込んだりして衝突

2. 実際に起きた状況と例

2.1 具体的なコード例

dartコピーする編集するFuture<List<Map<String, dynamic>>> fetchAllProductQuantities({List<String>? customCodes}) async {
  final int limit = 1000;
  int page = 1;

  // 初期定義
  final Map<String, dynamic> variables = {
    "input": {
      "page": page,
      "limit": limit,  // ここは int
    }
  };

  // 後から別のキーを追加
  if (customCodes != null && customCodes.isNotEmpty) {
    variables["input"]["customCodes"] = customCodes;  // ここは List<String>
  }

  ...
  // API呼び出し
}

このような形が一見正しそうに見えるものの、一部環境で

rustコピーする編集するtype 'List<String>' is not a subtype of type 'int' of 'value'

というエラーが起きるケースがありました。
表面的には Map<String, dynamic> のため、intList<String> を混在しても大丈夫そうですが、Dart の型推論・実行時チェックが「ここは int 用では?」とみなし、衝突してしまうシナリオが発生しました。

2.2 どのように対処したか

dartコピーする編集するfinal Map<String, dynamic> input = {
  "page": 1,       // int
  "limit": 10,     // int
};

final Map<String, dynamic> variables = {
  "input": input,  // input自体をマップとして入れる
};

// 後から customCodes を追加
if (customCodes != null && customCodes.isNotEmpty) {
  variables["input"]["customCodes"] = customCodes; // List<String>
}

このように「最初から別変数 (input) で int フィールドをまとめつつ、再代入箇所を整理」したところ、型不一致エラーが解消しました。
推測される理由としては、1つのマップに対して Dart が推論した「型情報の矛盾」が回避されたからです。


3. なぜこうした型の衝突が起こったのか

3.1 Dart の型安全と Map<String, dynamic> の関係

  • Map<String, dynamic> は一見「何でも入る」ように思えますが、Dart/Flutter では
    static analysis(コンパイル時の型検査)が入り、必要に応じて実行時にも型チェックを行います。
  • 特に「同じキーに対して異なる型を入れた時」や「マップ全体を int だけのものと推定してしまった時」に矛盾を検知すると、実行時エラーが出る場合があります。

3.2 final / const の影響

  • final は「変数の再代入」を禁止しますが、オブジェクトの内部書き換え自体はできるかもしれません。
  • しかし、もし const を使っていたり、あるいはコード生成などで実行時にイミュータブルなマップが作られていると、「内部への値追加」ができなくなる → 型不一致や書き換え不可エラーにつながる。

3.3 API 定義との不整合

  • もし API が「customCodes[Int]」と定義していたら、Dart 側で [String] を渡すと衝突する。逆も然り。
  • スキーマドキュメントに [String!] と書いてあっても、Dart 側の自動生成コードで間違って [int] になっていると実行時にエラーが出る。

4. ロジックツリーで整理する

rustコピーする編集する型不一致エラー: type 'List<String>' is not a subtype of type 'int'
├─ A. 実行時に、マップ全体の型推論が「int」とみなされている
│   ├─ 最初に page, limit(int) を入れているため
│   └─ 後から別型(List<String>) を追加しようとして衝突
├─ B. Dart の static analysis/実行時チェックの影響
│   ├─ IDE が constとして扱っている可能性
│   └─ code generation等で intを前提としているクラスがある
└─ C. サーバー/API定義とのギャップ
    └─ API側に送る際に intが期待される/実際にはList<String>を送ってエラー

このように複数の要因があり得ますが、現在のケースでは A が最も該当しやすいと思われます。


5. 今回の解決策と再発防止

5.1 解決策

  1. 「最初に定義したマップに全部まとめておき、最小限の再代入にする」
    • variables["input"] = {"page": ..., "limit": ..., "customCodes": ...} のように最初からキーを確定
    • あるいは「page, limit を持つ input マップを作成し、それを variables["input"] にセット。後からキーを追加しない or 追加するのを別マップにする」
  2. 「もし API 側の型が int / List<int> なら、型変換する」
    • 文字列が入っていれば int.parse() するなど

5.2 再発防止策

  1. 型を明確にするためにクラスを定義
    • class InputRequest { int page; int limit; List<String>? customCodes; ... } のように設計し、JSON serialize/deserialize する
    • コード生成ツール(例えば json_serializable)を使えば、型不一致があればコンパイル時点で検出しやすい
  2. const, final の扱いやイミュータブル化を確認
    • UI などで const を多用しすぎると「再代入不可」や「内部書き換え不可」によるエラーが発生しがち
  3. API スキーマとの整合
    • GraphQL や REST API スキーマが [String][Int] か、定義を事前に確認し、クライアントとサーバーで相違がないかテスト

6. まとめ

今回のトラブルは、Dart が内部マップの型を矛盾なく扱えず、実行時に「int の位置に List<String> が来た」と判断してエラーを出したのが原因です。

  • 一見 Map<String, dynamic> は自由に使えるように思えますが、最初に int フィールドを定義した後で List<String> を追加しようとすると衝突が起こるケースがあります。
  • 対処法は「最初にきちんとマップ構造を定義する」「クラス化して型を厳格に管理する」「必要ならキャスト/コンバートで別型に変換する」などが挙げられます。

本レポートの重要ポイント:

  1. 型推論と実行時チェックの矛盾が起きると、Map<String, dynamic> でもエラーが出る
  2. 初期定義を明確化し、あとから追加するキーを減らすことで回避できる
  3. API スキーマ自動生成コードとの整合性チェックが重要

これらを踏まえ、「型定義の一貫性」を意識した実装と検証が、今後同様の問題を防ぐためには欠かせません。

コメント

タイトルとURLをコピーしました