Flutter get navigator param in page - dart

I have 2 pages PAGE A and PAGE B. I navigate form PAGE A -> PAGE B and do edit some data, or toggle a setting. Now I want to navigate form PAGE B -> PAGE A and also what that a parameter would be send on navigator pop method. Now my question:
How I can access to these parameter in PAGE A?
Navigator.pop(context, this.selectedEquipmentId);

In fact you got to return something when you ends PageA. I put you an exemple with a popup to select an adress i made recently, this work exactly the same if this is not a popup.
Future<PlacesDetailsResponse> showAdressPicker({#required BuildContext context}) async {
assert(context != null);
return await showDialog<PlacesDetailsResponse>(
context: context,
builder: (BuildContext context) => AdressPickerComponent(),
);
}
You can send a result from Navigator.pop(...) and get it from PageA
Navigator.pop(context, result)
Just put anything you want in result, (here i created a class named PlacesDetailsResponse, use yours or just Int, String...).
Now In pageA when you call this
showAdressPicker(context: context).then((PlacesDetailsResponse value) {
//do whatever you want here
// this fires when PageB call previous to PageA
});

Related

Vaadin Dataprovider: how to avoid "auto-fetch"?

Use Case 1 is answered below, Use Case 2 has been moved to a separate question (Vaadin Flow: Returning to a view, the view should not reload data from the backend)
I'd like to use a Vaadin Flow (v14 LTS/v19) grid component backed by a lazy DataProvider which does not automatically fetch data from the backend when the grid is shown.
There are at least two use cases:
showing grid data does not make sense unless the user provided filter parameters
returning to a #PreserveOnRefresh tagged view should not replace the shown data with current data. (further elaborated in update)
Being pretty new to Vaadin 14+, I could not figure out how to achieve this. Every time my GridView is displayed, the count and fetch callbacks of DataProvider are queried. The call originates from the DataCommunicator of the grid.
So for Use Case 1: How to stop the DataProvider from fetching data as long as it does not make sense?
And for Use Case 2: How to prevent overwriting the grid state when adding a grid to the UI for the second time?
Thanks a lot!
StackTrace to my fetch callback (Vaadin Flow 14):
at org.vaadin.example.GridView.fetch(GridView.java:46)
at org.vaadin.example.GridView.lambda$new$c4b2c115$1(GridView.java:23)
at com.vaadin.flow.data.provider.CallbackDataProvider.fetchFromBackEnd(CallbackDataProvider.java:137)
at com.vaadin.flow.data.provider.AbstractBackEndDataProvider.fetch(AbstractBackEndDataProvider.java:61)
at com.vaadin.flow.data.provider.DataCommunicator.fetchFromProvider(DataCommunicator.java:362)
at com.vaadin.flow.data.provider.DataCommunicator.activate(DataCommunicator.java:647)
at com.vaadin.flow.data.provider.DataCommunicator.collectKeysToFlush(DataCommunicator.java:589)
at com.vaadin.flow.data.provider.DataCommunicator.flush(DataCommunicator.java:461)
at com.vaadin.flow.data.provider.DataCommunicator.lambda$requestFlush$2f364bb9$1(DataCommunicator.java:425)
at com.vaadin.flow.internal.StateTree.lambda$runExecutionsBeforeClientResponse$2(StateTree.java:390)
at [java.util.stream] omitted
at com.vaadin.flow.internal.StateTree.runExecutionsBeforeClientResponse(StateTree.java:387)
at com.vaadin.flow.server.communication.UidlWriter.encodeChanges(UidlWriter.java:411)
at com.vaadin.flow.server.communication.UidlWriter.createUidl(UidlWriter.java:187)
at com.vaadin.flow.server.communication.UidlRequestHandler.writeUidl(UidlRequestHandler.java:122)
at com.vaadin.flow.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:91)
at com.vaadin.flow.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40)
at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1547)
at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:247)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
update 20210430
Here's the code of my GridView which also fakes the backend DataProvider:
#Route(value = "grid", layout = MainView.class)
public class GridView extends VerticalLayout {
public GridView() {
final Grid<Person> g = new Grid(Person.class);
g.setColumns("name");
g.setDataProvider(DataProvider.fromCallbacks(q -> fetch(q), q -> count(q)));
add(g);
// filter omitted
final Button refresh = new Button("refresh");
refresh.addClickListener(e -> {
System.out.println("refresh clicked");
g.getDataProvider().refreshAll();
});
add(refresh);
add(new TextField("State check"));
}
// fake DataProvider
private int count(Query<Person, Void> q) { return 3; }
private Stream<Person> fetch(Query<Person, Void> q) {
q.getLimit(); //vaadin checks these have been called
q.getOffset(); //vaadin checks these have been called
System.out.println("fetching again");
new Exception().printStackTrace(); //figure out who called
return Arrays.asList(new Person("1"), new Person("2"), new Person("3")).stream();
}
}
My MainView is used to switch between GridView and EmptyView
#PreserveOnRefresh
public class MainView extends AppLayout {
private Component emptyBView;
private Component gridBView;
public MainView() {
final Button emptyB = new Button("Btn empty");
emptyB.addClickListener(e -> {
if (emptyBView == null) { emptyBView = new EmptyView();}
setContent(emptyBView);
});
addToNavbar(emptyB);
final Button gridB = new Button("Btn grid");
gridB.addClickListener(e -> {
if (gridBView == null) gridBView = new GridView();
setContent(gridBView);
});
addToNavbar(gridB);
}
}
MainView is an AppLayout used to switch the contents of the AppLayout from GridView to EmptyView and back.
Use Case 2 is: When returning to GridView, the GridView should be exactly same state as before (which works fine with the TextField).
open GridView -> grid should not be filled with data
enter filter params (not shown in code)
click "refresh" to populate the grid
enter "Spiderman" in TextField "stateCheck"
switch to EmptyView
in the real app: do something in EmptyView and potentially other views
return to GridView -> the grid should not reload the data, it should just stay as it was - just like the TextField still displays "Spiderman", the grid should display the same data as before without reloading it.
For Case 1: In the callback check if you have filter parameters, return an empty set if not. Using the new V17+ API it would look like this:
grid.setItems(query -> {
if(filterParameters.isEmpty()) {
// Return an empty stream
} else {
// Fetch from backend
}
});
You can read more in the docs here: https://vaadin.com/docs/latest/flow/binding-data/data-provider (V19) or https://vaadin.com/docs/v14/flow/binding-data/tutorial-flow-data-provider (V14)
I would need more info on what you're currently doing to help out with Case 2. How are you constructing the view, what does your code look like? A full stack trace with the "Caused by" would also help.
I would recommend only setting the DataProvider to the Grid once the first filter parameter is set. The client-side Grid expects to receive the number of items it requires from the fetch query; it might work in some corner case if you don't provide the requested numbers of items from fetch, but it's not designed to behave like that.
Note that this applies specifically to using DataProviders with filters in Vaadin 14 series - Vaadin 17 introduced a new optional simplified way of fetching items, which changes this equation a bit. It's not backported to Vaadin 14 yet (currently planned for 14.7).

