Hello I am new to flutter and I have a problem to update the list after executing a deleting an item from the database. Some said to use setState, but I still don't know how to implement it in my code. Tried to call seState right after the delete action, but still nothing happened. Still have some trouble to understand which component to update in Flutter. Thank you.
class ProfileView extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _ProfileViewState();
}
}
class _ProfileViewState extends State<ProfileView> {
late Future<List<Patient>> _patients;
late PatientService patientService;
#override
void initState() {
super.initState();
patientService = PatientService();
_patients = patientService.getPatient();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Profile')),
body: Column(
children: <Widget>[
Flexible(
child: SizedBox(
child: FutureBuilder<List<Patient>>(
future: _patients,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.hasError) {
print(snapshot);
return Center(
child: Text("Error"),
);
} else if (snapshot.hasData){
List<Patient> patients = snapshot.data;
return _buildListView(patients);
} else {
return Center(
child: Container(),
);
}
},
),
),
)
],
),
);
}
Widget _buildListView(List<Patient> patients) {
return ListView.separated(
separatorBuilder: (BuildContext context, int i) => Divider(color: Colors.grey[400]),
itemCount: patients.length,
itemBuilder: (context, index) {
Patient patient = patients[index];
return Row(
children: <Widget>[
Flexible(
child: SizedBox(
child: ListTile(
title: Text(patient.firstName),
subtitle: Text(patient.phone),
trailing: IconButton(
icon: new Icon(const IconData(0xf4c4, fontFamily: 'Roboto'), size: 48.0, color: Colors.red),
onPressed: () {
patientService.deletePatient(patient.id.toString());
}),
),
)
)
],
);
}
);
}
}
You can achieve that by removing the initialization of the future from the initState and simply give the future of patientService.getPatient() to the FutureBuilder, like this:
future: patientService.getPatient()
And call setState after making sure the patient have been successfully deleted.
The explanation behind doing that is when you delete your patient from the DB, yes it is removed from there, but the UI didn't get an update about the list of patients after the delete. And the reason why calling setState in your case doesn't make a change is because you are assigning the future in initState which is called once and only once when the widget is initialized. So when you call setState the future won't be called again hence no new data is fetched.
So what I did is just remove the initialization of the future from initState and give it to the FutureBuilder, which will be rebuild whenever you call setState.
Even though this works, it isn't the ideal solution. Because you are rebuilding your whole widget every time a delete is made which is kinda of heavy considering the FutureBuilder, so what I suggest is checking out some state mangement solutions like Bloc or Mobx or even the Provider package (which isn't a state mangement according to its creator).
Hope that makes clear !
Happy coding !
call setState() inside the onPressed method.
onPressed: () {
patientService.deletePatient(patient.id.toString());
setState((){});
}),
If you are not saving a local copy of the list from which you are deleting an item then this works
Although if the delete method deletes on from wherever you are fetching the items then you will need to call
_patients = patientService.getPatient();
before calling setState()
I think your deletePatient is asynchronous method. And you are calling this method without await and after this function setState is called thus widget is getting updated before delete is completed.
If deletePatient is asynchronous then add await before calling it and add setState after it.
onPressed: () async {
await patientService.deletePatient(patient.id.toString());
setState((){});
})
Related
I'm having an issue with my widget running its FutureBuilder code multiple times with an already resolved Future. Unlike the other questions on SO about this, my build() method isn't being called multiple times.
My future is being called outside of build() in initState() - it's also wrapped in an AsyncMemoizer.
Relevant code:
class _HomeScreenState extends State<HomeScreen> {
late final Future myFuture;
final AsyncMemoizer _memoizer = AsyncMemoizer();
#override
void initState() {
super.initState();
/// provider package
final homeService = context.read<HomeService>();
myFuture = _memoizer.runOnce(homeService.getMyData);
}
#override
Widget build(BuildContext context) {
print("[HOME] BUILDING OUR HOME SCREEN");
return FutureBuilder(
future: myFuture,
builder: ((context, snapshot) {
print("[HOME] BUILDER CALLED WITH SNAPSHOT: $snapshot - connection state: ${snapshot.connectionState}");
When I run the code, and trigger the bug (a soft keyboard being shown manages to trigger it 50% of the time, but not all the time), my logs are:
I/flutter (29283): [HOME] BUILDING OUR HOME SCREEN
I/flutter (29283): [HOME] BUILDER CALLED WITH SNAPSHOT: AsyncSnapshot<dynamic>(ConnectionState.waiting, null, null, null) - connection state: ConnectionState.waiting
I/flutter (29283): [HOME] BUILDER CALLED WITH SNAPSHOT: AsyncSnapshot<dynamic>(ConnectionState.done, Instance of 'HomeData', null, null) - connection state: ConnectionState.done
...
/// bug triggered
...
I/flutter (29283): [HOME] BUILDER CALLED WITH SNAPSHOT: AsyncSnapshot<dynamic>(ConnectionState.done, Instance of 'HomeData', null, null) - connection state: ConnectionState.done
The initial call with ConnectionState.waiting is normal, then we get the first build with ConnectionState.done.
After the bug is triggered, I end up with another FutureBuilder resolve without the build() method being called.
Am I missing something here?
Edit with full example
This shows the bug in question - if you click in and out of the TextField, the FutureBuilder is called again.
It seems related to how the keyboard is hidden. If I use the FocusScopeNode method, it will rebuild, whereas if I use FocusManager, it won't, so I'm not sure if this is a bug or not.
import 'package:flutter/material.dart';
void main() async {
runApp(const TestApp());
}
class TestApp extends StatelessWidget {
const TestApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Testapp',
home: Scaffold(
body: TestAppHomeScreen(),
),
);
}
}
class TestAppHomeScreen extends StatefulWidget {
const TestAppHomeScreen({super.key});
#override
State<TestAppHomeScreen> createState() => _TestAppHomeScreenState();
}
class _TestAppHomeScreenState extends State<TestAppHomeScreen> {
late final Future myFuture;
#override
void initState() {
super.initState();
myFuture = Future.delayed(const Duration(milliseconds: 500), () => true);
print("[HOME] HOME SCREEN INIT STATE CALLED: $hashCode");
}
#override
Widget build(BuildContext context) {
print("[HOME] HOME SCREEN BUILD CALLED: $hashCode");
return FutureBuilder(
future: myFuture,
builder: (context, snapshot) {
print("[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE ${snapshot.connectionState}: $hashCode");
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
return GestureDetector(
onTapUp: (details) {
// hide the keyboard if it's showing
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
// FocusManager.instance.primaryFocus?.unfocus();
},
child: const Scaffold(
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0),
child: TextField(),
),
),
),
);
},
);
}
}
Thank you for the full, reproducible example.
print statements inside the builder method of your FutureBuilder are likely misleading you towards the incorrect "culprit".
The key "problem" arises from this line:
FocusScopeNode currentFocus = FocusScope.of(context);
In case you didn't know, Flutter's .of static methods expose InheritedWidget APIs of some kind. By convention, in a .of method you can usually find a call to dependOnInheritedWidgetOfExactType, which is meant to register the caller, i.e. the children Widget, as a dependency, i.e. a Widget that depends and react to changes of a InheritedWidget of that type.
Shortly, putting a .of inside a build method is meant to trigger rebuilds on your Widget: it's actively registered for listening to changes!
In your code, FutureBuilder's builder method is being registered as dependant of FocusScope.of and will be rebuilt if FocusScope changes. And yes, that does happen whenever we change focus. Indeed, you can even move up those few lines (outside GestureDetector, directly in the builder scope), and you'd obtain even more rebuilds (4: one for the first focus change, then others subsequent caused by the focus shift caused by such rebuilds).
One quick fix would be to directly look for the associated InheritedWidget these API expose, and then, instead of a simple .of, you'd call:
context.getElementForInheritedWidgetOfExactType<T>();
EDIT. I just looked for T in your use case. Unluckily, it turns out it is a _FocusMarker extends InheritedWidget class, which is a private class, and therefore it cannot be used outside of its file / package. I'm not sure why they designed the API like that, but I am not familiar with FocusNodes.
An alternative approach would be to simply isolate the children for your FutureBuilder, like so:
builder: (context, snapshot) {
print("[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE ${snapshot.connectionState}: $hashCode");
// ...
return Something();
}
Where Something is just the refactored StatelessWidget that contains the UI you've shown there. This would rebuild just Something and not the whole builder method, if that's your concern.
You want to deepen the "how" and the "whys" of InheritedWidgets, make sure you first watch this video to correctly understand what InheritedWidgets are. Then, if you wish to understand how to exploit didChangeDependencies, watch this other video and you'll be good to go.
You need to understand the role of BuildContext.
Example-1:
I'm using context passed to the Widget.build() method, and doing
FocusScope.of(context).unfocus();
will invoke both build() and builder() method because you're telling Flutter to take the focus away from any widget within the context and therefore the Widget.build() gets called, which further calls the Builder.builder() method.
// Example-1
#override
Widget build(BuildContext context) {
print("Widget.build()");
return Builder(builder: (context2) {
print('Builder.builder()');
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(), // <-- Using `context`
child: Scaffold(
body: Center(
child: TextField(),
),
),
);
});
}
Example-2:
I'm using context2 passed to the Builder.builder() method, and doing
FocusScope.of(context2).unfocus();
will invoke only the builder() method because you're telling Flutter to take the focus away from any widget within the context2 and thus the Builder.builder() gets called.
// Example-2
#override
Widget build(BuildContext context) {
print("Widget.build()");
return Builder(builder: (context2) {
print('Builder.builder()');
return GestureDetector(
onTap: () => FocusScope.of(context2).unfocus(), // <-- Using `context2`
child: Scaffold(
body: Center(
child: TextField(),
),
),
);
});
}
To answer your question, if you replace
builder: (context, snapshot) { ...}
with
builder: (_, snapshot) { }
then your build() will also get called.
The difference was happen because the context you use is parent
context (from future builder method).
Just wrap GestureDetector with Builder then the result is same as 2nd way.
return Builder(builder: (_context) {
return GestureDetector(
onTapUp: () {
// hide the keyboard if it's showing
final currentFocus = FocusScope.of(_context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
},
} ...
When attempting to dismiss keyboard we should use second way FocusManager.instance.primaryFocus?.unfocus(); as discussion in official issue here:
https://github.com/flutter/flutter/issues/20227#issuecomment-512860882
https://github.com/flutter/flutter/issues/19552
Please try this solution /// provider package up super.initState();
your code will be like this
#override
void initState() {
/// provider package
final homeService = context.read<HomeService>();
myFuture = _memoizer.runOnce(homeService.getMyData);
super.initState();
}
please after trying it tell me the result
pass descendant context to FocusScope.of will not trigger the build(), i think because focus manager remove child for this parent (FutureBuilder), and reassign it based on current context, in this case build() context, so futurebuilder need to rebuild.
Widget build(BuildContext context) {
print("[HOME] HOME SCREEN BUILD CALLED: $hashCode");
return FutureBuilder(
future: myFuture,
builder: (context, snapshot) {
print("[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE ${snapshot.connectionState}: $hashCode");
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
//make StatefulBuilder as parent will prevent it
return StatefulBuilder(
builder: (context, setState) {
return GestureDetector(
onTapUp: (details) {
// hide the keyboard if it's showing
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
// FocusManager.instance.primaryFocus?.unfocus();
},
child: const Scaffold(
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0),
child: TextField(),
),
),
),
);
}
);
},
);
}
to prove it , i try to warp it parent (FutureBuilder) with another builder :
return LayoutBuilder(
builder: (context, box) {
print('Rebuild');
return FutureBuilder(
future: myFuture,
builder: (context, snapshot) {
print("[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE ${snapshot.connectionState}: $hashCode");
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
return GestureDetector(
onTapUp: (details) {
// hide the keyboard if it's showing
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
// FocusManager.instance.primaryFocus?.unfocus();
},
child: const Scaffold(
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0),
child: TextField(),
),
),
),
);
},
);
}
);
build() method not reinvoked because focusScope manager only rebuild context from FutureBuilder (Parent)
I am trying to initialize a variable in the initstate. Before initializing null value is used in my scaffold. How can I wait for a variable then later load the build?I am using shared_preference plugin.
This is my initstate:
void initState() {
super.initState();
_createInterstitialAd();
Future.delayed(Duration.zero, () async {
prefs = await SharedPreferences.getInstance();
future = Provider.of<Titles>(context, listen: false).fetchAndSetPlaces();
identifier = await getimages();
});
Here I am checking if it is null and it is always null:
#override
Widget build(BuildContext context) {
print(prefs);
Why you want to initialize variable in init state .
Use Future Builder inside your context and fetch variable data first and then your build will execute.
class FutureDemoPage extends StatelessWidget {
Future<String> getData() {
return Future.delayed(Duration(seconds: 2), () {
return "I am data";
// throw Exception("Custom Error");
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text('Future Demo Page'),
),
body: FutureBuilder(
builder: (ctx, snapshot) {
// Checking if future is resolved or not
if (snapshot.connectionState == ConnectionState.done) {
// If we got an error
if (snapshot.hasError) {
return Center(
child: Text(
'${snapshot.error} occured',
style: TextStyle(fontSize: 18),
),
);
// if we got our data
} else if (snapshot.hasData) {
// Extracting data from snapshot object
final data = snapshot.data as String;
return Center(
child: Text(
'$data',
style: TextStyle(fontSize: 18),
),
);
}
}
// Displaying LoadingSpinner to indicate waiting state
return Center(
child: CircularProgressIndicator(),
);
},
// Future that needs to be resolved
// inorder to display something on the Canvas
future: getData(),
),
),
);
}
}
If you want to initialize a variable later then we have late keyword for that purpose. You can use that like below when declaring a variable :
late String prefs;
First you have to understand the Life cycle of flutter.
I make it simple for you..
createState()
initState()
didChangeDependencies()
build()
didUpdateWidget()
setState()
dispose()
So Every override function have fixed functionality. For example initState can use only for normal initialisation. It can not hold/await the flow for several time. So that why any async method is not applicable inside the initState.
For Solution you have to use FutureBuilder for awaiting data or before navigating to the next page initialise the preferences then proceeded.
i assume you have 3 different condition :
first wait data for prefences,
get future data from that prefences,
and render result.
try this: bloc pattern
its basically a Stream to different State and Logic part of app ui
I would like to know when to use a FutureBuilder and how is it usefull, given the fact that a widget can be built multiple times during its life time and not only when we setState or update it, so instead of using below code:
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool done = false;
#override
void initState() {
wait();
super.initState();
}
Future<void> wait() async {
await Future.delayed(Duration(seconds: 2));
setState(() {
done = true;
});
}
#override
Widget build(BuildContext context) {
print('is built');
return done ? Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Increment',
child: Icon(Icons.add),
),
) : Scaffold(body: CircularProgressIndicator(),);
}
}
In which cases would a FutureBuilder work for me instead of the above set up, given the fact that I would also want to optimize my app for less backend reads (in FutureBuilder I would read more than once). I am looking for a case where FutureBuilder would be more usefull and correct than the above setup.
FutureBuilder is used to handle "asynchronous" calls that return the data you want to display.
For example, let's say we have to get a List<String> from the server.
The API call :
Future<List<String>> getStringList() async {
try {
return Future.delayed(Duration(seconds: 1)).then(
(value) => ['data1', 'data2', 'data3', 'data4'],
);
} catch (e) {
throw Exception(e);
}
}
How can we handle the above API call status (load, data, error...etc) using FutureBuilder:
FutureBuilder<List<String>?>(
future: getStringList(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
case ConnectionState.done:
if (snapshot.hasError)
return Text(snapshot.error.toString());
else
return ListView(
children: snapshot.data!.map((e) => Text(e)).toList(),
);
default:
return Text('Unhandle State');
}
},
),
As we can see, we don't have to create a state class with variable isLoading bool and String for error...etc. FutureBuilder saves us time.
BUT
Since FutureBuilder is a widget and lives in the widget tree, rebuilding can make FutureBuilder run again.
So I would say you can use FutureBuilder when you know there won't be a rebuild, however there are many ways (in the internet) to prevent FutureBuilder from being called again when the rebuild happens but it didn't work for me and leads to unexpected behavior.
Honestly I prefer handling the state in a different class with any state management solution than using FutureBuilder because it would be safer (rebuild wont effect it), more usable and easier to read (spreating business logic from UI).
FutureBuilder
Widget that builds itself based on the latest snapshot of interaction with a Future.
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder.
If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.A general guideline is to assume that every build method could get called every frame, and to treat omitted calls as an optimization.
Documentation is very great way to get started and understand what widget does what in what condition...
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Actually, you will never need to use FutureBuilder Widget if you don't want to. Your logic in your code do exactly what FutureBuilder Widget does if you optimise FutureBuilder Widget correctly.
This code is exactly same with yours:
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool done = false;
late Future myFuture;
#override
void initState() {
myFuture = wait();
super.initState();
}
Future<bool> wait() async {
await Future.delayed(const Duration(seconds: 2));
return true;
}
#override
Widget build(BuildContext context) {
print('is built');
return FutureBuilder(
future: myFuture,
builder: (BuildContext context, snapshot) {
if(snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(body: CircularProgressIndicator(),);
} else {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
);
}
}
FutureBuilder is just a StatefulWidget whose state variable is _snapshot
Initial state is _snapshot = AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData);
FutureBuilder is generally used to remove boilerplate code.
consider you want to fetch some data from the backend on page launch and show a loader until data comes.
It is subscribing to future which we send via the constructor and update the state based on that.
Tasks for FutureBuilder:
Give the async task in future of Future Builder
Based on connectionState, show message (loading, active(streams), done)
Based on data(snapshot.hasError), show view
Benefits of FutureBuilder
Does not use the two state variables and setState
Reactive programming (FutureBuilder will take care of updating the view on data arrival)
Example:
FutureBuilder<String>(
future: _fetchNetworkCall, // async work
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return Text('Loading....');
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return Text('Result: ${snapshot.data}');
}
},
)
hence to conclude Future builder is wrapper/boilerplate of what we do typically, thus there should not be any performance impact.
FutureBuilder is a widget by Flutter which lets you easily determine the current state of the Future and choose what to show during that state.
you can refer this: https://walkingtree.tech/futurebuilder-flutter-widget/
One of the use cases is FutureBuilder update on a particular section inside the widget tree, not the full widget tree like you implement in the sample.
Future<void> wait() async {
return Future.delayed(Duration(seconds: 2));
}
Here setState() => update widget tree.
So same code in FutureBuilder you can change the UI value in a particular position in the widget tree.
What would be a best practice for (provider based) state management of modal widgets in flutter, where when user makes an edit changes do not propagate to parent page until user confirms/closes modal widget. Optionally, user has a choice to discard the changes.
In a nutshell:
modal widget with OK and cancel actions, or
modal widget where changes are applied when modal is closed
Currently, my solution looks like this
Create a copy of the current state
Call flutter's show___() function and wrap widgets with a provider (using .value constructor) to expose copy of the state
If needed, update original state when modal widget is closed
Example of case #2:
Future<void> showEditDialog() async {
// Create a copy of the current state
final orgState = context.read<MeState>();
final tmpState = MeState.from(orgState);
// show modal widget with new provider
await showDialog<void>(
context: context,
builder: (_) => ChangeNotifierProvider<MeState>.value(
value: tmpState,
builder: (context, _) => _buildEditDialogWidgets(context)),
);
// update original state (no discard option to keep it simple)
orgState.update(tmpState);
}
But there are issues with this, like:
Where should I dispose tmpState?
ProxyProvider doesn't have .value constructor.
If temporary state is created in Provider's create: instead, how can I safely access that temporary state when modal is closed?
UPDATE: In my current app I have a MultiProvider widget at the top of widget tree, that creates and provides multiple filter state objects. Eg. FooFiltersState, BarFiltersState and BazFiltersState. They are separate classes because each these three extends either ToggleableCollection<T> extends ChangeNotifier or ToggleableCollectionPickerState<T> extends ToggleableCollection<T> class. An abstract base classes with common properties and functions (like bool areAllSelected(), toggleAllSelection() etc.).
There is also FiltersState extends ChangeNotifier class that contains among other things activeFiltersCount, a value depended on Foo, Bar and Baz filters state. That's why I use
ChangeNotifierProxyProvider3<
FooFiltersState,
BarFilterState,
BazFilterState,
FiltersState>
to provide FiltersState instance.
User can edit these filters by opening modal bottom sheet, but changes to filters must not be reflected in the app until bottom sheet is closed by taping on the scrim. Changes are visible on the bottom sheet while editing.
Foo filters are displayed as chips on the bottom sheet. Bar and baz filters are edited inside a nested dialog windows (opened from the bottom sheet). While Bar or Baz filter collection is edited, changes must be reflected only inside the nested dialog window. When nested dialog is confirmed changes are now reflected on bottom sheet. If nested dialog is canceled changes are not transferred to the bottom sheet. Same as before, these changes are not visible inside the app until the bottom sheet is closed.
To avoid unnecessary widget rebuilds, Selector widgets are used to display filter values.
From discussion with yellowgray, I think that I should move all non-dependent values out of proxy provider. So that, temp proxy provider can create new temp state object that is completely independent of original state object. While for other objects temp states are build from original states and passed to value constructors like in the above example.
1. Where should I dispose tmpState?
I think for your case, you don't need to worry about it. tmpState is like a temporary variabl inside function showEditDialog()
2. ProxyProvider doesn't have .value constructor.
It doesn't need to because it already is. ProxyProvider<T, R>: T is a provider that need to listen to. In your case it is the orgState. But I think the orgState won't change the value outside of this function, so I don't know why you need it.
3. If temporary state is created in Provider's create: instead, how can I safely access that temporary state when modal is closed?
you can still access the orgState inside _buildEditDialogWidgets and update it by context.read(). But I think you shouldn't use same type twice in the same provider tree (MeState)
Actually when I first see your code, I will think why you need to wrap tmpState as another provider (your _buildEditDialogWidgets contains more complicated sub-tree or something else that need to use the value in many different widget?). Here is the simpler version I can think of.
Future<void> showEditDialog() async {
// Create a copy of the current state
final orgState = context.read<MeState>();
// show modal widget with new provider
await showDialog<void>(
context: context,
builder: (_) => _buildEditDialogWidgets(context,MeState.from(orgState)),
);
}
...
Widget _buildEditDialogWidgets(context, model){
...
onSubmit(){
context.read<MeState>().update(updatedModel)
}
...
}
The simplest way is you can just provide a result when you pop your dialog and use that result when updating your provider.
import 'dart:collection';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class Item {
Item(this.name);
String name;
Item clone() => Item(name);
}
class MyState extends ChangeNotifier {
List<Item> _items = <Item>[];
UnmodifiableListView<Item> get items => UnmodifiableListView<Item>(_items);
void add(Item item) {
if (item == null) {
return;
}
_items.add(item);
notifyListeners();
}
void update(Item oldItem, Item newItem) {
final int indexOfItem = _items.indexOf(oldItem);
if (newItem == null || indexOfItem < 0) {
return;
}
_items[indexOfItem] = newItem;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(_) {
return ChangeNotifierProvider<MyState>(
create: (_) => MyState(),
builder: (_, __) => MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Builder(
builder: (BuildContext context) => Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
FlatButton(
onPressed: () => _addItem(context),
child: const Text('Add'),
),
Expanded(
child: Consumer<MyState>(
builder: (_, MyState state, __) {
final List<Item> items = state.items;
return ListView.builder(
itemCount: items.length,
itemBuilder: (_, int index) => GestureDetector(
onTap: () => _updateItem(context, items[index]),
child: ListTile(
title: Text(items[index].name),
),
),
);
},
),
),
],
),
),
),
),
),
);
}
Future<void> _addItem(BuildContext context) async {
final Item item = await showDialog<Item>(
context: context,
builder: (BuildContext context2) => AlertDialog(
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.pop(context2),
child: const Text('Cancel'),
),
FlatButton(
onPressed: () => Navigator.pop(
context2,
Item('New Item ${Random().nextInt(100)}'),
),
child: const Text('ADD'),
),
],
),
);
Provider.of<MyState>(context, listen: false).add(item);
}
Future<void> _updateItem(BuildContext context, Item item) async {
final Item updatedItem = item.clone();
final Item tempItem = await showModalBottomSheet<Item>(
context: context,
builder: (_) {
final TextEditingController controller = TextEditingController();
controller.text = updatedItem.name;
return Container(
height: 300,
child: Column(
children: <Widget>[
Text('Original: ${item.name}'),
TextField(
controller: controller,
enabled: false,
),
TextButton(
onPressed: () {
updatedItem.name = 'New Item ${Random().nextInt(100)}';
controller.text = updatedItem.name;
},
child: const Text('Change name'),
),
TextButton(
onPressed: () => Navigator.pop(context, updatedItem),
child: const Text('UPDATE'),
),
TextButton(
onPressed: () => Navigator.pop(context, Item(null)),
child: const Text('Cancel'),
),
],
),
);
},
);
if (tempItem != null && tempItem != updatedItem) {
// Do not update if "Cancel" is pressed.
return;
}
// Update if "UPDATE" is pressed or dimissed.
Provider.of<MyState>(context, listen: false).update(item, updatedItem);
}
}
as my understand, one of Provider benefits is to avoid rebuild the widget tree, by calling build function, but when i try it practically with this simple example:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:providerexamplelast/counterWidget.dart';
void main() => runApp(ChangeNotifierProvider<Provider1>(
create: (_) => Provider1(),
child: MaterialApp(
home: Counter(),
),
));
int n =0;
class Counter extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("${n++}");
var counter = Provider.of<Provider1>(context);
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: ()=> counter.counter(),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("1"),
SizedBox(height: 5,),
countText(),
SizedBox(height: 5,),
Text("3"),
SizedBox(height: 5,),
Text("4"),
],
),
),
);
}
}
Widget countText(){
return Builder(
builder: (context){
var count = Provider.of<Provider1>(context);
return Text("${count.c}");
},
);
}
by using this part :
print("${n++}");
i noticed that (build) function is recall whenever i press the button and call (counter) function from provider?
so the question here it is just (Stateless) widget, how it rebuild again? and then why i need to use Provider if it is not solve this problem?
Edit: I heard about this way:
var counter = Provider.of<Provider1>(context, listen: false);
so is it solve this problem? and how?
var counter = Provider.of<Provider1>(context, listen: false);
The Provider is one of state management mechanism which flutter have, under the hood, Provider keeps track of the changes which are done inside the widget. It doesn't matter to Provider whether it's a Stateless widget or Stateful widget, It's gonna rebuild widget if anything gets change.
listen: false tells the Provider not to rebuild widget even if data gets modified.