Flutter: ListenableBuilder is rebuilding the child widget

378 views Asked by At

The child widget of the ListenableBuilder is rebuilt even though it claims to preserve the child widget from re-building.

In the example below counterNotifier is an instance of ChangeNotifier. I believe there is no need to see its implementation, I'm simply calling notifyListener() when changes are done.

      ListenableBuilder(
        listenable: counterNotifier,
        builder: (context, child) {
          if (counterNotifier.isLoading) {
            return const AppLoader();
          }
          if (counterNotifier.value == null) {
            return const SizedBox.shrink();
          }
          return Row(
            mainAxisAlignment: MainAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: [
              child!,
              Text('${counterNotifier.value}'),
            ],
          );
        },
        child: const CountText(),
      ),
1

There are 1 answers

0
Arnold Parge On BEST ANSWER

Issue

The reason why the child is rebuilt is that I was returning the widget conditionally in the builder callback.

This changes the widget tree as per the condition, hence the child was not preserved in the widget tree.


Fix

To fix this, we can use Offstage which hides the widget from view but the widget is still present in the widget tree.


Example

After changes the ListenableBuilder will look like this:

return ListenableBuilder(
  listenable: listenable,
  builder: (context, child) {
    final isLoading = listenable.isLoading;
    final hasData = listenable.value != null;
    return Stack(
      children: [
        Offstage(
          offstage: !isLoading,
          child: const AppLoader(),
        ),
        Offstage( // <-- This one is unnecessary in this case. This is just for the sake of converting the code.
          offstage: !hasData,
          child: const SizedBox.shrink(),
        ),
        Offstage(
          offstage: isLoading || !hasData,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: [
              child!,
              Text('${listenable.value}'),
            ],
          ),
        ),
      ],
    );
  },
  child: child,
);

Note

Instead of Stack, you can use whatever best suits you.