Is there GlobalKey counterpart of HookWidget? - flutter

I want to call the onPopPage in the following code in the parent widget.
In traditional way I may use GlobalKey<SomeState>().currentState.someMethod.
But what's the desirable way with HookWidget?
class SomeWidget extends HookWidget {
final innerNavigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
final isPasswordReset = useState(false);
final onPopPage = useMemoized(() => (_, __) {
if (isInInnerRoute.value &&
innerNavigatorKey.currentState?.onWillPop() == true) {
return true;
}
return false;
});
return Navigator(
pages: [
NoTransitionPage(child: _buildLogin(() {
isInInnerRoute.value = true;
})),
if (isInInnerRoute.value)
NoTransitionPage(
child: SomePage(navigatorKey: innerNavigatorKey)),
],
onPopPage: onPopPage,
);
}
}
class NoTransitionPage extends Page {
final Widget child;
NoTransitionPage({Key key, this.child}) : super(key: key);
Route createRoute(BuildContext context) {
return PageRouteBuilder(
maintainState: false,
settings: this,
pageBuilder: (_, __, ___) => child,
);
}
}

I asked to library author and got an answer. The gist is key usage should be avoided.

Related

The named parameter child is required but there's no corresponding argument

After upgrade my Flutter app is now producing this error at
return BaseWidget<BillsModel>(
The named parameter child is required but there's no corresponding
argument.
My BaseWidget has a child parameter but I don't know how to specify the child. This code previously worked but now doesn't. I realise there are many similar questions but they are sufficiently different that I can't figure this out. I have many similar errors in my project which all extend from BaseWidget
class Bills extends StatelessWidget {
const Bills({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
Tbl _table = Provider.of<Tbl>(context, listen: false);
return BaseWidget<BillsModel>(
model: BillsModel(api: Provider.of(context, listen: false)),
onModelReady: (model) => model.fetchBills(context, _table.id),
builder: (context, model, child) => model.busy
? Center(
child: CircularProgressIndicator(),
)
: Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: model.bills.length,
itemBuilder: (context, index) => BillListItem(
bill: model.bills[index],
),
)
)
);
}
}
Here is my BillsModel
class BillsModel extends BaseModel {
Api _api;
BillsModel({required Api api}) : _api = api;
List<Bill> bills = [];
Future fetchBills(BuildContext context, int tableId) async {
setBusy(true);
bills = await _api.getBills(context, tableId);
setBusy(false);
}
...
#override
void dispose() {
print('Bills has been disposed!!');
super.dispose();
}
}
Here is my BaseWidget:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {
final Widget Function(BuildContext context, T model, Widget? child) builder;
final T model;
final Widget child;
final Function(T) onModelReady;
BaseWidget({
Key? key,
required this.builder,
required this.model,
required this.child,
required this.onModelReady,
}) : super(key: key);
_BaseWidgetState<T> createState() => _BaseWidgetState<T>();
}
class _BaseWidgetState<T extends ChangeNotifier> extends State<BaseWidget<T>> {
late T model;
#override
void initState() {
model = widget.model;
widget.onModelReady(model);
super.initState();
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<T>(
create: (context) => model,
child: Consumer<T>(
builder: widget.builder,
child: widget.child,
),
);
}
}
You should pass child parameters with any widget as your BaseWidget according to BaseWidget class.
Add an example code line, check it please
class Bills extends StatelessWidget {
const Bills({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
Tbl _table = Provider.of<Tbl>(context, listen: false);
return BaseWidget<BillsModel>(
model: BillsModel(api: Provider.of(context, listen: false)),
onModelReady: (model) => model.fetchBills(context, _table.id),
child: const Sizedbox.shrink(), // Added This Line !
builder: (context, model, child) => model.busy
? Center(
child: CircularProgressIndicator(),
)
: Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: model.bills.length,
itemBuilder: (context, index) => BillListItem(
bill: model.bills[index],
),
)
)
);
}
}

Flutter Navigator 2.0 does not transition between root navigator pages

