Trigger a rebuild of PopupMenuItem based on event in another PopupMenuItem

104 views Asked by At

I want to synchronize values across PopupMenuItems as seen in the image below image

I would like the second menu item to also show 4. If the menu is closed, the numbers will all be the same before I push the + icon again.

See below for the code. I'm using Riverpod, but I'm open to other solutions too. I understand that the second PopupMenuItem doesn't rebuild when the first item changes, but I don't know how to force a rebuild.

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

final providerOfCount = StateProvider<int>((ref) => 0);

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends ConsumerWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends ConsumerStatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  @override
  ConsumerState<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends ConsumerState<MyHomePage> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    var count = ref.watch(providerOfCount);
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            PopupMenuButton(
              child: const Text('Push me!',
                  style: TextStyle(
                    color: Colors.purple,
                    fontSize: 24,
                  )),
              itemBuilder: (BuildContext context) {
                var count = ref.watch(providerOfCount);
                return [
                  /// Need a stateful builder to sync the number inside the Menu
                  PopupMenuItem(
                      child: StatefulBuilder(builder: (context, setState) {
                    return Row(
                      children: [
                        IconButton(
                            onPressed: () {
                              setState(() {
                                ref
                                    .read(providerOfCount.notifier)
                                    .update((state) => state + 1);
                                _counter++;
                              });
                            },
                            icon: const Icon(
                              Icons.add,
                              color: Colors.purple,
                            )),
                        Text('   ${ref.read(providerOfCount.notifier).state}'),
                        Text('   $count'),
                        Text('   $_counter'),
                      ],
                    );
                  })),
                  PopupMenuItem(
                      child: Text(
                          'Count: ${ref.read(providerOfCount.notifier).state}  '
                          '$count  $_counter',
                          style: const TextStyle(color: Colors.red))),
                ];
              },
            ),
            const SizedBox(
              height: 72,
            ),
            const SizedBox(
              height: 72,
            ),
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$count',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
    );
  }
}

Thanks, Tony

1

There are 1 answers

0
user3103070 On

Turned out it was pretty easy. I needed to wrap the Text of the second PopMenuItem with a riverpod Consumer widget and listen for the provider there.
See below for the solution.

enter image description here

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

final providerOfCount = StateProvider<int>((ref) => 0);

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends ConsumerWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends ConsumerStatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  @override
  ConsumerState<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends ConsumerState<MyHomePage> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    var count = ref.watch(providerOfCount);
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            PopupMenuButton(
              position: PopupMenuPosition.under,
              child: const Text('Push me!',
                  style: TextStyle(
                    color: Colors.purple,
                    fontSize: 24,
                  )),
              itemBuilder: (BuildContext context) {
                return [
                  /// Need a stateful builder to sync the number inside the MenuItem
                  PopupMenuItem(
                      child: StatefulBuilder(builder: (context, setState) {
                    return Row(
                      children: [
                        IconButton(
                            onPressed: () {
                              setState(() {
                                ref
                                    .read(providerOfCount.notifier)
                                    .update((state) => state + 1);
                                _counter++;
                              });
                            },
                            icon: const Icon(
                              Icons.add,
                              color: Colors.purple,
                            )),
                        Text('   ${ref.read(providerOfCount.notifier).state}'),
                        Text('   $_counter'),
                      ],
                    );
                  })),
                  PopupMenuItem(child: Consumer(
                    builder: (context, ref, child) {
                      var count = ref.watch(providerOfCount);
                      return Text(
                          'Count: ${ref.read(providerOfCount.notifier).state}  '
                          '$count',
                          style: const TextStyle(color: Colors.red));
                    },
                  )),
                ];
              },
            ),
            const SizedBox(
              height: 150,
            ),
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$count',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
    );
  }
}