Flutter: Execute next steps after the on before finished - flutter

I used the BLOC architecture inside my app. One method inside my bloc resolves a picked address-ID from a listView and return the city name. After this I want my Text-Editing-Controller to have this picked value and show it on the texteditingfield:
The BlocA uses a simple ID and look it up using Google-Places API. BlocA then return a new state with the resolved address as a state property. Running the code below did not show the correct city after tapping on a listView-item.
Await do not work unfortunately.
// ListView.builder code
onTap: () {
context
.read<BlocA>()
.add(PickAddress(id: state.searchResults[index].placeId));
FocusScope.of(context).unfocus(); //hide Keyboard
_textController.value = TextEditingValue(text: state.address.city);

the code its not clear enough
remember to use "await" you have to set Async on onTap like this
onTap: () async { ....
if you did that and still not working send the whole code

Do you know what your Bloc actually returns? Use the debugger and step through your code to see what actually happens in your code.
When it comes to using Bloc's to build your state you usually don't access the state with by calling .state.
The line _textController.value = TextEditingValue(text: state.address.city); is not the way you access your state. When the Bloc sends the new state, the widget will rebuild and render based on the new state.

Related

Navigating to another screen gets slower and slower each time i repeat click, go back, click, go back

I have a Navigator.push and MaterialPageRoute() to navigate to another screen. But navigation to other screens gets slower and slower becuase in my initState() i have a method which initializes the json data which i show each time i navigate to another screen. The json data is big and i only use one file with big json data which has objects and each object is shown i different screens. In my usecase i have to use one file with big json data.
In my initState() i initialize the data i grabbed with the method setTestData() and inside this method i set the data inside an object:
late Map<String, dynamic> grabbedData = {};
setTestData() async {
await TestData()
.getTestData()
.then((result) => setState(() => grabbedData = result));
}
#override
initState() {
setTestData();
super.initState();
}
In my view i can for example navigate to another screen and then show different objects inside the same object json i grabbed in setTestData(). I only use one view called AppView() to show different screen so when i navigate for example from screen A to B, both A and B screen are shown with AppView() widget. This is necessary for my use case which is irrelevant for this question.
This is the navigation which i use to load another screen and which technacly runs initState() from AppView() again because the previous route is also AppView():
Navigator.push(
context,
MaterialPageRoute(
maintainState: false,
builder: (_) => AppView(
selectedMenuItems:
grabbedData['data']
['views'][
widget.selectedMenuItems[
index]
[
'content']],
)));
But the problem is that each time i navigate to AppView() and click back on my phone to show previous data from AppView() and navigate again, it re-initializes the state and so the proces is slowed after i repeat it a couple of times. How do i solve this problem?
Better to use Provider package for this task. Provider does not rebuild the widget rather it only updates the state of the widget so it is faster. Then, you can get your data or data stream only once at the beginning of your app or a particular screen. No need to generate data each time. Also, provider automatically disposes data when the screen is closed. It is recommended by flutter team as well and an awesome package. For more example, check some YouTube videos. For your particular problem, I think it is better to use provider.value. Check the references and hopefully later on you will understand what is a provider.value object.
If you are generating new data every time then you need to set your provider.value each time where you are using Navigator.push, otherwise if you do not use your provider at the beginning of your app at MaterialApp section then after the push the provider won't be available.
As an example, to add provider inside a push please follow the following code snippet:
Navigator.push(context,
MaterialPageRoute(builder: ((context) {
return StreamProvider<User>.value(
initialData: initialUserData,
value: userDataStream,
child: const UpdateUser(),
);
})));
Next, access the value in the UpdateUser page like this:
final user = Provider.of<User>(context);
If you are not using any data stream then just try the normal ChangeNotifierProvider and get the value from the Consumer. Try some youtube tutorials and you will love it.

Stateful widget consumer widget builds twice and hence calls initState twice. How to stop this from happening?

So for my project, I am using Riverpod, and I am creating the home page of my app.
The tree looks like this ->
CommunityView(Stateful Widget)
WillPopScope
ProviderScope
Consumer
Scaffold
...and so on
Now inside the build method of the CommunityView Widget,
final params = _VSControllerParams(
selectedFamily: widget.selectedFamily,
);
print('familiesStatus: rebuild main');
return WillPopScope(
onWillPop: onWillPop,
child: ProviderScope(
overrides: [_paramsProvider.overrideWithValue(params)],
child: Consumer(
builder: (context, watch, child) {
print('familiesStatus: rebuild consumer');
final state = watch(_vsProvider(params));
final stateController = watch(_vsProvider(params).notifier);
The rebuild main print happens only once, while the rebuild consumer print happens twice. Previously home page fetched data and then shows data fetched or error depending on network response. Now I use an enum to get loading state, i.e. ApiStatus.loading is the initial state, then ApiStatus.success or failure depending on response. Now what I have changed that is causing this issue is -> I have added a function call in initstate that fetches local saved cache and loads data from that. So what happens is ApiStatus.loading->ApiStatus.success(Cache fetched)->ApiStatus.loading(somehow whole widget rebuild)->ApiStatus.success(cache fetched again)->ApiStatus.success(Data fetched from internet). So I am not sure why it is rebuilding first time when cache is fetched.
Now to fix this, I first tried to find any reason in the cache method call that is causing this rebuild, but was not able to find any even with heavy debugging. Then I thought to create a global parameter which is something like this
bool fetchDataFromCache = true;
and then inside initState, I call fetch Cache method something like this
if(fetchDataFromCache){
fetchCache();
fetchDataFromCache = false;
}
But this caused the ApiStatus loading parameter to change like this ->ApiStatus.loading(initial build)->ApiStatus.success(Cache fetched)->ApiStatus.loading(somehow whole widget rebuild)->ApiStatus.success(Data fetched from internet). So widget is still rebuilding because I have set ApiStatus.loading only in initState, and nowhere else. So it is still glitching out.
Demo video - https://youtu.be/1EzYfCRiwk0

How does the listen: false work in Flutter Providers?

I just started learning Flutter, and I'm going through the Udemy course "Flutter & Dart - The Complete Guide". In that course, there is a section about building a shopping app, which uses providers. In one instance of that app, where the user swipes to delete a product from the Cart page (or screen/route) with the help of the Dismissible widget, he uses a function inside the provider class, which takes a product ID, to delete the item from the cart.
Here is the thing that I don't understand. The Dismissible widget is connected to the provider via this code in the onDismissed property (which fires after the swipe):
Provider.of<Cart>(context, listen: false).removeItem(productId);
And it all works just fine like this. But if you remove the listen parameter (hence turning it into it's default state which is true), then the Dismiss animation still takes place, but the removeItem() method doesn't work, and the cart still stays the same.
Why does this happen?
When we use listen: false we are telling to not rebuild the widget after we remove an item, but they know that we are removing an item so we don't need to listen any value here it's just doing the action of removing
I'll refer to the method Provider.of(context, listen: true) as x throughout this answer.
x is expected to be used ONLY for properties of a Widget that is
expected to change; and
can be rebuilt. For example:
SizedBox(
width: Provider.of<MyLogic>(context, listen: true).width,
)
When used this way, x will ONLY be called when the context owner is being built/rebuilt.
To ensure that it is being used properly, x performs a sanity check every time it is called, making sure that the owner of the context you passed is actually being built/rebuilt. When you call x from within your onPressed or whatever method it is you call it from, x sees that the context owner is not in the "build" phase, and throws this error.
There are a few more details to this, but you don't actually need to know more about it (especially you're just beginning) unless you want to contribute to the package, in which case you should read their documentation.
Side note: you can now use context.watch() and context.read() instead of Provider.of().

How can I listen to changes in my bloc stream

I am new to flutter and am trying to build an app using Bloc architecture.
I could manage to figure out a sink, inputting my event bloc.updateNavigation(_selectedItem.value);. I also figured out how to use that data by calling my bloc (is that what its called). I had a TextFormField and changed some settings like this if (bloc.navigationProvider.currentNavigation == 1), if (bloc.navigationProvider.currentNavigation == 2)
There is one thing I couldn't figure out how to do though. I would like to set _Controller.clear(); whenever the bloc changes. So that whenever a user makes a new selection and changes the "currentNavigation" the text controller clears. Is there a way I can find out just when a change is made, rather than having to call by the changes. I would like to do something like if (change is made to currentNavigation) {_Controller.clear();}
My bloc file looks like this:
class NavigationBloc {
final navigationController = StreamController();
NavigationProvider navigationProvider = new NavigationProvider();
Stream get getNavigation => navigationController.stream;
void updateNavigation(int navigation) {
navigationProvider.updateNavigation(navigation);
navigationController.sink.add(navigationProvider.currentNavigation);
}
void dispose() {
navigationController.close();
}
}
final bloc = NavigationBloc();
Streams have a .listen(..) method which gets called everytime data is passed into the stream.
For better usage, use the flutter_bloc package. It provides widgets to provide a bloc to all descendants (BlocProvider) and widgets to listen to state-changes (BlocListener). There you don't have to mess with streams at all and there are a few plugins that help creating the boilerplate code.

How can you get the flutter visible page's state or context?

Sometimes an event (eg upload) starts async while the user is on one page. If they navigate away from that page the task's .then(...) will try to display the result on the page which is no longer visible, eg by means of a toast.
How can I get the context of the currently visible page at the time when the Future completes to display a snackbar, toast or dialog?
EDIT:
I see in the description of oktoast (https://pub.dev/packages/oktoast) that version 2 "Does not need the buildContext to be passed", and further
Quote:
Explain #
There are two reasons why you need to wrap MaterialApp
Because this ensures that toast can be displayed in front of all other
controls Context can be cached so that it can be invoked anywhere
without passing in context
This implies to me that there is a better way by providing a Material app ancestor somewhere....
For a simple Solution I would use the Provider Package. Keyword here is StateManagement (https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple)
The Model would look sth like this:
class UploadModel extends ChangeNotifier {
final bool loading = false;
void uploadXY() async{
loading = true;
// This call tells the widgets that are listening to this model to rebuild.
notifyListeners();
await realUploadStuff();
loading = true;
notifyListeners();
}
}
To start the upload:
Provider.of<UploadModel>(this).uploadXY()
To react if loading-bool changes:
if(Provider.of<UploadModel>(this).loading)...
You can find a simple Example here:
https://github.com/flutter/samples/blob/master/provider_counter/lib/main.dart