Flutterのページ遷移が上手くいかない...
Flutterで簡易的なTodoアプリを実装しています。
AppBarのactionsに配置したIconButton押下時イベントで別ページへの遷移を実装したかったのですが、あるエラーに惑わされました🤨
正確に言うと、画面遷移は可能なのですが遷移するたびにThere are multiple heroes that share the same tag within a subtree
と例外が発生してました。
そのままにしておくのも気持ち悪いので、修正したく調査を実施しました。
エラー解析には下記の記事を参考にさせていただきました。
エラーの内容
There are multiple heroes that share the same tag within a subtree
翻訳してみると、
「サブツリー内に同じタグを共有するヒーローが複数存在します」となります。
どうやらFlutterのMaterialデザインの実装では、一部の要素に対して暗黙的にHeroというWidetを使ったアニメーションが適用されているようです。
Heroについては奥が深そうなので詳細は言及を省きますが、平たく言うと画面遷移時のアニメーションを実現するために用意されたFlutterフレームワークの一部とのことです。
そのHeroには遷移元と遷移先を紐付けるためのtag
が存在しており、サブツリー内に同一のtag
が複数存在する場合に上記のエラーが発生します。
FloatingActionButton
の実装時によく遭遇するそうですね。
ちなみにFloatingActionButton
とは、画面下部に表示されているボタンのことです。
今回の不具合の原因
今回の問題もFloatingActionButton
のtag
が重複していたことが原因でした。
今まではアプリ内にページ遷移の処理が存在しなかったため、tag
の重複による直接的な影響がなかったようです。だから重複の問題に気づかなかったんですね。
修正前のコードはこんな感じ
(今回の事象と関係のない部分は一部省略してます)
/// メイン画面
class MainPage extends HookConsumerWidget {
const MainPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
drawer:
// サイドメニューの実装
// 画面上部のAppBar
appBar: AppBar(
backgroundColor: AppColors.main,
actions: const [
SearchButton(), // 追加実装した検索ボタン
SettingsButton(), // 設定ボタン
],
bottom:
// bottom部の実装
body:
// body部の実装
// 問題のfloatingActionButton
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: const Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
DeleteButton(), // ゴミ箱ボタン
AddButton(), // 追加ボタン
],
),
),
);
}
}
// 検索のアイコンボタン(押下時にページ遷移したい)
class SearchButton extends StatelessWidget {
const SearchButton({
super.key,
});
@override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.search),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SearchPage()),
);
},
);
}
}
// 遷移先のTodo検索ページ
class SearchPage extends StatelessWidget {
const SearchPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('検索'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
),
body: const Column(
children: [
Material(
color: Colors.transparent,
child: SearchBar(),
),
// 他の検索関連のウィジェットを実装予定
],
),
);
}
}
// ゴミ箱アイコンのfloatingActionButton
class DeleteButton extends ConsumerWidget {
const DeleteButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return FloatingActionButton(
onPressed: () {
// ボタン押下時の処理
},
child: Icon(
Icons.delete,
),
);
}
}
// +アイコンのfloatingActionButton
class AddButton extends StatelessWidget {
const AddButton({super.key});
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
// ボタン押下時の処理
},
child: const Icon(
Icons.add,
),
);
}
}
SearchButton
クラスのボタン押下時で検索ページへの遷移を実装したところ、FloatingActionButton
で内部的に使用しているHeroでtag
の重複エラーが浮き彫りになったということですかね。
修正方法
修正方法は単純でした。
FloatingActionButton
のプロパティでheroTag
を明示的に設定し、それぞれのHeroに一意性を確保することで修正できました。
修正するのは以下の2つのクラスだけです。
// ゴミ箱アイコンのfloatingActionButton
class DeleteButton extends ConsumerWidget {
const DeleteButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return FloatingActionButton(
// ここを追加
heroTag: 'delete_button',
onPressed: () {
// ボタン押下時の処理
},
backgroundColor: AppColors.white,
child: Icon(
Icons.delete,
),
);
}
}
// +アイコンのfloatingActionButton
class AddButton extends StatelessWidget {
const AddButton({super.key});
@override
Widget build(BuildContext context) {
return FloatingActionButton(
// ここを追加
heroTag: 'add_button',
onPressed: () {
// ボタン押下時の処理
},
child: const Icon(
Icons.add,
),
);
}
}
例外が発生することなくページ遷移できるようになりました🎉
最後に
FloatingActionButton
が複数存在する場合はHeroのタグを個々で設定しないと思わぬ事故の原因となりかねないということですね。
今回は調査方法と修正方法をまとめましたが、Flutter初心者のため認識の相違などがあるかもしれません。
実際にページ遷移可能な処理は実装できましたが、もし間違いやご意見などがございましたらコメントいただけると幸いです🙇♂️