Navigating back to page from FutureBuilder

I have a submission form for my app where I have some data the user fills out in a form. I need to GET from an external API in the process, and use that data to create an entry in the database. All this happens once a Submit button is pressed, then after that I want to be able to go back to my homepage route.
I'm not sure how to get data from a Future function without using FutureBuilder, even though I don't need to build a widget, I just need the data.
This is what I have currently:
_populateDB() {
return new FutureBuilder(
future: fetchPost(latitude, longitude),
builder: (context, snapshot) {
if (snapshot.hasData) {
_createJson(snapshot.data);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomeScreen()
),
);
} else if (snapshot.hasError) {
return new Text("${snapshot.error}");
}
return new CircularProgressIndicator();
},
);
}
The _populateDB() function is being called when a button is pressed on the screen. What I would like to do is get data from fetchPost(latitude, longitude), use that data in the function _createJson(snapshot.data), and finally go back to the HomeScreen().
I haven't implemented _createJson(snapshot.data) yet, but currently when I call this method with onPressed, it does not go back to the HomeScreen(), and I'm not sure why.
You can get data from a Future function in asynchronous way or in synchronous way.
1 Asynchronous way
It's simple, you can use Native Future API from dart. The method then is a callback method that is called when your Future is completed. You can also use catchError method if your future was completed with some error.
fetchPost(latitude, longitude).then(
(fetchPostResultsData) {
if (fetchPostResultsData != null)
print (fetchPostResultsData);
} ).catchError(
(errorFromFetchPostResults){
print(errorFromFetchPostResults);
}
);
With this Approach your UI isn't blocked waiting results from network.
2 Synchronous way
You can use Dart key words async and await to keep your calls synchronized. In your case you have to transform your _populateDB method in an async method and await from fetchPost results.
_populateDB() async {
var data = await fetchPost(latitude, longitude);
// just execute next lines after fetchPost returns something.
if (data !=null ){
_createJson(snapshot.data);
//... do your things
}
else {
//... your handle way
}
}
With this approach your _populateDB function will wait the results from fetchPost blocking the UI Isolete and just after getting the results will execute the next instructions.
About Navigation if your HomeScreen is the previous the previous widget on stack you just need Navigator.pop(context) call but if there are others widgets in the Stack above of your HomeScreen is a better choice use Navigator.pushReplacement call.
This article shows in details with illustrations how the effects of Navigator methods. I hope it helps.
Use the below code snippet to solve it.
Future.delayed(const Duration(milliseconds: 0))
.then((value) => Navigator.pushNamed(context, '/routeName'));