I'm using Navigator 2.0 API to handle navigation in my app, and I have problem with top-most RouterDelegate (the nested RouterDelegates underneath it work fine). Depending on authentication state I want to show Splash Screen, Login Screen, or Home Screen. The logic works fine and pages change depending on provided state, but there are no page transitions shown, neither on Android and iOS. That's weird, because in nested navigators this problem does not occurr. I've tried using several different Page classes, as well as changing page order in stack, but nothing seems to help - the transition animations seem to be completely ignored. Here's how my app structure looks like:
Main App definition:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) => FutureBuilder(
future: AppInit.initApp(),
builder: (context, snapshot) {
if (snapshot.hasError) {
log(
"App init failed!",
level: Level.SEVERE.value,
error: snapshot.error,
stackTrace: snapshot.stackTrace,
);
}
return _app();
},
);
Widget _app() => MaterialApp(
supportedLocales: LocalizationConfig.supportedLocales,
localizationsDelegates: LocalizationConfig.localizationDelegates,
theme: LightTheme().themeData,
navigatorObservers: [
FirebaseAnalyticsObserver(
analytics: AppDependencies.firebaseAnalytics,
)
],
home: MultiRepositoryProvider(
providers: AppRepositoryProviders.list,
child: MultiBlocProvider(
providers: AppBlocProviders.list,
child: Router(
routerDelegate: AppRouter(),
backButtonDispatcher: RootBackButtonDispatcher(),
),
),
),
);
}
Any my AppRouter page construction looks like this:
#override
Widget build(BuildContext context) => BlocBuilder<AppNavigationCubit, AppNavigationState>(
builder: (context, state) => Navigator(
key: internalNavigatorKey,
onPopPage: (route, dynamic result) => _popPage(context, route, result),
pages: getPageStack(context, state),
),
);
List<Page> getPageStack(BuildContext context, AppNavigationState state) {
List<Page> pageStack = [];
if (state is AppNavigationInitial) {
pageStack.add(SlideTransitionPage<void>(
key: AppRoutes.splash.valueKey,
name: AppRoutes.splash.name,
child: const SplashScreen(),
));
}
if (state is AppNavigationLoggedOut) {
pageStack.add(SlideTransitionPage<void>(
key: AppRoutes.auth.valueKey,
name: AppRoutes.auth.name,
child: const AuthScreen(),
));
}
if (state is AppNavigationLoggedIn) {
pageStack.add(SlideTransitionPage<void>(
key: AppRoutes.home.valueKey,
name: AppRoutes.home.name,
child: const HomeScreen(),
));
}
return pageStack;
}
And my implementation that extends Page (not sure if that has any impact, since default MaterialPage also doesn't do transitions):
class SlideTransitionPage<T> extends Page<T> {
final Widget child;
final Duration duration;
const SlideTransitionPage({
required LocalKey? key,
required String name,
required this.child,
this.duration = const Duration(milliseconds: 350),
}) : super(key: key, name: name);
#override
Route<T> createRoute(BuildContext context) =>
PageBasedSlideTransitionRoute<T>(this);
}
class PageBasedSlideTransitionRoute<T> extends PageRoute<T> {
final SlideTransitionPage<T> page;
PageBasedSlideTransitionRoute(this.page) : super(settings: page);
#override
Color? get barrierColor => null;
#override
String? get barrierLabel => null;
#override
Duration get transitionDuration => page.duration;
#override
bool get maintainState => true;
#override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) =>
SlideTransition(
position: animation.drive(Tween(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).chain(CurveTween(
curve: Curves.ease,
))),
child: (settings as SlideTransitionPage).child,
);
#override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
child;
}
Any suggestions what can be the reason behind it? All routers (f.e. one nested in HomeScreen and AuthScreen) perform transitions correctly. My guess is, the construction somehow causes the main router to recreate, instead of updating it's state and performing transitions, but I fail to find any solution or possible causes
CustomRouterDelegate class edit
final List<CustomPageRouteBuilder> _pages = [];
CustomPageRouteBuilder _createPage(
Widget child, PageConfiguration pageConfig) {
return CustomPageRouteBuilder(
key: Key(pageConfig.key) as LocalKey,
pageRouteSettings: pageConfig.pageRouteSettings,
child: child,
);
}
CustomPageRouteBuilder class create
class CustomPageRouteBuilder extends Page {const CustomPageRouteBuilder({
required this.pageRouteSettings,
required this.child,
required LocalKey key,}) : super(key: key);
final Widget child;
finalPageRouteSettings pageRouteSettings;
//Widget Function(BuildContext, Animation<double>, Animation<double>) transition;
#override
Route createRoute(BuildContext context) {
return PageRouteBuilder(
barrierColor: pageRouteSettings.barrierColor,
opaque: pageRouteSettings.backgroundOpaque,
barrierDismissible: pageRouteSettings.barrierDismissible,
fullscreenDialog: pageRouteSettings.fullscreenDialog,
settings: this,
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return FadeTransition(
opacity: animation,
child: child,
);
},
);
}
}

