Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ValueListenableBuilder: listen to more than one value

Tags:

flutter

I am using ValueListenableBuilder to watch some values in several animated custom widgets. Those are using several values, based on their children sizes, to animate depending actions.

My problem is listening to more than one value. I have to nest them.

The following is a reduced example to explain:

class MyWidget extends StatelessWidget {
  final ValueNotifier<double> _height1 = ValueNotifier<double>(40);
  final ValueNotifier<double> _height2 = ValueNotifier<double>(120);
  final ValueNotifier<double> _height3 = ValueNotifier<double>(200);

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
      builder: (BuildContext context, double h1, Widget child) {
        return ValueListenableBuilder(
          builder: (BuildContext context, double h2, Widget child) {
            return ValueListenableBuilder(
                builder: (BuildContext context, double h3, Widget child) {
                  return GestureDetector(
                    // i am using h1, h2 and h3 here ...
                    child: Stack(
                      children: <Widget>[
                        Positioned(
                          left: 0,
                          right: 0,
                          bottom: value,
                          child: Column(
                            children: <Widget>[
                              Container(height: _height1.value),
                              Container(height: _height2.value),
                              Container(height: _height3.value),
                            ],
                          ),
                        ),
                      ],
                    ),
                  );
                },
                valueListenable: _height3,
                child: null);
          },
          valueListenable: _height2,
          child: null,
        );
      },
      valueListenable: _height1,
      child: null,
    );
  }
}

https://github.com/flutter/flutter/issues/40906#issuecomment-533383128 give a short hint to Listenable.merge, but I have no idea how to use this.

like image 850
n8crwlr Avatar asked Sep 20 '19 14:09

n8crwlr


People also ask

How do you use value Notifier?

ValueNotifiers can hold data of type int, String, double, boolean and even you can use your own Datatype (using class). import 'package:flutter/material. dart';ValueNotifier<int> buttonClickedTimes =ValueNotifier(0);

What is value listener flutter?

ValueListenableBuilder will listen for changes to a value notifier and automatically rebuild its children when the value changes. If you have more complex data, create a custom notifier for your data classes by extending ValueNotifier.

What is ValueNotifier?

ValueNotifier is a special type of class that extends Changenotifier, which can hold a single value and notifies the widgets which are listening to it whenever its holding value gets change. ChangeNotifier is a class that provides change notification to its listeners.


2 Answers

There's nothing built-in.

You could use Listenable.merge. But it has flaws:

  • it's not "safe" (you can forget to listen to a value)
  • it's not ideal performance-wise (since we're recreating a Listenable on every build)

Instead, we can use composition: we can write a stateless widget that combines 2+ ValueListenableBuilders into one, and use that.

It'd be used this way:

ValueListenable<SomeValue> someValueListenable;
ValueListenable<AnotherValue> anotherValueListenable;

ValueListenableBuilder2<SomeValue, AnotherValue>(
  someValueListenable,
  anotherValueListenable,
  builder: (context, someValue, anotherValue, child) {
    return Text('$someValue $anotherValue');
  },
  child: ...,
);

Where the code of such ValueListenableBuilder2 is:

class ValueListenableBuilder2<A, B> extends StatelessWidget {
 ValueListenableBuilder2(
    this.first,
    this.second, {
    Key key,
    this.builder,
    this.child,
  }) : super(key: key);

  final ValueListenable<A> first;
  final ValueListenable<B> second;
  final Widget child;
  final Widget Function(BuildContext context, A a, B b, Widget child) builder;

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<A>(
      valueListenable: first,
      builder: (_, a, __) {
        return ValueListenableBuilder<B>(
          valueListenable: second,
          builder: (context, b, __) {
            return builder(context, a, b, child);
          },
        );
      },
    );
  }
}

UPDATE: null safety version:

class ValueListenableBuilder2<A, B> extends StatelessWidget {
  const ValueListenableBuilder2({
    required this.first,
    required this.second,
    Key? key,
    required this.builder,
    this.child,
  }) : super(key: key);

  final ValueListenable<A> first;
  final ValueListenable<B> second;
  final Widget? child;
  final Widget Function(BuildContext context, A a, B b, Widget? child) builder;

  @override
  Widget build(BuildContext context) => ValueListenableBuilder<A>(
        valueListenable: first,
        builder: (_, a, __) {
          return ValueListenableBuilder<B>(
            valueListenable: second,
            builder: (context, b, __) {
              return builder(context, a, b, child);
            },
          );
        },
      );
}
like image 125
Rémi Rousselet Avatar answered Nov 26 '22 16:11

Rémi Rousselet


You cannot use ValueListenableBuilder with Listenable.merge because merge returns only a Listenable and no ValueListenable and ValueListenableBuilder expects a Value Changenotifier. You can use AnimatedBuilder instead which despite its name is just a ListenableBuilder.

class MyWidget extends StatelessWidget {
  final ValueNotifier<double> _height1 = ValueNotifier<double>(40);
  final ValueNotifier<double> _height2 = ValueNotifier<double>(120);
  final ValueNotifier<double> _height3 = ValueNotifier<double>(200);

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: Listenable.merge([_height1, _height2, _height3]),
      builder: (BuildContext context, _) {
        return GestureDetector(
          // i am using h1, h2 and h3 here ...
          child: Stack(
            children: <Widget>[
              Positioned(
                left: 0,
                right: 0,
                bottom: value,
                child: Column(
                  children: <Widget>[
                    Container(height: _height1.value),
                    Container(height: _height2.value),
                    Container(height: _height3.value),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}
like image 35
Thomas Avatar answered Nov 26 '22 15:11

Thomas