Flutter: Hero widget, Navigation 2.0 and Provider are not working properly together

Issue

I tried to use Hero widget with ChangeNotifierProvider and the new Navigation 2.0 system. But I am facing issues there. The same code works when changeNotifier is not used.

The issue I am facing is, after popping the second page W2, the text (ie, hero’s child) from the first page disappearing.

Below is the complete code. runApp(HeroTestApp2()) is working as usual. But I would love to make the first approach working. Please help.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

main(List<String> args) {
  runApp(HeroTestApp());
}

class MyModel extends ChangeNotifier {
  bool _go = false;
  bool get go => _go;
  set go(bool go) {
    _go = go;
    notifyListeners();
  }
}

class HeroTestApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<MyModel>(
      create: (context) => MyModel(),
      builder: (context, _) {
        final state = context.watch<MyModel>();
        return MaterialApp(
          home: Navigator(
            pages: [
              MaterialPage(child: W1(onTapped: null)),
              if (state.go) MaterialPage(child: W2()),
            ],
            onPopPage: (route, result) {
              if (!route.didPop(result)) return false;
              state.go = false;
              return true;
            },
            observers: [HeroController()],
          ),
        );
      },
    );
  }
}

class HeroTestApp2 extends StatefulWidget {
  @override
  _HeroTestApp2State createState() => _HeroTestApp2State();
}

class _HeroTestApp2State extends State<HeroTestApp2> {
  bool go = false;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Navigator(
        pages: [
          MaterialPage(
            child: W1(onTapped: () {
              setState(() {
                go = true;
              });
            }),
          ),
          if (go) MaterialPage(child: W2()),
        ],
        onPopPage: (route, result) {
          if (!route.didPop(result)) return false;
          go = false;
          return true;
        },
        observers: [HeroController()],
      ),
    );
  }
}

class W1 extends StatelessWidget {
  final void Function() onTapped;

  const W1({Key key, this.onTapped}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTapped ??
          () => context.read<MyModel>().go = true,
      child: Scaffold(
        appBar: AppBar(),
        body: Container(
          alignment: Alignment.topRight,
          child: Hero(
            tag: "topic",
            child: DefaultTextStyle(
              style: TextStyle(
                  fontSize: 40,
                  decoration: TextDecoration.none,
                  color: Colors.blue),
              child: Text(
                "data",
                style: TextStyle(fontSize: 40),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class W2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        alignment: Alignment.bottomLeft,
        child: Hero(
          tag: "topic",
          child: DefaultTextStyle(
            style: TextStyle(
                fontSize: 140,
                decoration: TextDecoration.none,
                color: Colors.blue),
            child: Text(
              "data",
            ),
          ),
        ),
      ),
    );
  }
}

Solution

Ok, I fount the answer.

It was the observers: [HeroController()],

Each time the state changes, the hole tree was rebuilding along with the HeroController. The solution is to define the HeroController above the changeNotifier:

class HeroTestApp extends StatelessWidget {
  final heroC = HeroController();

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<MyModel>(
      create: (context) => MyModel(),
      builder: (context, _) {
        final state = context.watch<MyModel>();
        return MaterialApp(
          home: Navigator(
            pages: [
              MaterialPage(child: W1(onTapped: null)),
              if (state.go) MaterialPage(child: W2()),
            ],
            onPopPage: (route, result) {
              if (!route.didPop(result)) return false;
              state.go = false;
              return true;
            },
            observers: [heroC],
          ),
        );
      },
    );
  }
}

Thankyou.

Answered By – Midhunraj R Pillai

Answer Checked By – Marilyn (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published.