class EventTimeModel with ChangeNotifier {
update() {
notifyListeners();
}
}
class SingleEvent extends StatelessWidget {
...
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context2) => event.timeModel,
child: buildEventColumn());
}
}
class EventList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: events.length,
itemBuilder: (context, index) {
Event event = events[index];
return SingleEvent(event: event)
}
}
EventList uses a ListView to display mutliple SingleEvents. SingleEvent uses a ChangeNotifierProvider to provide EventTimeModel. When scrolling up and down I got the message
Unhandled Exception: A EventTimeModel was used after being disposed.
E/flutter (10215): Once you have called dispose() on a EventTimeModel, it can no longer be used.
So I think an event was deleted because it has been outside the screen. When it should be displayed again the error was thrown. How can I fix this?
Since timeModel exists the create method can't be used here.
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: event.timeModel,
child: buildEventColumn());
}
Related
I need to access two different view model in one page in Flutter.
How to use nested Consumers in Flutter, Will they effect each other?
How can I use it in this code for example?
class CounterDisplay extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<CounterModel>(
builder: (context, counterModel, child) {
return Text('${counterModel.count}');
},
);
}
}
you can use Consumer2<> to access two different providers like this :
class CounterDisplay extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer2<CounterModel, SecondModel>(
builder: (context, counterModel, secondModel, child) {
return Text('${counterModel.count}');
},
);
}
}
With this, your Text() widget will be rebuilt each time one the providers value is changed with notifyListener().
If your Text() widget doesn't need to be rebuilt with one of your providers, you can simply use Provider.of<MySecondProvider>(context, listen: false);.
Here for example:
class CounterDisplay extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<CounterModel>(
builder: (context, counterModel, child) {
MyThemeProvider myThemeProvider = Provider.of<MyThemeProvider>(context, listen: false);
return Text('${counterModel.count}', color: myThemeProvider.isDark ? Colors.white : Colors.dark);
},
);
}
}
I hope this helps!
You can absolutely nest consumers, but you need to understand if you really want them to nest.
Case 1: Nesting consumers:
class CounterDisplay extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<CounterModel>(
builder: (context, counterModel, child) {
return Consumer<SomeAnotherModel>(
builder: (anothercontext,anotherCounterModel, anotherChild) {
return Text('${counterModel.count}');
});
},
);
}
}
Please note that if consumer for CounterModel is rebuilt, everything will be rebuilt. If consumer for SomeAnotherModel is rebuild, only the part inside its builder would be rebuilt.
Instead of using consumers this way, I'd recommend them to be used as in case 2 below.
Case 2: Use consumers on the required components:
class CounterDisplay extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children:[
Consumer<CounterModel>(
builder: (context, counterModel, child) { return...}),
Consumer<CounterModel>(
builder: (anotherContext, anotherModel, anotherChild) { return...}),
]
);
}
}
I'm new to flutter and trying to use two Stateful Widgets first one calling the second in build() method and I just want to update the child widget variable that is passed from parent in the constructor.
Here is the code I'm trying with.
Parent Widget
class Parent extends StatefulWidget {
Parent({Key? key}) : super(key: key);
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
List appointments = [];
#override
void initState() {
super.initState();
fetchAppointments();
}
#override
Widget build(BuildContext context) {
return Builder(builder: (BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
_pullRefresh();
},
child: ListView(
children: [
AppointmentsWidget(appointments: appointments) // <----- I passed the variable in constructor and this one is updating in setState and I want it to update in the child widget too
],
),
),
);
});
}
_pullRefresh() {
fetchAppointments();
}
fetchAppointments() {
setState(() {
// Stuff to do
appointments = ......
......
......
});
}
}
Child Widget
class AppointmentsWidget extends StatefulWidget {
var appointments;
AppointmentsWidget({this.appointments});
#override
_AppointmentsWidgetState createState() =>
_AppointmentsWidgetState(appointments: appointments); // <--- Constructor 1
}
class _AppointmentsWidgetState extends State<AppointmentsWidget> {
var appointments;
_AppointmentsWidgetState({this.appointments}); // <--- Constructor 2
#override
Widget build(BuildContext context) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: appointments.length,
itemBuilder: (BuildContext context, int index) {
return Text(appointments[index].toString()); // <--- This is where I use it
},
);
}
}
I know the constructor calls once but I couldn't find a way to either recall the constructor OR somehow pass the updated value to the constructor.
Your help is really appreciated.
You should make your child widget stateless, as its state (the appointments) are handled by the parent. What happens currently is that your child widget is constructed, where the empty list is used as its widget.appointments value. Then when the appointments have been fetched, the widget.appointments rebuilds, but since the state of the child is maintained, this value is not passed on (initState of the child is not re-run).
class AppointmentsWidget extends StatelessWidget {
final List appointments;
const AppointmentsWidget({Key? key, required this.appointments}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: appointments.length,
itemBuilder: (BuildContext context, int index) {
return Text(appointments[index].toString()); // <--- This is where I use it
},
);
}
}
Also, take a look at the flutter docs on handling state:
https://docs.flutter.dev/development/data-and-backend/state-mgmt/intro
These also state that it's good to keep your state high up (in the parent in this case), and make child widgets use the state of the parents to render themselves appropriately.
Rectification
As you mention in the comments, you need the child widget to be stateful (for maintaining state on some other data). In that case, you can simply get rid of the appointments state variable and use widget.appointments instead, which will update when the parent rebuilds with a new value.
class AppointmentsWidget extends StatefulWidget {
var appointments;
AppointmentsWidget({this.appointments});
#override
_AppointmentsWidgetState createState() =>
_AppointmentsWidgetState(); // <--- Constructor 1
}
class _AppointmentsWidgetState extends State<AppointmentsWidget> {
_AppointmentsWidgetState(); // <--- Constructor 2
#override
Widget build(BuildContext context) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: widget.appointments.length,
itemBuilder: (BuildContext context, int index) {
return Text(widget.appointments[index].toString()); // <--- This is where I use it
},
);
}
}
You need some listenable to update the widget because the context of parent and child are diferents. In that case the correct way to do it (without a state managment package) is with a InheritedWidget.
Inherited class:
class ExampleInherited extends InheritedWidget {
final Widget child;
final ExampleBloc exampleBloc; // <-- change notifier that can do changes and notify their children
const ExampleInherited({Key? key, required this.child, required this.exampleBloc}) : super(key: key, child: child);
static ExampleInherited? of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<ExampleInherited>();
#override
bool updateShouldNotify(covariant InheritedWidget oldWidget) => true;
}
ChangeNotifier class:
class ExampleBloc extends ChangeNotifier {
static final ExampleBloc _exampleBloc = ExampleBloc._internal();
factory ExampleBloc() {
return _exampleBloc;
}
ExampleBloc._internal();
exampleMethod(){
// here you can do whatever you need (update vars)
notifyListeners(); // <-- this notifies the children that they need to be rebuilded
}
}
Then set this in your parent view:
ExampleInherited(
exampleBloc: ExampleBloc(),
child: // content
}
And then in your child view:
#override
Widget build(BuildContext context) {
ExampleBloc exampleBloc = ExampleInherited.of(context)!.exampleBloc;
return AnimatedBuilder(
animation: exampleBloc,
builder: (context, child) {
return //your content
// this will rebuild every time you call notifyListeners() in your bloc
})
}
I am making a list of stateless widget as shown below and passing the id as the parameter to the widgets.
Code for cartPage:-
class Cart extends StatefulWidget {
#override
_CartState createState() => _CartState();
}
class _CartState extends State<Cart> {
bool loading=true;
List<CartTile> cartTiles=[];
#override
void initState() {
super.initState();
if(currentUser!=null)
getData();
}
getData()async
{
QuerySnapshot snapshot=await cartReference.doc(currentUser.id).collection('cartItems').limit(5).get();
snapshot.docs.forEach((doc) {
cartTiles.add(CartTile(id: doc.data()['id'],index: cartTiles.length,));
});
setState(() {
loading=false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: loading?Center(child:CircularProgressIndicator():SingleChildScrollView(
child: Column(
children: cartTiles,
),
),
);
}
}
Code for CartTile:-
class CartTile extends StatelessWidget {
final String id;
CartTile({this.id,});
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: productReference.doc(id).snapshots(),
builder: (context,snapshot)
{
//here am using the snapshot to build the cartTile.
},
);
}
}
So, my question is whenever I will call setState in my homepage then will the stateless widget be rebuilt and increase my document reads. Because i read somewhere that when we pass the same arguments or parameters to a stateless widget then due to its cache mechanism it doesn't re build. If it will increase my reads then is there any other way to solve this problem?
I'm trying to use the package ShowoCaseView in a flutter application, here are the steps I've made :
GlobalKey _oneShowcaseKey = GlobalKey();
startShowCase() {
WidgetsBinding.instance.addPostFrameCallback((_) async {
ShowCaseWidget.of(context).startShowCase([_oneShowcaseKey]);
});
}
#override
void initState() {
startShowCase();
super.initState();
}
#override
Widget build(BuildContext context) {
super.build(context);
return ShowCaseWidget(
builder: Builder(
builder: (context) => Scaffold(
child: Showcase(
key: _oneShowcaseKey,
title: 'Menu',
description: 'Click here to see menu options',
child: Column())
)
this is the way I've implemented the package in my application, but I get this error :
[ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Exception: Please provide ShowCaseView context
I also got issue.
I changed to write "ShowCaseWIdget" into parent widget as follows, this issue is solved.
class SolvedStatelessWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ShowCaseWidget(
builder: Builder(builder: (context) => SolvedStatefulWidget())));
}
}
class SolvedStatefulWidget extends StatefulWidget {
#override
_SolvedStatefulWidgetState createState() => _SolvedStatefulWidgetState();
}
class _SolvedStatefulWidgetState extends State<SolvedStatefulWidget> {
GlobalKey _oneShowcaseKey = GlobalKey();
startShowCase() {
WidgetsBinding.instance.addPostFrameCallback((_) async {
ShowCaseWidget.of(context).startShowCase([_oneShowcaseKey]);
});
}
#override
void initState() {
startShowCase();
super.initState();
}
#override
Widget build(BuildContext context) {
super.build(context);
return yourWidget()
)
Don't wrap your Scaffold inside ShowCaseWidget. Instead, do this wrapping to main navigation point.
For example:
Using onGenerateRoute:
return setTransition(ShowCaseWidget(
builder: Builder(
builder: (_) => DashboardView(map),
),
));
Hope this fixes your issue, ask you if you still find any issue.
If remove the MediaQuery.of(context).size code, it will not rebuild.
this is my code.
class ExamplePage extends StatelessWidget {
Future<Size> init(BuildContext context) async {
print("init");
return MediaQuery.of(context).size;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: init(context),
builder: (BuildContext context, AsyncSnapshot<Size> snapshot) {
return Center(child: TextField());
}));
}
}
future: init(context),
This will probably cause init(context) to be called each time build() is called for this widget. Instead, something like this should be done in the initState() for this widget, so that it is done only once at widget creation.