I'm trying to get the current snack bar (showing one) to determine if it is the same snack that am trying to show or not ; in other words ,i don't want to duplicate the snack , but i couldn't get it.
I'v tried to google it up but nothing showed about it and what is really pissing me off is when i call Scaffold.of(context).showSnackBar(sb); multiple times they show will show one at a time until they end .
import 'package:flutter/material.dart';
const sb = SnackBar(
content: Text('the snack bar'),
);
void main() => runApp(MaterialApp(
title: 'Snack bar test',
home: Scaffold(
appBar: AppBar(
title: Text('snack bar demo'),
),
body: Center(
child: MyApp(),
),
),
));
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text('push to test'),
onPressed: () {
// Scaffold.of(context).
Scaffold.of(context).showSnackBar(sb);
},
);
}
}
What i want to achieve is something like this :-
if (sb.isShowing()){
//hide or remove it
}
and thanks in advance .
You can use .close() event. It specifies how a snack bar is closed. Details here and Sample code below :
bool _isSnackbarActive = false ;
...
...
_isSnackbarActive = true ;
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text("Title")))
.closed
.then((SnackBarClosedReason reason) {
// snackbar is now closed.
_isSnackbarActive = false ;
});
The variable _isSnackbarActive to true when snackBar is displayed and set it to false when it is closed. In your onPressed event, just check the status of the snackbar and decide if new snackbar is to be shown or not. In addition, per your requirement, you can check the text on the current snack bar to see if the intended snackbar and the current one are same.
To me this works fine, and don't need an external variable to control visibility status. Maybe would be helpful for others.
I think it isn't the best approach, because it will not prevent new SnackBars, it will just clean the "queue" on close.
ScaffoldMessenger.of(context)
.showSnackBar(_snackBar)
.closed
.then((value) => ScaffoldMessenger.of(context).clearSnackBars());
Use ScaffoldMessenger.of(context).removeCurrentSnackBar(); since Scaffold.of(context).removeCurrentSnackBar(); is deprecated.
Just call removeCurrentSnackBar() to remove the current snackbar.
import 'package:flutter/material.dart';
const sb = SnackBar(
content: Text('the snack bar'),
);
void main() => runApp(MaterialApp(
title: 'Snack bar test',
home: Scaffold(
appBar: AppBar(
title: Text('snack bar demo'),
),
body: Center(
child: MyApp(),
),
),
));
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('push to test'),
onPressed: () {
// Scaffold.of(context).
Scaffold.of(context).showSnackBar(sb);
},
),
RaisedButton(
child: Text('Remove snackbar'),
onPressed: () {
// Scaffold.of(context).
Scaffold.of(context).removeCurrentSnackBar();
},
),
],
);
}
}
Adding to what #Sukhi mentioned
//Set this in state
...
bool _isSnackbarActive = false;
...
.
.
.
//If you want to show snackBar on a button press
onPressed: () {
if (!_isSnackbarActive) {
showSnack();
}
//This is the showSnack(). When called it'll set the _isSnackbarActive to true and now even if you click on the button multiple times since it's true a new snackBar will not be shown. Once the current snackBar closes it set _isSnackbarActive to false and then it can be called again.
showSnack() {
setState(() {
_isSnackbarActive = true;
});
return ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Posts updated!")))
.closed
.then((SnackBarClosedReason reason) {
// snackbar is now closed.
setState(() {
_isSnackbarActive = false;
});
});
}
There are two easy ways:
removeCurrentSnackBar will disappear without animation
ScaffoldMessenger.of(context).removeCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("snackbar text")));
hideCurrentSnackBar will disappear with animation
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("snackbar text")));
chose according to your design.
Related
I need to dismiss an alert dialogue when a callback is being called. How can i achieve it?
One way is you can add timer for it.
Try this code:-
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(GetMaterialApp(title: 'Flutter', home: Flutter()));
}
class Flutter extends StatefulWidget {
const Flutter({Key? key}) : super(key: key);
#override
State<Flutter> createState() => _FlutterState();
}
class _FlutterState extends State<Flutter> {
int seconds = 3;
bool visible = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter'),
centerTitle: true,
),
body: Center(
child: ElevatedButton(
child: Text('Open Alert'),
onPressed: () {
RemoveAlert();
visible = true;
Get.defaultDialog(
title: 'Alert',
middleText: 'Alert will go in ${seconds} seconds').then((value){
visible = false;
});
},
)),
);
}
void RemoveAlert() {
Future.delayed(Duration(seconds: seconds)).then((value){
if(visible){
Get.back();
print('removed');
}else{
print('removed already');
}
});
}
}
Note: I have used get package here.
Yes I got the solution after a long time effort.
I called Navigator.pop(context) two time. Then it worked for me.
I have added following code to my main screen in order to exit app after 2 times back button taps
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final timeGap = DateTime.now().difference(preBackpress);
final cantExit = timeGap >= const Duration(seconds: 2);
preBackpress = DateTime.now();
if(cantExit){
const snack = SnackBar(
content: Text('Press Back button again to Exit'),
duration: Duration(seconds: 2),
);
ScaffoldMessenger.of(context).showSnackBar(snack);
return false;
}else{
return true;
}
},
child: Scaffold(....)
);
}
But what I get instead is that when I hit back button it goes all the steps back to every single screen user has been visited.
The reason I added WillPopScope above was to avoid going back to visited screen and just close the app but it seems still doesn't help.
My question is: How do I exit app (when in main screen back button is tapped) without going back to visited screens?
Maybe this is not the same as your code. but I have code to close apps as you want
class ExitController extends StatefulWidget {
#override
_ExitControllerState createState() => _ExitControllerState();
}
class _ExitControllerState extends State<ExitController> {
static const snackBarDuration = Duration(milliseconds: 2000);
// set how long the snackbar appears
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
DateTime? backButtonPressTime;
final snackBar = SnackBar(
behavior: SnackBarBehavior.floating,
// margin: EdgeInsets.fromLTRB(75, 0, 75, 250),
// add margin so that the snackbar is not at the bottom
duration: snackBarDuration,
backgroundColor: Colors.black87,
content: Text('Press Again to Exit',
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12)),
);
Future<bool> onWillPop() async {
DateTime currentTime = DateTime.now();
bool backButtonCondition =
backButtonPressTime == null ||
currentTime.difference(backButtonPressTime!) > snackBarDuration;
if (backButtonCondition) {
backButtonPressTime = currentTime;
ScaffoldMessenger.of(context).showSnackBar(snackBar);
return false;
}
return true;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
body: WillPopScope(
onWillPop: onWillPop,
child: yourPageClass(),
),
);
}
}
The snackbar will appear for 2000 milliseconds and during that time if the user press the back button again, the application will exit
Note to call ExitController class in main.dart, and wrapping your all structure(navigation class/page class/etc) with this ExitController class.
Example:
main.dart
import 'package:flutter/material.dart';
import 'package:app_name/structure.dart';
void main(){
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ExitController(),
);
}
}
WillPopScope(
onWillPop: () async {
return await showDialog(context: ctx, builder: (_) => exitAlertDialog(_));
},
child: GestureDetector(onTap: () => FocusScope.of(ctx).unfocus(), child: widget));
Widget exitAlertDialog(BuildContext context) {
return AlertDialog(
backgroundColor: plColor2,
content: Text('Are you sure you want to exit?'),
actions: <Widget>[
TextButton(child: Text('No'), onPressed: () => Navigator.of(context).pop(false)),
TextButton(child: Text('Yes, exit'), onPressed: () => SystemNavigator.pop())
],
);
}
or you can use showsnackbar as your need
I have list of Dismissible widgets that I'd like to swipe left to delete. These have a an argument confirmDismis which takes a Future<bool>. I'd like to show a SnackBar once you've swiped an item allowing you to undo the delete for a few seconds. For this I have this method:
Future<bool> _confirmDismiss() {
var remove = true;
var duration = Duration(seconds: 5);
final snackbar = SnackBar(
duration: duration,
content: Text('This item was deleted'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
remove = false; //how to return false immediately?
},
),
);
_scaffoldKey.currentState.showSnackBar(snackbar);
return Future.delayed(duration,(){
return remove;
});
}
The snackbar is shown for 5 seconds, after that it disappears and the callback is triggered.
Now I'd like to return the item as soon as you hit the undo button. Right now you have to wait for the 5 seconds to pass for it to return.
I have tried timeout and Time() and many other things but I can't seem to make it work... Any ideas?
#Janneman, I don't think you need to use Future.delayed, that could be the reason why you now have to wait 5 secs. Just use the default SnackBar functions. Here is a quick example that shows SnackBar for 5 secs and quickly changes the text on Undo,
class MyApp extends StatefulWidget {
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
String text = 'Yay! A Snackbar';
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('SnackBar'),
),
body: Builder(builder: (BuildContext context) {
return Padding(
padding: EdgeInsets.all(20.0),
child: Center(
child: Column(children: <Widget>[
Text(text),
RaisedButton(
onPressed: () {
text = 'Snackbar changed';
setState(() {
});
final snackBar = SnackBar(
duration: Duration(seconds: 5),
content: Text(text),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
text = 'Yay! A Snackbar';
setState(() {});
},
),
);
// Find the Scaffold in the widget tree and use
// it to show a SnackBar.
Scaffold.of(context).showSnackBar(snackBar);
},
child: Text('Show SnackBar'),
),
])));
})));
}
}
Hope this helps. Good luck!
Flow is: user clicks on a link from screen 1 to go to screen 2. On screen 2, user enters required data and taps on save button that saves data and navigates to screen 1 where I want to show a snackbar.
This sounds very similar to this and this post, but it's not working for me. So, I followed this code sample which works properly, but there's one issue.
If I press app bar back button or device back button from screen 2, it still shows the snackbar on screen 1. Wondering how could I avoid this behavior of not showing snackbar after hitting back buttons.
After user saves data on screen 2, I am simply using Navigator.pop(context) that takes user to screen 1. On screen 1, I've a method that navigates to screen 2 and triggers snackbar as below:
Navigator.push(
context, MaterialPageRoute(builder: (context) => Screen2())
);
scaffoldKey.currentState.showSnackBar(SnackBar(content: Text('Show me'),));
Although this works, I don't want to show the snackbar if user clicks back button.
Problem is, your snackBar has duration, within that duration if you navigate back to the screen, you'll see that old snackBar again.
Solution is add this method before Navigator:
Scaffold.of(context).removeCurrentSnackBar();
In your case, you're using scaffoldKey, so translate it as you need. But, be sure you are contacting with right Scaffold.
scaffoldKey.currentState.removeCurrentSnackBar();
Official link:
https://api.flutter.dev/flutter/material/ScaffoldState/removeCurrentSnackBar.html
Edit: About calling Screen 1 Scaffold inside Screen 2 Scaffold:
Your screen has Scaffolds. Scaffolds have keys in your situation as I see.
Let's say Screen1 has screen1Key ScaffoldState key. Same for Screen2.
Inside Screen2 you must call
screen1Key.currentState.removeCurrentSnackBar();
full example:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Screen1(),
));
}
GlobalKey<ScaffoldState> screen1Key = GlobalKey<ScaffoldState>();
GlobalKey<ScaffoldState> screen2Key = GlobalKey<ScaffoldState>();
class Screen1 extends StatefulWidget {
#override
_Screen1State createState() => _Screen1State();
}
class _Screen1State extends State<Screen1> {
#override
Widget build(BuildContext context) {
return Scaffold(
key: screen1Key,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RaisedButton(
onPressed: () {
screen1Key.currentState
.showSnackBar(SnackBar(content: Text('Screen 1 SnackBar')));
},
child: Text('Show SnackBar'),
),
RaisedButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return Screen2();
}));
},
child: Text('Navigate forward'),
),
],
),
),
);
}
}
class Screen2 extends StatefulWidget {
#override
_Screen2State createState() => _Screen2State();
}
class _Screen2State extends State<Screen2> {
#override
Widget build(BuildContext context) {
return Scaffold(
key: screen2Key,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RaisedButton(
onPressed: () {
screen2Key.currentState
.showSnackBar(SnackBar(content: Text('Screen 2 SnackBar')));
},
child: Text('Show SnackBar'),
),
RaisedButton(
onPressed: () {
screen1Key.currentState.removeCurrentSnackBar();
Navigator.pop(context);
},
child: Text('Navigate Back'),
),
],
),
),
);
}
}
I hope I understand well your problem.
// On this noteItem long press i want to show a snackbar
class NoteItem extends StatelessWidget {
NoteItem({Note note, this.removeNote}): note = note, super(key: ObjectKey(note));
final Note note;
final RemoveNote removeNote;
#override
Widget build(BuildContext context){
return ListTile(
onLongPress: () {
removeNote(note);
},
leading: CircleAvatar(
backgroundColor: Theme.of(context).primaryColor,
child: Text(note.title[0]),
),
title: Text(note.title),
);
}
}
// This class gives the state`enter code here`
class _NoteListState extends State<NoteList> {
void _addNote() {
setState(() {
widget.notes.add(Note(title: 'New Note', description: 'Successfully Added New Note'));
});
}
void _removeNote(Note note) {
setState(() {
widget.notes.remove(note);
Scaffold.of(context).showSnackBar( SnackBar( content: Text('Note Deleted!')));
});
}
#override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text('Notes List'),
),
body: ListView(
padding: EdgeInsets.symmetric(vertical: 8.0),
children: widget.notes.map((Note note) {
return NoteItem(
note: note,
removeNote: _removeNote,
);
}).toList(),
),
floatingActionButton: FloatingActionButton(
onPressed: _addNote,
tooltip: 'Add Note',
child: Icon(Icons.add),
),
);
}
}
If you want to look at the full code go to this link:- https://pastebin.com/h5HJWwdg
I tried to return a scaffold from the NoteItem but then after getting error realized that you can't do that. Also tried using builder that was also not working. It would be helpful if you direct to some documentation if possible so i can avoid these kind of mistakes in future.
I'm learning Flutter and just started with it few days ago so it would be helpful if you also tell if the way I am working on the note app correct.
The method _removeNote (where you are trying to get the Scaffold) is a property of NodeListState which is the state for NodeList. NodeList probably does not have a Scaffold above it in the tree. It however has a Scaffold as a child.
Here is what I do.
Create a property in NodeList
final _scaffoldKey = GlobalKey<ScaffoldState>();
Assign it to the key property of my Scaffold.
Scaffold(
key: _scaffoldKey,
Then get the Scaffold like this...
_scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text("Select player"),
));