Flutter - How to make a variable accessible to a child widget - flutter

i've created a StatelessWidget named FirestoreStreamBuilder that create a Stream and return a child.
class FirestoreStreamBuilder extends StatelessWidget {
FirestoreStreamBuilder({#required this.collectionReference, #required this.child, #required this.noDataChild});
final String collectionReference;
final Widget child;
final Widget noDataChild;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection(collectionReference).snapshots() ,
builder: (context, snapshot) {
if (!snapshot.hasData) return noDataChild;
return child;
},
);
}
}
I need to make snapshot accessible from child. How can i do that?
Example:
FirestoreStreamBuilder(
collectionReference: 'collection',
child: Text('I want to acces to snapshot here $snapshot'), //How can I access snapshot from here?
noDataChild: Container(),
)

Create custom callback
typedef OnDataReceived = void Function(DocumentSnapShot);//or your return type
class FirestoreStreamBuilder extends StatelessWidget {
FirestoreStreamBuilder({#required this.collectionReference, #required this.child,
#required this.noDataChild, #required this.onDataReceived});
final String collectionReference;
final Widget child;
final Widget noDataChild;
final OnDataReceived onDataReceived;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection(collectionReference).snapshots() ,
builder: (context, snapshot) {
if (!snapshot.hasData) {
onDataReceived(snapshot);
return noDataChild;
}
return child;
},
);
}
}
Access snapshot in widget
FirestoreStreamBuilder(
collectionReference: 'collection',
child: Text('Hurray you got snapshot !'),
noDataChild: Container(),
onDataReceived:(snapshot){
//TODO your task with snapshot
}
)

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

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;
},
),

How to get a List from another class

I have a characterList class that has a final List <Character> character field;
How can I access the character from the SWMain class?
SWMain class:
class _SWMainState extends State<SWMain> {
Icon customIcon = Icon(Icons.search);
static Text titleText = Text("Star Wars API");
Widget customSearchBar = titleText;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar()
body: FutureBuilder<List<Character>>(
future: fetchCharacters(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? CharacterList(character: snapshot.data)
: Center(child: CircularProgressIndicator());
},
),
),
);
}
}
characterList class:
class CharacterList extends StatelessWidget {
final List<Character> character;
CharacterList({Key key, this.character}) : super(key: key);
...
}
CharacterList({Key key, this.character}) : super(key: key);
this is the constructor.
You are using a stateless widget so you can directly access via variable name which is character in your case.
if you are using a stateful widget so you have to access it via widget.character
Thanks.

Flutter - How to make a FutureBuilder that resolves a Future ImageProvider

I'd like to eliminate the boilerplate code used to wait and then replace an ImageProvider placeholder with a final image.
The following works for an Image Widget.
class FutureImage extends StatelessWidget {
final Future<Image> futureImage;
final Image placeholder;
const FutureImage({Key key, this.futureImage, this.placeholder})
: super(key: key);
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: futureImage,
builder: (BuildContext context, AsyncSnapshot<Image> image) {
if (image.hasData) {
return image.data;
} else {
return placeholder;
}
},
);
}
}
However I need an ImageProvider (because it can be used in places where an Image cannot)
The following doesn't work because ImageProvider isn't a Widget:
class FutureImageProvider extends StatelessWidget {
final Future<ImageProvider> futureImageProvider;
final ImageProvider placeholder;
const FutureImageProvider(
{Key key, this.futureImageProvider, this.placeholder})
: super(key: key);
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: futureImageProvider,
builder: (BuildContext context, AsyncSnapshot<ImageProvider> image) {
if (image.hasData) {
return image.data;
} else {
return placeholder;
}
},
);
}
}

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,
);
}