1

I'm working with Provider

provider: ^4.1.2

I had 2 pages : A, B.

Page A and B show same simple design, with below codes :

PageA.dart

class PageA extends StatelessWidget {

   var songTitle = '';

   @override
   Widget build(BuildContext c) {
     return Column(children: <Widget>[
         FloatingActionButton.extended(
            onPressed: () {
                 // LOGIC : TRANSFER TO PAGE B AFTER CLICKED BUTTON FROM PAGE A
                 Navigator.push(context, MaterialPageRoute(
                     builder: (context) => PageB()));
            },
            label: Text('Click'),
           ),
         ),
         Text('$songTitle');
         // STEP 2 : while page B already got `songTitle` 
         // to show on UI, I want `songTitle` also update on this page A 
         // in the same time
     ]);
      
  }
}

PageB.dart

class PageB extends StatelessWidget {

   var songTitle = '';

   @override
   Widget build(BuildContext c) {
     return Text('$songTitle');
   }

   @override
   void initState() {
     super.initState();

     // LOGIC : CALL API TO GET `songTitle` TO UPDATE ON UI
     api.request().then((title) {
        setState(() {
           this.songTitle = title;
           // STEP 1 : this causes update to page B 
           // show `songTitle` got from api success
        });
     });
   }

}

These codes run with no bugs.

What I want is after Step 1 got songTitle data,

It will updated its data to page A (Step 2) and page B (Step 1) in the same time by using Provider (ex. Provider.of(context) ...)

People who knows,

Please tell me,

Thank you,

UPDATED I got correct @Chichebe answer in below already.

Huy Tower
  • 7,769
  • 16
  • 61
  • 86

2 Answers2

3

main.dart

import 'dart:async';

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return MyAppState();
  }
}

class MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [ChangeNotifierProvider<Song>(create: (context) => Song())],
      child: MaterialApp(home: PageA()),
    );
  }
}

Song Model

class Song extends ChangeNotifier {
  String songTitle = 'Title';

  void updateSongTitle(String newTitle) {
    songTitle = newTitle;

    notifyListeners();
  }
}

pageA

class PageA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      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.push(
                  context,
                  MaterialPageRoute(builder: (context) => PageB()),
                ),
                child: Text('Button'),
              ),
            ],
            mainAxisAlignment: MainAxisAlignment.center,
          );
        }),
      ),
    );
  }
}

pageB

class PageB extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return PageBState();
  }
}

class PageBState extends State<PageB> {
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    Timer(Duration(seconds: 2), () {
      final song = Provider.of<Song>(context, listen: false);

      song.updateSongTitle('New Title');
    });
  }

  @override
  Widget build(BuildContext c) {
    return Container(
      child: Text('Page B'),
      color: Colors.white,
    );
  }
}

p/s :I understand that

  • use the Provider package in managing your state and passing data down your widget tree.
  • use ChangeNotifier alongside ChangeNotifierProvider to achieve this.
Huy Tower
  • 7,769
  • 16
  • 61
  • 86
Chichebe
  • 580
  • 5
  • 12
  • I got this approach already and applied, but it not worked. As in ex. in the question, after I applied, only at `Consumer` in `PageB` fired when `notifyListener()` was called. In my case, I want `Consumer` in `pageA` and `pageB` was called. Thank you for your explain, My refs : https://stackoverflow.com/questions/57910630/persist-provider-data-across-multiple-pages-not-working – Huy Tower Jun 03 '20 at 02:34
  • Did you wrap both PageA and PageB with a consumer? – Chichebe Jun 03 '20 at 03:54
  • I used separate `Consumer` inside page A & B. How you wrap it for both them? You can edit answer? Page A go to page B by `onPress` action by using `Navigator.push` – Huy Tower Jun 03 '20 at 07:16
  • I've edited it. Apply the new codes, should work for you now. – Chichebe Jun 03 '20 at 11:25
  • hello @Chichebe, I followed this way as full example code in edited question, but it does not work. Could you take a quick look for this? (p/s : I don't use HTTP requests) – Huy Tower Jun 24 '20 at 09:24
  • Refer to PageA on your code: song != null ? Text(song.songTitle) : Text(''), - Remember that the 'song' from your Consumer is just an instance of the class Song so you actually need to write song.songTitle != null in your ternary operator.The second issue is with your PageB class in your BuildContext. Calling the updateSongTitle() method in the build context can result in the error you're getting. Please refer to https://stackoverflow.com/questions/45409565/flutter-setstate-or-markneedsbuild-called-when-widget-tree-was-locked on how to fix it – Chichebe Jun 27 '20 at 01:11
  • Okay, thank you for your detail, I archive this target. – Huy Tower Jul 21 '20 at 02:07
1

Have you read the Provider examples?

  • In flutter don't let your widgets handle logic operations, such as making API requests, you could do that in your provider, and even better create an independent class that will handle HTTP requests for you, then you inject it in your provider code,

You could do something like that:

class Song{
  String title;

  Song(this.title);
}

class MyProvider extends ChangeNotifier{
   Song song;

   void getSongInformation(String song) async{
       try{
          final req = await doYourAPIRequest();
          song = Song(req['title']);

          notifyListeners(); // Notify the listeners about this change

       }catch(e){
          // handle the error
       }
   }
}

Then in your Widgets

Consumer<Song>(
  builder: (context, song, child) {
    return Text(song.title);
  },
)

So any Widgets that is listening for Song changes will be notified about any changes when you call notifyListeners(), if you only need the Provider to notify about a single Song change, then you can also use ValueNotifierProvider. Also, it's good practice to put your Consumer deep as possible in the Widget tree in order to rebuild only the Widgets that depends on that value.

Leo Letto
  • 1,774
  • 1
  • 12
  • 17
  • 1
    Sorry, it does not related to my question. I does not remind about HTTP request to parse data and show on UI. – Huy Tower Jun 24 '20 at 09:17