How to use Provider's ChangeNotifier with a stateful widget?

62 Views Asked by At

I am building a photo editor in Flutter. My home page contains variables such as a list of layers and the currentlySelected layer. My home page has a child widget, Toolbar, which needs access to those variables and must update homePage after changing them. I want to use Provider for this, but I can't figure out how to set that up since I cant extend two classes at once.

import 'package:provider/provider.dart';

class Toolbar extends StatefulWidget {
  const Toolbar({super.key});

  @override
  State<Toolbar> createState() => _ToolbarState();
}

class _ToolbarState extends State<Toolbar> {  //EXTENDS ChangeNotifer????
  
  XFile? _image; 
  final _picker = ImagePicker();

  Future<void> _getImageFromGallery() async {
    final XFile? pickedFile = await _picker.pickMedia();
      if (pickedFile != null) {
        _image = XFile(pickedFile.path);
      }
  }

  void _addTextEntity() {
    TextEntity newTextEntity = TextEntity(
      controller: TextEntityController(), 
    );
    selectedLayer = newTextEntity; //Variable from parent
    textLayers.add(newTextEntity); //Variable from Parent
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: [
        PopupMenuButton(
          icon: const Icon(Icons.edit), //Leftmost icon on the toolbar
          itemBuilder: (BuildContext context) => [
            PopupMenuItem(
              child: ListTile(
                title: const Text("Add Image"),
                trailing: const Icon(Icons.image),
                onTap: () => {
                  _getImageFromGallery(),
                  Navigator.pop(context) //Terminates the project manager
                },
              )
            ),
            PopupMenuItem(
              child: ListTile(
                title: const Text("Add Shape"),
                trailing: const Icon(Icons.shape_line),
                onTap: () => {
                  /** Add Shape  */
                  Navigator.pop(context),
                }
              )
            ),
            PopupMenuItem(
              child: ListTile(
                title: const Text("Add Text"),
                trailing: const Icon(Icons.text_fields),
                onTap: () => {
                  _addTextEntity(),
                  Navigator.pop(context)
                }
              )
            ),
          ],
        ),
        
        const VerticalDivider(), //To seperate persistent project manager from the rest of the toolbar

        Row(
          children: [
            //Change Font
            IconButton(
              icon: const Icon(BoxIcons.bx_font),
              onPressed: () { /** Launch change font menu */},
            ),
            //Change Font Size
            IconButton(
              icon: const Icon(BoxIcons.bx_font_size),
              onPressed: () { /** Launch change font size slider */},
            ),
            //Justify
            IconButton(
              icon: const Icon(Icons.format_align_left),
              onPressed: () { /** Launch formating menu */},
            ),
            //Change Color
            IconButton(
              icon: const Icon(BoxIcons.bx_font_color),
              onPressed: () { /** Launch color picker */},
            ),
            //Add Stroke
            IconButton(
              icon: const Icon(LineAwesome.heading_solid),
              onPressed: () { /** Launch stroke slider */},
            ),
            //Add Shadow
            IconButton(
              icon: const Icon(Bootstrap.shadows),
              onPressed: () { /** Launch shadow sliders */},
            ),
          ]
        )
      ]
    );
  }

}
1

There are 1 best solutions below

0
Rashmi Rekha On

You can achieve this by introducing a separate class for Change Notifiers. I will provide sample codes to get an idea.

Here I created a class called AppData to update the value of textToBeDisplayed variable and to keep notify about the value changes.

class AppData extends ChangeNotifier {
 String textToBeDisplayed = "Hello";
  void updateText(String text) {
   textToBeDisplayed = text;
   notifyListeners();
 }
}

Then add ChangeNotifiersProvider and AppData to the MyApp class as demonstrated below:

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

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

 @override
 Widget build(BuildContext context) {
    return ChangeNotifierProvider(
    create: (context) => AppData(),
    child: MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.yellow,
      ),
      home: FirstPage()),
   );
 }
}

Now, you can access the value of textToBeDisplayed from anywhere in the app using the following code:

String text =
    Provider.of<AppData>(context, listen: false).textToBeDisplayed;

Whenever you need to change the value of textToBeDisplayed, you can achieve that with the following code:

Provider.of<AppData>(context, listen: false).updateText('World');

Hope this explanation helps. Feel free to ask if anything remains unclear.