How to update streamProvider stream based on other providers value

I want to update StreamProvider stream, based on the value of the Provider. I arrived at this solution.
return Provider<SelectedDay>.value(
value: selectedDay,
child: ProviderToStream<SelectedDay>(
builder: (_, dayStream, Widget child) {
return StreamProvider<DailyHomeData>(
initialData: DailyHomeData.defaultValues(DateTime.now()),
create: (_) async* {
await for (final selectedDay in dayStream) {
yield await db
.dailyHomeDataStream(
dateTime: selectedDay.selectedDateTime)
.first;
}
},
child: MainPage(),
);
},
),
);
This is the providerToStream class, which i copied from here Trouble rebuilding a StreamProvider to update its current data
class ProviderToStream<T> extends StatefulWidget {
const ProviderToStream({Key key, this.builder, this.child}) : super(key: key);
final ValueWidgetBuilder<Stream<T>> builder;
final Widget child;
#override
_ProviderToStreamState<T> createState() => _ProviderToStreamState<T>();
}
class _ProviderToStreamState<T> extends State<ProviderToStream> {
final StreamController<T> controller = StreamController<T>();
#override
void dispose() {
controller.close();
super.dispose();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
controller.add(Provider.of<T>(context));
}
#override
Widget build(BuildContext context) {
return widget.builder(context, controller.stream, widget.child);
}
}
And this is the error i get when i try to use it
type '(BuildContext, Stream<SelectedDay>, Widget) => StreamProvider<DailyHomeData>' is not a subtype of type '(BuildContext, Stream<dynamic>, Widget) => Widget'
Note: The code doesnt even work when i place a dummy widget inside the ProviderToStream widget.
child: ProviderToStream<SelectedDay>(
builder: (_, ___, Widget child) {
return child;
},
),
I also tried to force somehow the type in the builder, so that it is not dynamic, with no luck
child: ProviderToStream<SelectedDay>(
builder: (_, Stream<SelectedDay> dayStream, Widget child) {
return child;
},
),

open database table in BLoC, dispose in MyApp - is this valid?

I am using the Hive database and would like to open a box (aka table) inside of a session BLoC. This appears to me more reasonable than to use a FutureBuilder in myApp() or alike.
Now, the hive team suggests to close a table upon app exit ("Before your application exits, you should call Hive.close() to close all open boxes."). Is it valid to do so or should opening and closing happen in the same widget for some (which) reason?
class App extends StatelessWidget {
const App({
required this.authenticationRepository,
required this.userRepository,
required this.sessionRepository,
}) : super(key: key);
final AuthenticationRepository authenticationRepository;
final UserRepository userRepository;
final SessionRepository sessionRepository;
#override
Widget build(BuildContext context) {
return RepositoryProvider.value(
value: authenticationRepository,
child: MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => AuthenticationBloc(
authenticationRepository: authenticationRepository,
userRepository: userRepository,
) ),
// *** IN THE BLOC BELOW I AM OPENING THE BOX AKA DATA TABLE WITH
// *** await Hive.openBox('problemMasterData');
BlocProvider(
create: (_) => SessionBloc()
),
],
child: AppView(),
));
}
}
class AppView extends StatefulWidget {
#override
_AppViewState createState() => _AppViewState();
}
class _AppViewState extends State<AppView> {
#override
Widget build(BuildContext context) {
return PlatformApp(
cupertino: (_, __) => CupertinoAppData(theme: HomeThemeCupertino.lightHomeTheme),
initialRoute: '/',
onGenerateRoute: AppRoutes.generateRoutes,
],
builder: (context, child) {
return BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
switch (state.status) {
case AuthenticationStatus.authenticated:
_navigator.pushNamedAndRemoveUntil('/home', (route) => false);
break;
case AuthenticationStatus.unauthenticated:
_navigator.pushNamedAndRemoveUntil('/login', (route) => false);
break;
default:
break;
}
},
child: child,
);
},
);
}
// *** AND HERE I WANT TO CLOSE THE BOX
#override
void dispose() {
Hive.box('problemMasterData');
super.dispose();
}
}

