Flutter 4 ways to hide persistentFooterButtons

eye-catchDart and Flutter
Sponsored links

Scaffold Widget has persistentFooterButtons property. How can we implement it if we want to show and hide it depending on the other widget state?

Sponsored links

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(),
        ],
      ),
    );
  }
}
Sponsored links

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.

The Riverpod version is 1.0.0. If you want to check the code for 0.14.0+3, open the toggle.

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

flutter_samples/riverpod-with-visibility.dart at main · yuto-yuto/flutter_samples
Contribute to yuto-yuto/flutter_samples development by creating an account on GitHub.

Comments

Copied title and URL