Navigate back and show snack - dart

I am thinking of this pattern for my application, and cannot find a proper way of doing it.
User is in list page, she clicks on an item and goes to edit page. She edits, and hits the save. Now I want the application to
- show spinning wheel, while saving the item
- after save pop the route to go back to list page, AND show a snackbar on the list page.
I don't seem to find a way to pass a parameter to the previous route to tell it about the state, which is to show the snackbar.

For showing progress on click of save button you can use something like this.
Firstly initialize a variable static bool isSaving = false; then in your scaffold use it like this.
child:Stack(
children: <Widget>[
isSaving
? new Container(
child: new CircularProgressIndicator(
value: null,
strokeWidth: 2.0,
),
alignment: Alignment.center,
)
: new Container(
child: new RaisedButton(
onPressed: () {
setState(() {
isSaving = true;
//Other Implementations here
});
},
child: new Text("Save"),
),
),
],
),
When the operation is complete again set state and make variable false.
Now when you get notified that operation has been done use Navigator to go back.
Navigator.pop(context);
For showing snackbar in your previous screen use a Global Key.
GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
Assign this key to your scaffold.
new Scaffold(
key: scaffoldKey,
...
)
As answered here show snackbar just after pop operation like this.
scaffoldKey.currentState
.showSnackBar(new SnackBar(content: new Text("Saved Successfully")));
Or can try a different approach as explained in this sample.
Hope it helps.

You can send data back through pop method. You can do something like that.
In your edit page:
bool saved = item.save();
Navigator.pop(context, saved);
In your list page:
var saved = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => EditRoute(item)),
);
if (saved) {
// Show success snackbar
} else {
// Show error snackbar
}

Related

How to update webview's url on setState flutter?

I need to update webview's url with my setState. How to do that?
The initial url I haved set on constructor have been loaded successfully. But it's not working when I try load the new one through my drawer menu by onTap method.
...
class _MyWebState extends State<MyWeb> {
Completer<WebViewController> _controller = Completer<WebViewController>();
final Set<String> _favorites = Set<String>();
String url;
Widget web;
_MyWebState() {
web = WebView(
initialUrl: 'https://my.flazhost.com',
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('MyWeb'),
actions: <Widget>[
NavigationControls(_controller.future),
Menu(_controller.future, () => _favorites),
],
),
body: web,
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
leading: FlutterLogo(
size: 20,
),
title: Text('Web AlQudwah'),
onTap: () {
setState(() {
web = WebView(
initialUrl: 'https://flazhost.com',
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
);
Navigator.pop(context);
});
},
),
],
),
),
floatingActionButton: _bookmarkButton(),
);
}
}
...
How to update webview's url onTap method? Please help?
I struggled with the flutter webview for a long time, and I'm still not very happy with it...
But the key is the WebController instance that you get from the onWebViewCreated method of the webview. The instance of the initialized WebViewController has a method called loadUrl('your_new_url')
So you can just call following in your onTap handler:
onTap: () {
_controller.loadUrl('your_new_url');
}
When working with WebViews in flutter I experienced problems when using setState, because it will trigger a rebuild of whole page including the webview which gets reinitialized and of course reloads the page...
But that is mostly not the desired result, so if you want to controll the webview (wheater it will be changes to url, going back in browser history or executing javascript) you should handle it over the WebController initialized from the onWebViewCreated method
Here are the most used methods of the WebViewController class:
_controller.currentUrl() // Gets current url from WebView
_controller.loadUrl('your_new_url'); // Loads new url
_controller.evaluateJavascript('your js code as a string') // Execute JS over WebView console
_controller.goBack() // Goes one step back in browser history
_controller.goForward() // Goes one step forwards in browser history
_controller.clearCache() // Clears cache of WebView
It's really confusing and badly documented (espacially for beginners like me), so I hope the flutter team will provide us with more detailed information and tutorials

Dismissible confirmDismiss in combination with new route navigation causes Flutter crash

