skip to Main Content

I’m having trouble using the Hero widget with SliverAppBar.

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: Row(
          children: [Colors.red, Colors.blue, Colors.yellow]
              .map((e) => GestureDetector(
                    onTap: () {
                      Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (_) => ScrollPage(color: e),
                          ));
                    },
                    child: Hero(
                      tag: e,
                      child: Container(
                        width: 100,
                        height: 100,
                        color: e,
                      ),
                    ),
                  ))
              .toList(),
        ),
      ),
    );
  }
}
import 'package:flutter/material.dart';

class ScrollPage extends StatefulWidget {
  const ScrollPage({super.key, required this.color});

  final Color color;

  @override
  State<ScrollPage> createState() => _ScrollPageState();
}

class _ScrollPageState extends State<ScrollPage> {
  // final GlobalKey key = GlobalKey<SliverState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          Hero(
            tag: widget.color,
            child: SliverAppBar.large(
              key: UniqueKey(),
              expandedHeight: 200,
              backgroundColor: widget.color,
              title: const Text(
                'This is a title blblaa',
              ),
            ),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) => ListTile(
                title: Text('Item $index'),
              ),
              childCount: 100,
            ),
          ),
        ],
      ),
    );
  }
}

I’m getting error like:

════════ Exception caught by widgets library ═══════════════════════════════════
'package:flutter/src/widgets/framework.dart': Failed assertion: line 6405 pos 12: 'renderObject.child == child': is not true.

and

════════ Exception caught by widgets library ═══════════════════════════════════
'package:flutter/src/widgets/framework.dart': Failed assertion: line 6369 pos 12: 'child == _child': is not true.
framework.dart:6369
The relevant error-causing widget was
MaterialApp

and sometimes:

════════ Exception caught by widgets library ═══════════════════════════════════
Duplicate GlobalKey detected in widget tree.
════════════════════════════════════════════════════════════════════════════════

I did similar not using Appbar and I achieved it. Demo video here. But, I really want to use SliverAppbar in this case.

Full minimal reproducible project: https://github.com/iqfareez/flutter_hero_sliver

How do I make the SliverAppBar work with Hero?

2

Answers


  1. You can try moving the Hero widget outside of the SliverAppBar, wrapping it around the entire CustomScrollView. Here’s how you can modify your code to achieve that:

    class MyHomePage extends StatelessWidget {
      const MyHomePage({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Hero(
            tag: 'row',
            child: Center(
              child: Row(
                children: [Colors.red, Colors.blue, Colors.yellow]
                    .map((e) => GestureDetector(
                          onTap: () {
                            Navigator.push(
                                context,
                                MaterialPageRoute(
                                  builder: (_) => ScrollPage(color: e),
                                ));
                          },
                          child: Container(
                            width: 100,
                            height: 100,
                            color: e,
                          ),
                        ))
                    .toList(),
              ),
            ),
          ),
        );
      }
    }
    
    class ScrollPage extends StatefulWidget {
      const ScrollPage({super.key, required this.color});
    
      final Color color;
    
      @override
      State<ScrollPage> createState() => _ScrollPageState();
    }
    
    class _ScrollPageState extends State<ScrollPage> {
      // final GlobalKey key = GlobalKey<SliverState>();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Hero(
            tag: 'row',
            child: CustomScrollView(
              slivers: [
                SliverAppBar(
                  key: UniqueKey(),
                  expandedHeight: 200,
                  backgroundColor: widget.color,
                  title: const Text(
                    'This is a title blblaa',
                  ),
                ),
                SliverList(
                  delegate: SliverChildBuilderDelegate(
                    (context, index) => ListTile(
                      title: Text('Item $index'),
                    ),
                    childCount: 100,
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    Login or Signup to reply.
  2. The issue is here Hero is a general widget rather than a sliver-widget. That’s the issue occurs while wrapping the SliverAppBar with Hero widget.

    You can do

    SliverToBoxAdapter(
      child: Hero(
        tag: widget.color,
        child: // customAppBar but it might loss the scroll-effect, 
    

    Also, you can wrap the Scaffold with Hero widget, but it will show a little different animation.

    You can create SliverPersistentHeaderDelegate.

    Check the pr and commit difference.

    import 'package:flutter/material.dart';
    
    class ScrollPage extends StatefulWidget {
      const ScrollPage({super.key, required this.color});
    
      final Color color;
    
      @override
      State<ScrollPage> createState() => _ScrollPageState();
    }
    
    class _ScrollPageState extends State<ScrollPage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: CustomScrollView(
            slivers: [
              SliverPersistentHeader(
                  delegate: MySliverPersistentHeaderDelegate(widget.color)),
              SliverList(
                delegate: SliverChildBuilderDelegate(
                  (context, index) => ListTile(
                    title: Text('Item $index'),
                  ),
                  childCount: 100,
                ),
              ),
            ],
          ),
        );
      }
    }
    
    class MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
      final tag;
    
      MySliverPersistentHeaderDelegate(this.tag);
      @override
      Widget build(
          BuildContext context, double shrinkOffset, bool overlapsContent) {
        return Hero(
          tag: tag,
          child: Material(
            color: tag,
            child: Stack(
              children: [
                Align(
                  child: const Text(
                    'This is a title blblaa',
                  ),
                ),
                Align(
                  alignment: Alignment.topLeft,
                  child: IconButton(
                      onPressed: () {
                        Navigator.of(context).pop();
                      },
                      icon: Icon(Icons.arrow_back)),
                ),
              ],
            ),
          ),
        );
      }
    
      @override
      double get maxExtent => 200;
    
      @override
      double get minExtent => kToolbarHeight;
    
      @override
      bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
          true;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search