I am currently learning how to use the Bloc state management library in Flutter, and I have a scenario where I would like to listen to an action performed (e.g. clicking a button) and react to that action in a child widget down the widget tree using a Cubit. From the documentation, it seems like the only way to rebuild child widgets is by storing a state object and reacting to changes in the state. However in my case, I don't need to store any state, I just want to know if an action has been done and react every single time it has been invoked.
Given this example below, I want WidgetB
to rebuild whenever the button in WidgetA
has been pressed, but I can't figure out how to construct my Cubit to allow that without storing some kind of state object.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// What should the code be here if I don't want to store any state?
class WidgetACubit extends Cubit<void> {
WidgetACubit({initialState}) : super(initialState);
void doSomething() => emit(null);
}
class App extends StatelessWidget {
App({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlocProvider(
create: (_) => WidgetACubit(),
child: WidgetA(),
),
),
);
}
}
class WidgetA extends StatelessWidget {
WidgetA({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: () {
BlocProvider.of<WidgetACubit>(context).doSomething();
print("Something has been done");
WidgetB.count++;
},
child: const Text("Press me to do something"),
),
WidgetB(),
],
);
}
}
class WidgetB extends StatelessWidget {
static int count = 0;
WidgetB({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<WidgetACubit, void>(
builder: (context, state) {
return Text("I've been rebuilt $count times");
},
);
}
}
I can't add the code as a comment to my original answer because it's too long. The somewhat "correct"-ish way would be to not use bloc for this. You don't need a state management solution when you don't want to have any state. You can create a widget to achieve the behavior you desire.
class RebuildInvokerProvider extends InheritedWidget {
const RebuildInvokerProvider({
Key? key,
required Widget child,
required this.rebuild,
}) : super(key: key, child: child);
static RebuildInvokerProvider of(BuildContext context) {
final RebuildInvokerProvider? result =
context.dependOnInheritedWidgetOfExactType<RebuildInvokerProvider>();
assert(result != null, 'No RebuildInvokerProvider found in context');
return result!;
}
final void Function() rebuild;
@override
bool updateShouldNotify(RebuildInvokerProvider oldWidget) {
return oldWidget.rebuild != rebuild;
}
}
class RebuildInvoker extends StatefulWidget {
const RebuildInvoker({
super.key,
required this.child,
});
final Widget child;
@override
State<RebuildInvoker> createState() => _RebuildInvokerState();
}
class _RebuildInvokerState extends State<RebuildInvoker> {
@override
Widget build(BuildContext context) {
return RebuildInvokerProvider(
child: widget.child,
rebuild: () => setState(() {}),
);
}
}
You can use a custom widget like this to achieve the behavior you desire. Wrap a widget with a RebuildInvoker, and when you want to rebuild that widget and all its children from within one of the children, call RebuildInvokerProvider.of(context).rebuild()
. Not sure why you would want to do this though. I did not test the code by the way.
PS.
returning oldWidget.rebuild != rebuild
from updateShouldNotify is not necessary for this to work and does not really have anything to do with how and why this works. This just causes widgets that depend on RebuildInvoker to also do a rebuild when the value of rebuild
changes. But this value stays the same, in this particular example anyways. Just added this PS to avoid confusion.