0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FlutterAdvent Calendar 2024

Day 6

【Flutter】「There are multiple heroes that share the same tag within a subtree」の不具合と戦ってみた

Last updated at Posted at 2024-12-05

スクリーンショット 2024-12-03 21.51.47.png

エラーになるまでの簡単な流れ

  • bottomNavigationBarを使って実装していた
  • それぞれのタブの切り替えをした時に再描画されるのが嫌で、修正してみた
  • 修正してみたところ、Exceptionが発生した

エラーになるまでを詳しく

1. 以下のようなタブ画面を作成していた (作成しているアプリから抜粋)

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:food_gram_app/ui/screen/tab/tab_view_model.dart';

class TabScreen extends ConsumerWidget {
  const TabScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(tabViewModelProvider());
    final controller = ref.watch(tabViewModelProvider().notifier);
    return Scaffold(
      body: controller.pageList[state.selectedIndex],
      bottomNavigationBar: SizedBox(
        height: 80,
        child: Theme(
          data: Theme.of(context).copyWith(
            splashColor: Colors.transparent,
            highlightColor: Colors.transparent,
          ),
          child: BottomNavigationBar(
            currentIndex: state.selectedIndex,
            onTap: controller.onTap,
            items: <BottomNavigationBarItem>[
              BottomNavigationBarItem(
                icon: Padding(
                  padding: const EdgeInsets.only(top: 18),
                  child: Icon(
                    Icons.fastfood_outlined,
                    semanticLabel: 'timelineIcon',
                  ),
                ),
                label: '',
              ),
              BottomNavigationBarItem(
                icon: Padding(
                  padding: const EdgeInsets.only(top: 18),
                  child: Icon(
                    CupertinoIcons.map,
                    semanticLabel: 'mapIcon',
                  ),
                ),
                label: '',
              ),
              BottomNavigationBarItem(
                icon: Padding(
                  padding: const EdgeInsets.only(top: 18),
                  child: Icon(
                    CupertinoIcons.profile_circled,
                    semanticLabel: 'profileIcon',
                  ),
                ),
                label: '',
              ),
              BottomNavigationBarItem(
                icon: Padding(
                  padding: const EdgeInsets.only(top: 18),
                  child: Icon(
                    Icons.settings,
                    semanticLabel: 'settingIcon',
                  ),
                ),
                label: '',
              ),
            ],
            type: BottomNavigationBarType.fixed,
            iconSize: 28,
            elevation: 0,
            backgroundColor: Colors.white,
            selectedItemColor: Colors.black,
            unselectedItemColor: Colors.grey,
            selectedFontSize: 0,
            unselectedFontSize: 0,
          ),
        ),
      ),
    );
  }
}

2.それぞれのタブの切り替えをした時に再描画されるのが嫌だったため、修正した

  • IndexedStack を使用すると、タブ切り替え時に他のタブの状態を保持しつつ、現在のタブだけが表示される
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:food_gram_app/ui/screen/tab/tab_view_model.dart';

class TabScreen extends ConsumerWidget {
  const TabScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(tabViewModelProvider());
    final controller = ref.watch(tabViewModelProvider().notifier);
    return Scaffold(
      /// タブ切り替え時に他のタブの状態を保持しつつ、現在のタブだけが表示される 
      body: IndexedStack(
        index: state.selectedIndex,
        children: controller.pageList,
      ),
      bottomNavigationBar: SizedBox(
        height: 80,
        child: Theme(
          data: Theme.of(context).copyWith(
            splashColor: Colors.transparent,
            highlightColor: Colors.transparent,
          ),
          child: BottomNavigationBar(
            currentIndex: state.selectedIndex,
            onTap: controller.onTap,
            items: <BottomNavigationBarItem>[
              BottomNavigationBarItem(
                icon: Padding(
                  padding: const EdgeInsets.only(top: 18),
                  child: Icon(
                    Icons.fastfood_outlined,
                    semanticLabel: 'timelineIcon',
                  ),
                ),
                label: '',
              ),
              BottomNavigationBarItem(
                icon: Padding(
                  padding: const EdgeInsets.only(top: 18),
                  child: Icon(
                    CupertinoIcons.map,
                    semanticLabel: 'mapIcon',
                  ),
                ),
                label: '',
              ),
              BottomNavigationBarItem(
                icon: Padding(
                  padding: const EdgeInsets.only(top: 18),
                  child: Icon(
                    CupertinoIcons.profile_circled,
                    semanticLabel: 'profileIcon',
                  ),
                ),
                label: '',
              ),
              BottomNavigationBarItem(
                icon: Padding(
                  padding: const EdgeInsets.only(top: 18),
                  child: Icon(
                    Icons.settings,
                    semanticLabel: 'settingIcon',
                  ),
                ),
                label: '',
              ),
            ],
            type: BottomNavigationBarType.fixed,
            iconSize: 28,
            elevation: 0,
            backgroundColor: Colors.white,
            selectedItemColor: Colors.black,
            unselectedItemColor: Colors.grey,
            selectedFontSize: 0,
            unselectedFontSize: 0,
          ),
        ),
      ),
    );
  }
}

3.以下のエラーが発生した

  • 「There are multiple heroes that share the same tag within a subtree」が発生した
======== Exception caught by scheduler library =====================================================
The following assertion was thrown during a scheduler callback:
There are multiple heroes that share the same tag within a subtree.

Within each subtree for which heroes are to be animated (i.e. a PageRoute subtree), each Hero must have a unique non-null tag.
In this case, multiple heroes had the following tag: <default FloatingActionButton tag>
Here is the subtree for one of the offending heroes: Hero
  tag: <default FloatingActionButton tag>
  state: _HeroState#f3a81
When the exception was thrown, this was the stack: 

以下省略...

4.とりあえず治ったので共有

  • FloatingActionButtonheroTagを無効化することによって不具合解消
import 'package:flutter/material.dart';

class AppFloatingButton extends StatelessWidget {
  const AppFloatingButton({
    required this.onTap,
    super.key,
  });

  final Function() onTap;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 70,
      height: 70,
      child: FloatingActionButton(
        heroTag: null,
        foregroundColor: Colors.black,
        backgroundColor: Colors.black,
        elevation: 10,
        shape: CircleBorder(side: BorderSide()),
        onPressed: onTap,
        child: const Icon(
          Icons.add,
          color: Colors.white,
          size: 35,
        ),
      ),
    );
  }
}

まとめ

  • ウィジェットツリー内で同じ Hero タグ(tag)を持つ複数の Hero ウィジェットが存在し、それらが同じページ(PageRoute)内で競合しているそう
  • Hero ウィジェットは、画面遷移時にアニメーションを行うための仕組みであり、その仕組みでは tag を用いて対応するウィジェットを特定する。そのため、同じ tag が使われているとどちらをアニメーションさせるべきか分からなくなり、エラーになるらしい
  • 今回は、タブの画面のうちFloatingActionButtonを二回使っているため、重複してしまった

最後に

MapLibreを使用したアプリを個人で開発しています・:*+.(( °ω° ))/.:+

Group 93.png

  • 「FoodGram」は、フードシェアアプリとなっており、あなたの好きなレストランの食事をぜひこのアプリで共有していただけると嬉しいです!!

このアプリのセールスポイント

  • このアプリだけのレストランマップをユーザー全員で作成できる⭐️
  • 自分だけのフードアルバムを構築できる⭐️
  • 世界中の人たちの投稿を自由に閲覧できる⭐️
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?