how to add a widget in shared preference

I have built an application with intro sliders that appear after the user log in.
I want the intro sliders to appear only for the first user login and not every login user does.
How can I save all the widget in the SharedPreference?
There is no way to save a Widget in SharedPreferences.
You can try this simple logic.
#override
void initState() {
super.initState();
SharedPreferences.getInstance().then((prefs) {
bool isFirstTime = prefs.getBool("first_time") ?? true;
if (isFirstTime) {
// it is first time app opening you can show your intro part
// also call setState() from here to reflect changes in the build method.
prefs.setBool("first_time", false);
} else {
// it is regular opening of the app.
}
});
}
You can't save widgets in SharedPreferences. You can save Boolean value to show intro widgets or not .By default set the Boolean of the variable to true means you can show intro slides, then after showing slides successful then make that Boolean to false. such that from next time it won't appear.

How to check if Widget has mounted in flutter

So in my app, I want to make an Ajax request as soon as the widget is mounted, not in initState(). Similar to ComponentWillMount() in react
if the Widget has not mounted then return. Do it before the setState method
if (!mounted) return;
setState(() {});
or
if (mounted) {
//Do something
};
setState(() {});
If you want to execute some code as soon as the widget loaded , you could simply put this code in the initstate like the following ;
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => yourFunction(context));
}
In this way , the yourFunction will be exectued as soon as the first frame of the widget loaded on the screen.
I don't think it's currently possible.
Here's the mounted property: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/framework.dart#L974
bool get mounted => _element != null;
And here's when _element is set: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/framework.dart#L3816
_state._element = this
And I don't see any hook around this code that would inform us.
Why not use initState anyway? It's probably what you want. Here's the comment above the mounted property: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/framework.dart#L967
/// After creating a [State] object and before calling [initState], the
/// framework "mounts" the [State] object by associating it with a
/// [BuildContext]. The [State] object remains mounted until the framework
Simply do this as follows.
if (this.mounted) {
setState(() {
//Your code
});
}
I know this answer comes a little bit late but...
Inside your method, you should have something like this:
if(mounted){
setState(() {});
}
This would help, to rebuild the UI only if something changes.
I use this myself inside a method, where I fill my list with users from firestore.
The mounted property helps to avoid the error, when you trying to call setState before build.

ICommand not always firing when tab selected

I have a simple ActionBar with 3 tabs attached. When a tab is clicked, the fragment is inflated and the view shows. The tab being click event is fired using an event. Initially, the first fragment is inflated, but the others respond and inflate if clicked.
If I change the event being fired to an ICommand, only the last fragment is inflated and then if I click on the first tab, that and the last are inflated. Never the second.
My code is this
ICommand TabClicked
{
get
{
return new RelayCommand(() =>
{
tab.TabSelected += (object sender, ActionBar.TabEventArgs e) => TabOnTabSelected(sender, e);
});
}
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
fragments.Add(new TODFragment());
fragments.Add(new ConditionsFragment());
fragments.Add(new ResultsFragment());
AddTabToActionBar("Time", Resource.Drawable.crucifix_colour);
AddTabToActionBar("Conditions", Resource.Drawable.weather_colour);
AddTabToActionBar("Results", Resource.Drawable.tod_colour);
}
void AddTabToActionBar(string text, int iconResourceId)
{
tab = ActionBar.NewTab().SetTag(text).SetText(text).SetIcon(iconResourceId);
/* uncomment and comment out one of the two below to see the difference in operation */
tab.TabSelected += TabOnTabSelected;
//tab.SetCommand<ActionBar.TabEventArgs>("TabSelected", TabClicked);
ActionBar.AddTab(tab);
}
void TabOnTabSelected(object sender, ActionBar.TabEventArgs tabEventArgs)
{
var tabNo = sender as ActionBar.Tab;
var frag = fragments[tabNo.Position];
tabEventArgs.FragmentTransaction.Replace(Resource.Id.frameLayout1, frag);
}
Am I missing something fundamental here in the difference between ICommands and Events or is it something else?
I'm using Xam.Android and MVVMLight
I found the answer. When I create the partial class I define the UI objects like this (or something like this at least)
EditText myEditText;
EditText MyEditText = myEditText ?? (view.FindViewById<EditText>(Resources.Id.myEdit);
This is fine, but it does mean that once defined, it doesn't get redefined.
Not a problem if the UI is not really going to change, but every time an action tab is pressed, the fragment is refreshed. Only problem is the Id isn't changing as myEditText is not null.
The answer is add a method in the UI definition code that nulls the objects then in the main code, when the UI disappears, call the nulling method. Everything works then

Resources