arrow-left arrow-right brightness-2 chevron-left chevron-right facebook-box facebook loader magnify menu-down rss-box star twitter-box youtube-box twitter white-balance-sunny window-close
Programatically Display Widgets with Flutter
4 min read

Programatically Display Widgets with Flutter

Programatically Display Widgets with Flutter

In this article we're going to look at how we could programatically display widgets with Flutter. If you're reading this, you'll have come across a situation where you want to conditionally remove a widget from the tree but may not know the different ways you can do it.

Project Setup

Create a new Flutter project in the terminal:

$ flutter create flutter_visibility

$ cd flutter_visibility && code .

Our project will be a small one:

  1. We'll have a checkbox which controls whether to display the I'm visible! box.
  2. When the box is clicked, we'll show a SnackBar to show the differences between Opacity and Visibility.

Our MyBox widget will be a small box with an onTap event:

import 'package:flutter/material.dart';

class MyBox extends StatelessWidget {
  final String title;
  final VoidCallback onTap;

  const MyBox({Key key, @required this.title, @required this.onTap})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        padding: const EdgeInsets.all(20.0),
        decoration: BoxDecoration(
          color: Colors.blue,
        ),
        child: Text(
          title,
          style: TextStyle(color: Colors.white),
        ),
      ),
    );
  }
}

We can then wire up our HomePage to use MyBox:

import 'package:flutter/material.dart';
import 'package:flutter_visibility/widgets/my_box.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  bool _shouldShow;
  GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  void initState() {
    super.initState();

    _shouldShow = true;
    _scaffoldKey = GlobalKey<ScaffoldState>();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      body: Column(
        children: [
          CheckboxListTile(
            value: _shouldShow,
            title: Text("Show Box"),
            onChanged: (bool value) => setState(() {
              _shouldShow = value;
            }),
          ),
          MyBox(
            onTap: () => _scaffoldKey.currentState.showSnackBar(
              SnackBar(
                content: Text("Hello, SnackBar!"),
              ),
            ),
            title: "I'm visible!",
          )
        ],
      ),
    );
  }
}

Ternary ? or if

The first way we're going to investigate is with the use of an if statement or ? operator. This allows us to switch out a widget based on a particular condition, such as the value of _shouldShow:

_shouldShow
    ? MyBox(
        onTap: () => _scaffoldKey.currentState.showSnackBar(
          SnackBar(
            content: Text("Hello, SnackBar!"),
          ),
        ),
        title: "I'm visible!",
      )
    : SizedBox.shrink(),

This allows us to switch out MyBox for an empty SizedBox if _shouldShow is false. This works, but it does have the overhead of having to create an empty SizedBox as we can't provide null.

We can replace it with an if statement:

if (_shouldShow)
  MyBox(
    onTap: () => _scaffoldKey.currentState.showSnackBar(
      SnackBar(
        content: Text("Hello, SnackBar!"),
      ),
    ),
    title: "I'm visible!",
  )

I think this looks cleaner than our previous example. You could also use an else here if you wanted to:

if (_shouldShow)
  MyBox(
    onTap: () => _scaffoldKey.currentState.showSnackBar(
      SnackBar(
        content: Text("Hello, SnackBar!"),
      ),
    ),
    title: "I'm visible!",
  )
else
  Text("Not showing!")
NOTE: In order to use this feature you'll need to be targeting Dart 2.3 and above in your pubspec.yaml.

However, I don't love the idea of having too many nested if/else statements to manage control flow inside of our build method.

It would be even better if we had a widget that could help us with this, such as... Opacity or Visibility?

Opacity

Opacity allows us to control, well, the opacity of a widget. Assuming that we wanted to hide a widget but not remove it from the tree, Opacity is your friend.

Opacity(
  opacity: _shouldShow ? 1 : 0,
  child: MyBox(
    onTap: () => _scaffoldKey.currentState.showSnackBar(
      SnackBar(
        content: Text("Hello, SnackBar!"),
      ),
    ),
    title: "I'm still clickable!",
  ),
),

It does come with a caveat that might not be initially apparent. If we have the opacity value set to 0, the widget is still there and still takes up space, it just isn't visible.

That's why if we select it in space, we still see our SnackBar and our onTap event has been fired.

Therefore, I wouldn't suggest using Opacity for our use case of programatically showing a widget based on a conditional

What about Visibility?

Visibility

The Visibility widget allows us to set the visible boolean which we've set to our _shouldShow.

Visibility(
  visible: _shouldShow,
  child: MyBox(
    onTap: () => _scaffoldKey.currentState.showSnackBar(
      SnackBar(
        content: Text("Hello, SnackBar!"),
      ),
    ),
    title: "I'm visible!",
  ),
),

Our box will now be removed from the Widget tree whenever _shouldShow is false. This is important - because if you try and click where the widget would have been, we don't get our SnackBar popup!

We can even add a replacement for when the MyBox widget isn't being shown:

Visibility(
  visible: _shouldShow,
  replacement: Text("I'm shown instead of the box!"),
  child: MyBox(
    onTap: () => _scaffoldKey.currentState.showSnackBar(
      SnackBar(
        content: Text("Hello, SnackBar!"),
      ),
    ),
    title: "I'm visible!",
  ),
),

When the box isn't visible, it'll then show some Text:

We can also configure the Visibility widget to maintain attributes such as its size, interactivity and so on. This would allow the widget to still exist in space and/or be interactable if required:

Visibility(
  visible: _shouldShow,
  maintainAnimation: true,
  maintainState: true,
  maintainSize: true,
  maintainSemantics: true,
  maintainInteractivity: true,
  child: MyBox(
    onTap: () => _scaffoldKey.currentState.showSnackBar(
      SnackBar(
        content: Text("Hello, SnackBar!"),
      ),
    ),
    title: "I'm visible!",
  ),
),
Text("I'm under MyBox!")

We can see an example of the MyBox still taking up space with the maintainX attributes set to true here:

Summary

In this article we identified numerous ways to programatically display/hide a widget within Flutter.

In most circumstances, the Visibility widget is the preferred method for removing widgets from the tree when they're no longer needed. Other options such as Opacity, AnimatedOpacity, Offstage can also be used, but careful consideration is required.