The context:
I stumbled upon a minor crash while testing a ListView of Dismissibles in Flutter. When swiping a dismissible, a Dialog is shown using the confirmDismiss option, for confirmation. This all works well, however the UI crashes when testing an unlikely use case. On the page are several options to navigate to other (named) routes. When a dismissible is swiped, and during the animation an option to navigate to a new route is tapped, the crash happens.
How to replicate the crash:
Dismiss the Dismissible
During the animation that follows (the translation of the position of the dismissible), tap on an action that brings you to a
new route. The timeframe to do this is minimal, I've extended it in the example.
The new route loads and the UI freezes
For reference, this is the error message:
AnimationController.reverse() called after AnimationController.dispose()
The culprit is the animation that tries to reverse when it was already disposed:
package:flutter/…/widgets/dismissible.dart:449
Things I've tried:
Initially, I tried checking this.mounted inside the showDialog builder but quickly realised the problem is not situated there.
Another idea was to circumvent the problem by using CancelableOperation.fromFuture and then cancelling it in the dispose() method of the encompassing widget, but that was to no avail.
What can I do solve or at least circumvent this issue?
The code (can also be found and cloned here):
// (...)
class _DimissibleListState extends State<DimissibleList> {
int childSize = 3;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: childSize,
itemBuilder: (context, index) {
if (index == 0) {
return _buildNextPageAction(context);
}
return _buildDismissible();
},
),
);
}
Widget _buildNextPageAction(context) {
return FlatButton(
child: Text("Go to a new page"),
onPressed: () => Navigator.of(context).pushNamed('/other'),
);
}
Dismissible _buildDismissible() {
GlobalKey key = GlobalKey();
return Dismissible(
key: key,
child: ListTile(
title: Container(
padding: const EdgeInsets.all(8.0),
color: Colors.red,
child: Text("A dismissible. Nice."),
),
),
confirmDismiss: (direction) async {
await Future.delayed(const Duration(milliseconds: 100), () {});
return showDialog(
context: context,
builder: (context) {
return Dialog(
child: FlatButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text("Confirm dismiss?"),
),
);
},
);
},
resizeDuration: null,
onDismissed: (direction) => setState(() => childSize--),
);
}
}
I had almost same problem with confirmDismiss ,in my case I was using (await Navigator.push() ) inside of confirmDismiss to navigate to another screen but in return I faced this error :
AnimationController.reverse() called after
AnimationController.dispose()
so to solve my problem inside of confirmDismiss I call a future function out side of confirmDismiss (without await ) and then add return true or false after that function call to finish animation of confirmDismiss.

Not able to delete in onDismissible

I have a code, that uses dismissible in the listview (showes items from database). After dismissing an item it is supposed to show snackbar but it is not showing it and it seems that the dismissible is still part of the tree. Can you help me with that?
return ListView.builder(
itemCount: count,
itemBuilder: (BuildContext context, int position) {
final ThemeData theme = Theme.of(context);
return Dismissible(
key: Key(this.objs[position].id.toString()),
onDismissed: (direction) {
setState(() async {
int result = await helper.delete(this.objs[position].id);
});
Scaffold.of(context)
.showSnackBar(SnackBar(
content: Text(this.objs[position].title + "dismissed")));
},
background: Container(
color: Colors.red,
child: const ListTile(
leading: Icon(Icons.delete, color: Colors.white, size: 36.0)
)
),
child: ListTile(
leading: CircleAvatar(
backgroundColor: getColor(this.objs[position].priority),
child: Text(this.objs[position].id.toString()),
),
title: Text(obj[position].title),
subtitle: Text(objs[position].date),
onTap: () {
debugPrint("Tapped on " + objs[position].id.toString());
navigateToDetail(this.objs[position]);
},
),
);
},
);
this is called inside a Scaffold. And objs is a list that contains all my objects from the database.
Here is my delete code that is called inside onDismissed:
Future<int> delete(int id) async {
Database db = await this.db;
var result = await db.rawDelete("DELETE FROM $tblT WHERE $colId=$id");
return result;
}
I've noticed if I delete one item, and immediately try to create another one (I have an option to insert to DB):
It sometimes throws the error: A dismissed Dismissible widget is still part of the tree
Update:
Moved the delete part, before setState and I am getting the error: A dismissed Dismissible widget is still part of the tree every time I swipe to dismiss
You could try the following code for the onDismissed: property.
The problem is the future inside the onDismissed function. We need to reorder the async and await keywords.
Anyway, take care with the timings when removing successive items.
onDismissed: (direction) async {
String title = this.obj[position].title;
await helper.delete(this.obj[position].id);
setState(() {});
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text("$title dismissed")));
},
It also moves the async out of the setState() and stores the title to be used later by the SnackBar.
Inside content in SnackBar you can try :
Text(this.obj[position].title.toString() + "dismissed")

