Scaffold Widget has persistentFooterButtons property. How can we implement it if we want to show and hide it depending on the other widget state?
Common function to generate footer widget
I will show 4 ways to show/hide persistentFooterButtons. The following function is called in all of them.
List<Widget> _createFooterWidgets(int number) {
return [
Center(
child: Card(
child: Text(
"I am footer $number.",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
),
];
}
The top-level widget looks like this.
class RiverpodWithVisibility extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SafeArea(
child: PageView(
children: [
_View1(),
_View2(),
_View3(),
_View4(),
],
),
);
}
}
Call setState function
The first way is to call setState. _visible variable value changes when pressing the edit icon button. It sets null when the value is false.
class _View1 extends StatefulWidget {
@override
_View1State createState() => _View1State();
}
class _View1State extends State<_View1> {
bool _visible = false;
@override
Widget build(BuildContext context) {
final footer = _visible ? _createFooterWidgets(1) : null;
return Scaffold(
appBar: AppBar(title: Text("Visibility sample")),
body: Center(
child: IconButton(
icon: Icon(Icons.edit),
onPressed: () => setState(() => _visible = !_visible),
),
),
persistentFooterButtons: footer,
);
}
}
It works but it’s not efficient if there are many widgets.
Using Riverpod StateProvider
Let’s use Riverpod in order to rebuild only the target widget. Since the target widget is top-level, I guess the performance is the same as the previous one.
Set a Visibility widget
We can hide the target widget if we use Visibility widget. If the property visible is true, the child widget appears, otherwise, it disappears.
class _View2 extends ConsumerWidget {
final StateProvider<bool> _provider = StateProvider((ref) => false);
@override
Widget build(BuildContext context, ScopedReader watch) {
return Scaffold(
appBar: AppBar(title: Text("Visibility sample2")),
body: Center(
child: IconButton(
icon: Icon(Icons.edit),
onPressed: () =>
context.read(_provider).state = !context.read(_provider).state,
),
),
persistentFooterButtons: [
Visibility(
child: _createFooterWidgets(2).first,
visible: watch(_provider).state,
)
],
);
}
}
class _View2 extends ConsumerWidget {
final StateProvider<bool> _provider = StateProvider((ref) => false);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: Text("Visibility sample2")),
body: Center(
child: IconButton(
icon: Icon(Icons.edit),
onPressed: () => ref.read(_provider.state).state =
!ref.read(_provider.state).state,
),
),
persistentFooterButtons: [
Visibility(
child: _createFooterWidgets(2).first,
visible: ref.watch(_provider.state).state,
)
],
);
}
}
It looks working at first glance… But, wait… Take a look at it again.
An unnecessary line is shown. If we want to completely hide it we have to set null.
Visibility widget isn’t useful in this case.
Watch the visible state and provide a footer widget
We need to set null if the visible is false. We can provide the footer widget via Provider. Since it’s not possible to use other variables in an initializer, we need to define the provider outside of a class. It’s a global space.
final _visibleProvider = StateProvider((ref) => false);
final _footerProvider = StateProvider<List<Widget>?>((ref) {
// Listen the visible state
final visible = ref.watch(_visibleProvider).state;
return visible ? _createFooterWidgets(3) : null;
});
class _View3 extends ConsumerWidget {
@override
Widget build(BuildContext context, ScopedReader watch) {
return Scaffold(
appBar: AppBar(title: Text("Visibility sample3")),
body: Center(
child: IconButton(
icon: Icon(Icons.edit),
onPressed: () => context.read(_visibleProvider).state =
!context.read(_visibleProvider).state,
),
),
persistentFooterButtons: watch(_footerProvider).state,
);
}
}
final _visibleProvider = StateProvider((ref) => false);
final _footerProvider = StateProvider<List<Widget>?>((ref) {
final visible = ref.watch(_visibleProvider.state).state;
return visible ? _createFooterWidgets(3) : null;
});
class _View3 extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: Text("Visibility sample3")),
body: Center(
child: IconButton(
icon: Icon(Icons.edit),
onPressed: () => ref.read(_visibleProvider.state).state =
!ref.read(_visibleProvider.state).state,
),
),
persistentFooterButtons: ref.watch(_footerProvider.state).state,
);
}
}
An unnecessary line doesn’t appear when the footer is hidden. Nice.
Provider with a ternary operator
The previous way works but is the second provider really necessary? We can remove it in the following way.
class _View4 extends ConsumerWidget {
final StateProvider<bool> _provider = StateProvider((ref) => false);
@override
Widget build(BuildContext context, ScopedReader watch) {
final visible = watch(_provider).state;
return Scaffold(
appBar: AppBar(title: Text("Visibility sample4")),
body: Center(
child: IconButton(
icon: Icon(Icons.edit),
onPressed: () =>
context.read(_provider).state = !context.read(_provider).state,
),
),
persistentFooterButtons: visible ? _createFooterWidgets(4) : null,
);
}
}
class _View4 extends ConsumerWidget {
final StateProvider<bool> _provider = StateProvider((ref) => false);
@override
Widget build(BuildContext context, WidgetRef ref) {
final visible = ref.watch(_provider.state).state;
return Scaffold(
appBar: AppBar(title: Text("Visibility sample4")),
body: Center(
child: IconButton(
icon: Icon(Icons.edit),
onPressed: () => ref.read(_provider.state).state =
!ref.read(_provider.state).state,
),
),
persistentFooterButtons: visible ? _createFooterWidgets(4) : null,
);
}
}
I prefer this way.
Complete code is here
Comments