ValueListenableBuilder: listen to more than one value

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.
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);
},
);
},
);
}
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),
],
),
),
],
),
);
},
);
}
}
I too ran into same problem and had to use nested ValueListenableBuilder widgets. But then I wrapped whole logic inside a custom MultiValueListenableBuilder widget and published it as a Flutter package.
Here is link to my package: https://pub.dev/packages/multi_value_listenable_builder
Using this package you can provide a list of ValueListenable and their latest values will be available in same order in builder function. Here is an example.
MultiValueListenableBuider(
// Add all ValueListenables here.
valueListenables: [
listenable0,
listenable1,
listenable2,
.
.
listenableN
],
builder: (context, values, child) {
// Get the updated value of each listenable
// in values list.
return YourWidget(
values.elementAt(0),
values.elementAt(1),
values.elementAt(2),
.
.
values.elementAt(N),
child: child // Optional child.
);
},
child: YourOptionalChildWidget(),
)
Instead of messing with the builder simply wrap the listenables using a dummy Valuelistenable and the Listenable.merge
This ends up with minor overhead but far simpler code.
Please keep in mind that since it doesn't check the value of each listenable it might be slightly less inefficient if the listeners are being called with same value. Normally the builder would have ignored the notification because the value would be the same.
You could easily keep track of each individual value and compare before toggling the val, but i haven't seen any real performance issue so i didn't bother for now.
Using this code the 2nd argument of the builder will toggle between true/false to tell the ValueListenableBuilder that there has been a change. So simply ignore it.
You can always perform your functionality from your passed listenables
import 'package:flutter/foundation.dart';
class ValuesNotifier implements ValueListenable<bool> {
final List<ValueListenable> valueListenables;
late final Listenable listenable;
bool val = false;
ValuesNotifier(this.valueListenables) {
listenable = Listenable.merge(valueListenables);
listenable.addListener(onNotified);
}
#override
void addListener(VoidCallback listener) {
listenable.addListener(listener);
}
#override
void removeListener(VoidCallback listener) {
listenable.removeListener(listener);
}
#override
bool get value => val;
void onNotified() {
val = !val;
}
}
Example usage
ValueListenableBuilder(
valueListenable: ValuesNotifier([
valueNotifier1,
valueNotifier2,
]),
builder: (_, __, ___) => Visibility(
visible: valueNotifier1.value,
child: Text(valueNotifier2.value),
))
Multiple values can be listened, if those are encapsulated as a class, So you could listen to the data type of the class and access the members inside it, so any changes made to it are reflected to the listener,
A "Flutter-like" solution is to copy the existing ValueListenableBuilder code and add support for another listenable param. Do this for as many ValueListenableBuilders as needed (i.e. ValueListenableBuilder3, ValueListenableBuilder4...).
For more than 3 ValueListenableBuilders, it might be easier to use a ChangeNotifier instead.
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
typedef ValueWidgetBuilder2<T, T2> = Widget Function(
BuildContext context, T value1, T2 value2, Widget? child);
class ValueListenableBuilder2<T, T2> extends StatefulWidget {
const ValueListenableBuilder2({
Key? key,
required this.valueListenable1,
required this.valueListenable2,
required this.builder,
this.child,
}) : super(key: key);
final ValueListenable<T> valueListenable1;
final ValueListenable<T2> valueListenable2;
final ValueWidgetBuilder2<T, T2> builder;
final Widget? child;
#override
State<StatefulWidget> createState() => _ValueListenableBuilder2State<T, T2>();
}
class _ValueListenableBuilder2State<T, T2>
extends State<ValueListenableBuilder2<T, T2>> {
late T value1;
late T2 value2;
#override
void initState() {
super.initState();
value1 = widget.valueListenable1.value;
value2 = widget.valueListenable2.value;
widget.valueListenable1.addListener(_valueChanged1);
widget.valueListenable2.addListener(_valueChanged2);
}
#override
void didUpdateWidget(ValueListenableBuilder2<T, T2> oldWidget) {
if (oldWidget.valueListenable1 != widget.valueListenable1 &&
oldWidget.valueListenable2 != widget.valueListenable2) {
oldWidget.valueListenable1.removeListener(_valueChanged1);
value1 = widget.valueListenable1.value;
widget.valueListenable1.addListener(_valueChanged1);
oldWidget.valueListenable2.removeListener(_valueChanged2);
value2 = widget.valueListenable2.value;
widget.valueListenable2.addListener(_valueChanged2);
}
super.didUpdateWidget(oldWidget);
}
#override
void dispose() {
widget.valueListenable1.removeListener(_valueChanged1);
widget.valueListenable2.removeListener(_valueChanged2);
super.dispose();
}
void _valueChanged1() {
setState(() {
value1 = widget.valueListenable1.value;
});
}
void _valueChanged2() {
setState(() {
value2 = widget.valueListenable2.value;
});
}
#override
Widget build(BuildContext context) {
return widget.builder(context, value1, value2, widget.child);
}
}
Ive done recursive function to make behaviour you want, I am not sure its the best answer but you will probably like it.
class _MultiValueListenableBuilder extends StatelessWidget {
final List<ValueNotifier> valueListenables;
final Widget Function(BuildContext, List values) builder;
const _MultiValueListenableBuilder({Key? key,
required this.valueListenables,
required this.builder,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return _create(valueListenables, context);
}
Widget _create(List<ValueNotifier> notifiers, BuildContext context,) {
if(notifiers.isEmpty) {
return builder(context, valueListenables.map((e) => e.value).toList());
}
final copy = [...notifiers];
final notifier = copy.removeAt(0);
return ValueListenableBuilder(
valueListenable: notifier,
builder: (_, __, ___) => _create(copy, _),
child: _create(copy, context),
);
}
}
I also created test widget to check if everything works as it is supposed
class _MultiTest extends StatefulWidget {
const _MultiTest({Key? key}) : super(key: key);
#override
_MultiTestState createState() => _MultiTestState();
}
class _MultiTestState extends State<_MultiTest> {
final notifier1 = ValueNotifier('otherwise');
final notifier2 = ValueNotifier('look');
#override
Widget build(BuildContext context) {
return Column(
children: [
_MultiValueListenableBuilder(
valueListenables: [notifier1, notifier2],
builder: (_, values) => Text(values.fold<String>('', (pv, e) => pv + ' ' + e), style: const TextStyle(color: Colors.black, fontSize: 20),),
),
MyTextButton(
title: 'do',
onPressed: () {
notifier1.value = notifier1.value + 'otherwise';
notifier2.value = notifier2.value + 'look';
},
)
],
);
}
}
Feel free to ask any question I probably missed something up.
I was trying to achieve similar goals as in this answer i.e. without nesting BuildContext
I have left the class as abstract in my case, but, it can receive a callback function as argument as an alternative.
abstract class AllMultiValueListeners extends StatelessWidget {
/// List of [ValueListenable]s to listen to.
final List<ValueListenable> valueListenables;
const AllMultiValueListeners({Key key, #required this.valueListenables})
: super(key: key);
ValueListenableBuilder getListenableBuilder(int index) {
List<dynamic> values = [];
return ValueListenableBuilder(
valueListenable: valueListenables.elementAt(index),
builder: (BuildContext context, value, Widget child) {
for (int i = 0; i < valueListenables.length; i++) {
if (values.length <= i) {
values.add(valueListenables.elementAt(i).value);
} else {
values[i] = valueListenables.elementAt(i).value;
}
}
if (index == valueListenables.length - 1) {
return valuesChangedBuilder(context, values, child);
} else {
return getListenableBuilder(index + 1);
}
});
}
#override
Widget build(BuildContext context) {
return getListenableBuilder(0);
}
/// override this method to rebuild something when there the one or more values have changed
Widget valuesChangedBuilder(
BuildContext ctx,
List<dynamic> values,
Widget child,
);
}