State's build() method keep get called when I am using image_picker and image_crop library in iOS device only

I am using these two libraries for picking and cropping images from gallery or camera (image_picker and image_cropper) when I select image from gallery and want to perform further operation then build method is called automatically and changes the flow of my code.
In android devices this code is working fine and build method is called only once, but in iOS device build is called when I select image from gallery and after I crop this image.
In iOS devices as the device's photo library opened then build method is called and when crop is called then again build method is called of Drawer class.
This issue is occurring in Drawer only, if I call my TextRobo class as Navigator.of(context).pushReplacementNamed('/textRobo'); then this works fine.
Drawer Class
_getDrawerItemWidget(int pos, String title) {
switch (pos) {
case 0:
if(title.contains("From Gallery"))
return new TextRobo();
if(title.contains("From Camera"))
return new TextRoboCamera();
else if(widget.fragment_class.contains("Translate"))
return new TranslateLangue(widget.textToTranslate);
else
return new TranslateLangue("");
break;
case 1:
if(title.contains("From Gallery"))
return new BarCodeRobo();
else
return new BarCodeQuick();
break;
case 2:
return new TranslateLangue("");
//default:
//return new TranslateLangue("");
}
}
#override
Widget build(BuildContext context) {
print('Building widget');
return new Scaffold(
appBar: new AppBar(
iconTheme: new IconThemeData(color: Colors.white),
title: new Text("RoboScan",
style: new TextStyle(color: Colors.white),),
),
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new Container( height: 140.0, color: Colors.orange,
child: new Center(child:
new Text('RoboScan', style: new TextStyle(color: Colors.white,
fontSize:25.0, fontWeight: FontWeight.bold),
),
),
),
new Column(
children: drawerOptions)
],
),
),
body: _getDrawerItemWidget(_selectedDrawerIndex, widget.fragment_class ),
);
}
Image Picker and Crop class(TextRobo)
File _imageFile;
List<VisionText> _currentTextLabels = <VisionText>[];
FirebaseVisionTextDetector textDetector =
FirebaseVisionTextDetector.instance;
#override
void initState() {
// TODO: implement initState
//scanImage();
super.initState();
_getAndScanImage();
}
Future<void> _getAndScanImage() async {
setState(() {
_imageFile = null;
// _imageSize = null;
});
final File imageFile =
await ImagePicker.pickImage(source: ImageSource.gallery);
_cropImage(imageFile);
}
The widget's build method is meant to called frequently. I would recommend you re-structure things in order to allow your build method to be called as required by the Flutter framework.
Note: It's a good idea to push 'state' as far down the tree as possible (towards the leaf widgets) in order to minimize this effect of widget rebuilding due to state change.
In your case, you may want to consider removing _getAndScanImage() from the initState method. Having your render flow impact your interactions is not a good pattern.
Can you try having the _getAndScanImage() method be triggered by a button press or some other user trigger action rather than the rendering lifecycle in initState?

Dismissing a Cupertino dialogue action Flutter

While dismissing a presented Cupertino alert dialogue action using the explained method my entire screen gets popped of and the alert dialogue stays on the screen. This is my code.
if (deviceList.isEmpty){
var alert = new CupertinoAlertDialog(
title: new Text("Alert"),
content: new Text("There was an error signing in. Please try again."),
actions: <Widget>[
new CupertinoDialogAction(
child: const Text('Discard'),
isDestructiveAction: true,
onPressed: () { Navigator.pop(context, 'Discard'); }
),
new CupertinoDialogAction(
child: const Text('Cancel'),
isDefaultAction: true,
onPressed: () { Navigator.pop(context, 'Cancel'); }
),
],
);
showDialog(context: context, child: alert);
}
Is there anything wrong in what I am doing? I cant find any other solution to dismiss the alert dialogue. Please help.
In this case, you need to specify the rootNavigator to of() :
Navigator.of(context, rootNavigator: true).pop("Discard");
Check the implementation proposed in the documentation

Resources