Search code examples
flutterdartprovider

Flutter rebuild provider above MaterialApp


In my application, I defined a provider Song above MaterialApp in order to have access to it through my application.


Need: I did this because I have a PageA which contains a songTitle variable and a button to go to PageB. On PageB, I have a button that calls my provider Song and updates PageA. So when I'm on PageB and do a Navigator.pop (context) I come back to PageA and see the updated songTitle variable. To be able to update the PageA from the PageB I have to put my provider Song above MaterialApp.

==> It works.


My problem: I want to be able to reset my provider when I call PageA. So if my songTitle variable had been updated and I quit pageA, I want my songTitle variable to return to its default value when I initialize the provider Song. For the moment the songTitle variable remains updated all the time ...


Here the router:

abstract class RouterClass{

  static Route<dynamic> generate(RouteSettings settings){
    final args = settings.arguments;

    switch(settings.name){

      case RouterName.kMenu:
        return CupertinoPageRoute(
            builder: (context) => Menu()
        );
      case RouterName.kPageA:
        return CupertinoPageRoute(
            builder: (context) => PageA()
        );
      case RouterName.kPageB:
        return CupertinoPageRoute(
            builder: (context) => PageB()
        );

      default:
        return CupertinoPageRoute(
            builder: (context) => Error404View(title: "Error")
        );
    }
  }
}

The Menu:

class Menu extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text('Menu'),
        ),
        body : Center(
          child: MaterialButton(
            onPressed: () {
              Navigator.pushNamed(
                    context,
                    RouterName.kPageA,
                  ),
            },
            child: Text('Button'),
          ),
        ),
      ),
    );
  }
}

The PageA:

class PageA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
        title: Text('pageA'),
      ),
        body : Center(
          child: Consumer<Song>(builder: (context, song, child) {
            print('Consumer() : ${song.songTitle}');

            return Column(
              children: <Widget>[
                // SONG TITLE
                Text(song.songTitle),
                // Button
                MaterialButton(
                  onPressed: () => Navigator.pushNamed(
                    context,
                    RouterName.kPageB,
                  ),
                  child: Text('Button'),
                ),
              ],
            );
          }),
        ),
      ),
    );
  }
}

The PageB:

class PageB extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text('pageB'),
        ),
        body : Center(
          child: MaterialButton(
            onPressed: () {
              Provider.of<Song>(context, listen: false).updateSongTitle('New Title');
            },
            child: Text('Button'),
          ),
        ),
      ),
    );
  }
}

The provider Song:

class Song extends ChangeNotifier {
  late String songTitle;

  Song(){
    _initialise();
  }

  Future _initialise() async
  {
    songTitle = "Title";
    notifyListeners();
  }

  void updateSongTitle(String newTitle) {
    songTitle = newTitle;

    notifyListeners();
  }
}

Solution

  • Use create in PageA:

    child: ChangeNotifierProvider(
      create: (_) => Song(),
      child: Consumer<Song>(
        builder: (context, song, child) {
          print('Consumer() : ${song.songTitle}');
    ...
    

    Pass your song object to PageB:

    Navigator.pushNamed(
      context,
      '/pageB',
      arguments: song,
    );
    

    Get song in PageB:

    final song = ModalRoute.of(context)!.settings.arguments as Song;
    

    Full code:

    // ignore_for_file: avoid_print
    
    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    void main() => runApp(const MyApp());
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          routes: {
            '/': (_) => const HomePage(),
            '/pageA': (_) => const PageA(),
            '/pageB': (_) => const PageB(),
          },
        );
      }
    }
    
    class HomePage extends StatefulWidget {
      const HomePage({Key? key}) : super(key: key);
    
      @override
      _HomePageState createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: GestureDetector(
              onTap: () {
                Navigator.pushNamed(
                  context,
                  '/pageA',
                );
              },
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              ),
            ),
          ),
        );
      }
    }
    
    class PageA extends StatefulWidget {
      const PageA({Key? key}) : super(key: key);
    
      @override
      State<PageA> createState() => _PageAState();
    }
    
    class _PageAState extends State<PageA> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: ChangeNotifierProvider(
              create: (_) => Song(),
              child: Consumer<Song>(
                builder: (context, song, child) {
                  print('Consumer() : ${song.songTitle}');
                  return Column(
                    mainAxisSize: MainAxisSize.min,
                    children: <Widget>[
                      // SONG TITLE
                      Text(song.songTitle),
                      // Button
                      MaterialButton(
                        onPressed: () {
                          Navigator.pushNamed(
                            context,
                            '/pageB',
                            arguments: song,
                          );
                        },
                        child: const Text('Button'),
                      ),
                    ],
                  );
                },
              ),
            ),
          ),
        );
      }
    }
    
    class PageB extends StatelessWidget {
      const PageB({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        final song = ModalRoute.of(context)!.settings.arguments as Song;
        return SafeArea(
          child: Scaffold(
            appBar: AppBar(
              title: const Text('pageB'),
            ),
            body: Center(
              child: MaterialButton(
                onPressed: () {
                  song.updateSongTitle('New Title');
                },
                child: const Text('Button'),
              ),
            ),
          ),
        );
      }
    }
    
    class Song extends ChangeNotifier {
      late String songTitle;
    
      Song() {
        _initialise();
      }
    
      Future _initialise() async {
        songTitle = "Title";
        notifyListeners();
      }
    
      void updateSongTitle(String newTitle) {
        songTitle = newTitle;
    
        notifyListeners();
      }
    }