Top
/
Articles
/
ExpansionTile を使用時に _TypeError 例外が発生するときの対処法
カテゴリー「Flutter」の画像

ExpansionTile を使用時に _TypeError 例外が発生するときの対処法

Flutter
トラブル
ソース読み

2025年4月15日

現象

ExpansionTileListView の子として配置したとき、

_TypeError (type 'double' is not a subtype of type 'bool?' in type cast)

という例外が発生する。

対処法

ExpansionTileのドキュメントの先頭にも書かれているように
ExpansionTileListView の子として配置するときは、ExpansionTileのkey に PageStorageKey を指定する必要があります。

 
ListView(
  children: [
    ExpansionTile(
      key: PageStorageKey(someValue),
      ...
    ),
  ],
)

なぜこうする必要があるのか ?

ListView はスクロール位置をPageStoragedouble値で保存します。
ExpansionTile は展開されているかどうかを PageStoragebool 値で保存します。
ExpansionTilePageStorageKey を指定しない場合、
ListView と同じkeyに対して状態を保存・読み取ろうとしてしまいます。

PageStorage(実際はそのデータ保管庫であるPageStorageBucket)に保存するデータの型はdynamicであり、
状態を保存するときにどの型だったのかわからなくなってしまいます。
したがって、状態を取り出すときに実際の型にキャストする必要があります。

ListViewdouble で保存した状態を ExpansionTileboolとして読み取ろうとするため、
データのキャスト時に型が合わなくてこのエラーが発生するのです。

https://github.com/flutter/flutter/blob/99bf419997accdfe6013c1732ce6bc873b01d45f/packages/flutter/lib/src/material/expansion_tile.dart#L613

  _isExpanded =
        PageStorage.maybeOf(context)?.readState(context) as bool? ?? widget.initiallyExpanded; 

ExpansionTileのkeyにPageStorageKeyを指定することによって、
ListViewが状態を保存するときに使うkeyとは別のkeyを使ってExpansionTileが状態を保存・読み取りするようになるので、
型の不一致を防ぐことができるのです。

そもそもPageStorage, PageStorageKeyとは ?

PageStorageとは、
Widgetの状態を保存する場所です。Widgetは破棄されると状態も失われてしまうため、
PageStorage に子孫Widgetの状態を保存しておくことで、子孫Widgetが再作成されたときに保存した状態を復元することができます。

PageStorageKey は何かというと、PageStorage に状態を保存するときにkeyとして使われるものです。
実際は対象のWidgetに指定したPageStorageKey だけが使われるのではなく、
Widget位置から親Widgetへとツリーをさかのぼっていき、
PageStorage にたどり着くまでの間にある祖先のWidgetすべてのkeyに指定されたPageStorageKey のリストが実際のkeyとなります。

一連の祖先のWidgetも含めたPageStorageKeyのリストによって、ツリー構造が異なればたとえ同じPageStorageKeyが指定されていても異なるkeyということになり、
保存された状態を区別することができるようになるのです。

// 以下の二つのChildWidgetのkeyは別のkeyとして状態が保存される
  Column(
    children: [
      ParentWidget(
        key: const PageStorageKey("parent"),
        child: ChildWidget(
          key: const PageStorageKey("child"),
          ...
        ),
      ),
      ChildWidget(
        key: const PageStorageKey("child"),
        ...
      ),
    ],
  )

ソースを読んでみよう

状態の読み取り:

PageStorageBucket.readState(context, { identifier })
  dynamic readState(BuildContext context, {Object? identifier}) {
    if (_storage == null) {
      return null;
    }
    if (identifier != null) {
      return _storage![identifier];
    }
    final _StorageEntryIdentifier contextIdentifier = _computeIdentifier(context); 
    return contextIdentifier.isNotEmpty ? _storage![contextIdentifier] : null;
  }

https://github.com/flutter/flutter/blob/99bf419997accdfe6013c1732ce6bc873b01d45f/packages/flutter/lib/src/widgets/page_storage.dart#L124

PageStorageBucket._computeIdentifier(context)
  _StorageEntryIdentifier _computeIdentifier(BuildContext context) {
    return _StorageEntryIdentifier(_allKeys(context)); 
  }

https://github.com/flutter/flutter/blob/99bf419997accdfe6013c1732ce6bc873b01d45f/packages/flutter/lib/src/widgets/page_storage.dart#L90C3-L92C4

_StorageEntryIdentifier というのは、keyのリストを保持して比較できるようにするだけのデータクラスです。
https://github.com/flutter/flutter/blob/99bf419997accdfe6013c1732ce6bc873b01d45f/packages/flutter/lib/src/widgets/page_storage.dart#L41C1-L64C2

祖先のWidgetにさかのぼっていき、PageStorageKeyのリストを作成する

PageStorageBucket._allKeys(context)
  List<PageStorageKey<dynamic>> _allKeys(BuildContext context) {
    final List<PageStorageKey<dynamic>> keys = <PageStorageKey<dynamic>>[];
    if (_maybeAddKey(context, keys)) { 
      context.visitAncestorElements((Element element) {
        return _maybeAddKey(element, keys); 
      });
    }
    return keys;
  }

https://github.com/flutter/flutter/blob/99bf419997accdfe6013c1732ce6bc873b01d45f/packages/flutter/lib/src/widgets/page_storage.dart#L80

PageStorageにたどり着くまで親Widgetに指定されたPageStorageKeyを集めていく

PageStorageBucket._maybeAddKey(context, keys)
  static bool _maybeAddKey(BuildContext context, List<PageStorageKey<dynamic>> keys) {
    final Widget widget = context.widget;
    final Key? key = widget.key;
    if (key is PageStorageKey) { 
      keys.add(key);
    }
    return widget is! PageStorage; 
  }

https://github.com/flutter/flutter/blob/99bf419997accdfe6013c1732ce6bc873b01d45f/packages/flutter/lib/src/widgets/page_storage.dart#L71

横長宣伝