This question already has answers here:
Display SnackBar in Flutter
(20 answers)
Closed 10 months ago.
I am trying to show a Snackbar on click of a floatingActionbutton. But when I click on the floatingactionbutton it's not showing anything. Here is my code. I am using a StatefulWidget. I debugged and checked that the onPressed function is also getting executed but somehow the Snackbar is not visible. What can be the root cause of the issue? I feel the BuildContext I am passing has some issue.
class MyApp extends StatefulWidget{
#override
MyAppState createState() {
// TODO: implement createState
return new MyAppState();
}
}
class MyAppState extends State<MyApp>{
File _image;
String _text;
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
_image = image;
final FirebaseVisionImage visionImage = FirebaseVisionImage.fromFile(_image);
final TextRecognizer textRecognizer = FirebaseVision.instance.textRecognizer();
final VisionText visionText = await textRecognizer.processImage(visionImage);
String detectedText = visionText.text;
setState(() {
_image = image;
_text = detectedText;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: new AppBar(
title: new Text('Image Picker Example'),
),
body: new Center(
child: _image == null
? new Text('No image selected.')
: new Image.file(_image),
),
floatingActionButton: new FloatingActionButton(
onPressed: (){
showSnackBar(context);
// getImage();
},
tooltip: 'Pick Image',
child: new Icon(Icons.add_a_photo),
),
),
);
}
void showSnackBar(BuildContext context) {
final scaffold = Scaffold.of(context);
final snackBarContent = SnackBar(
content: Text("sagar"),
action: SnackBarAction(
label: 'UNDO', onPressed: scaffold.hideCurrentSnackBar),
);
scaffold.showSnackBar(snackBarContent);
}
}
That happens because the BuildContext used has not a Scaffold ancestor thus, won't be able to find it to render a SnackBar since it's up to the Scaffold to display it.
According to the of method documentation:
When the Scaffold is actually created in the same build function, the
context argument to the build function can't be used to find the
Scaffold (since it's "above" the widget being returned). In such
cases, the following technique with a Builder can be used to provide a
new scope with a BuildContext that is "under" the Scaffold:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Demo')
),
body: Builder(
// Create an inner BuildContext so that the onPressed methods
// can refer to the Scaffold with Scaffold.of().
builder: (BuildContext context) {
return Center(
child: RaisedButton(
child: Text('SHOW A SNACKBAR'),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Hello!'),
));
},
),
);
},
),
);
}
Solution
Wrapping your FloatingActionButton in a Builder widget will make it possible in a more elegant way than using a GlobalKey, which was already mentioned by the #Epizon answer.
class MyApp extends StatefulWidget{
#override
MyAppState createState() {
// TODO: implement createState
return new MyAppState();
}
}
class MyAppState extends State<MyApp>{
final GlobalKey<ScaffoldState> _scaffoldkey = new GlobalKey<ScaffoldState>();
File _image;
String _text;
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
_image = image;
final FirebaseVisionImage visionImage = FirebaseVisionImage.fromFile(_image);
final TextRecognizer textRecognizer = FirebaseVision.instance.textRecognizer();
final VisionText visionText = await textRecognizer.processImage(visionImage);
String detectedText = visionText.text;
setState(() {
_image = image;
_text = detectedText;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
key: _scaffoldkey,
appBar: new AppBar(
title: new Text('Image Picker Example'),
),
body: new Center(
child: _image == null
? new Text('No image selected.')
: new Image.file(_image),
),
floatingActionButton: new FloatingActionButton(
onPressed: (){
showSnackBar();
// getImage();
},
tooltip: 'Pick Image',
child: new Icon(Icons.add_a_photo),
),
),
);
}
void showSnackBar() {
final snackBarContent = SnackBar(
content: Text("sagar"),
action: SnackBarAction(
label: 'UNDO', onPressed: _scaffoldkey.currentState.hideCurrentSnackBar),
);
_scaffoldkey.currentState.showSnackBar(snackBarContent);
}
}
To show SnackBar You can use like this:
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('User Logged In'),
));
Previous SnackBar was used like this:
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Rahul Kushwaha!'),
));
Now, SnackBar is managed by ScaffoldMessenger. For details study this link
I faced this issue when trying to showSnackbar() in StatfulWidget from second level child of the parent Parent() => childLevel1 => childOfLevel1(){showing the snackbar here}
I implemented #Epizon answer but .currentState.showSnackBar(snackBarContent); was deprecated, According to flutter:
The SnackBar API within the Scaffold is now handled by the ScaffoldMessenger
read full document
so replacing this line in #Epizon answer:
_scaffoldKey.currentState!.showSnackBar(_snackBarContent);
to this:
ScaffoldMessenger.of(_scaffoldKey.currentState!.context) .showSnackBar(_snackBarContent);
will solve the deprecation issue
here is the code
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
void _showSnack(String msg) {
final _snackBarContent = SnackBar(content: Text(msg));
ScaffoldMessenger.of(_scaffoldKey.currentState!.context)
.showSnackBar(_snackBarContent);
}
return MaterialApp(
home: Scaffold(
//resizeToAvoidBottomInset: true,
key: _scaffoldKey,
body: Center(
child: ElevatedButton(
child: Text("show snackbar"),
onPressed(){_showSnack("im located in the btn's onPressed");},
);
);
Related
I am listing different files from device storage with changing extension i followed this link for getting files
link
getting files from device
function for getting files
var files;
Future <void> getFiles() async {
if (await Permission.storage.request().isGranted) {
// Either the permission was already granted before or the user just granted it.
}
//asyn function to get list of files
List<StorageInfo> storageInfo = await PathProviderEx.getStorageInfo();
var root = storageInfo[0]
.rootDir; //storageInfo[1] for SD card, geting the root directory
var fm = FileManager(root: Directory(root)); //
files = await fm.dirsTree(
excludedPaths: ["/storage/emulated/0/Android"],
//optional, to filter files, list only pdf files
);
setState(() {}); //update the UI
}
#override
void initState() {
getFiles(); //call getFiles() function on initial state.
super.initState();
}
body
body:files == null? Text("Searching Files"):
ListView.builder( //if file/folder list is grabbed, then show here
itemCount: files?.length ?? 0,
itemBuilder: (context, index) {
return Card(
child:ListTile(
title: Text(files[index].path.split('/').last),
// trailing: Icon(Icons.play_arrow, color:
Colors.redAccent,),
onTap: (){
}
Use This :
dependencies:
syncfusion_flutter_pdfviewer: ^20.3.59
Example:
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
void main() {
runApp(MaterialApp(
title: 'Syncfusion PDF Viewer Demo',
home: HomePage(),
));
}
/// Represents Homepage for Navigation
class HomePage extends StatefulWidget {
#override
_HomePage createState() => _HomePage();
}
class _HomePage extends State<HomePage> {
final GlobalKey<SfPdfViewerState> _pdfViewerKey = GlobalKey();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.bookmark,
color: Colors.white,
semanticLabel: 'Bookmark',
),
onPressed: () {
_pdfViewerKey.currentState?.openBookmarkView();
},
),
],
),
body: SfPdfViewer.network(
'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf',
key: _pdfViewerKey,
),
);
}
}
From Local Storage :
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Step 2: Mention the PDF file path while creating the SfPdfViewer widget as shown in the following code.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
),
body: SfPdfViewer.file(
File('storage/emulated/0/Download/gis_succinctly.pdf'),
),
);
}
I have a drawer that have a listview that have a nested expansion tiles as its children.
1- I want to close all open expanded tiles exepet the one that just opened.(No more the 1 expanded tile that is open)
What is the best way to do this?
2- i also want to keep the open one stay open when i close and reopen the drawer (I acheved this by using key:PageStorageKey but if there is a better way i would like to hear it).
I solved the problem by using this custom ExpansionTile widget from https://stackoverflow.com/a/58221096/19329222
It have errors (I think because the answer is 3 years old) but you can get rid of them by replacing the incorrect syntax with the correct one from the original ExpansionTile widget file.
This just work, but go through solution and adopt solution with your project design pattern and adopt it with your state management(Provider)
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(new ExpansionTileSample());
}
class ExpansionTileSample extends StatefulWidget {
#override
ExpansionTileSampleState createState() => new ExpansionTileSampleState();
}
class ExpansionTileSampleState extends State {
String foos = 'One';
int _key;
_collapse() {
int newKey;
do {
_key = new Random().nextInt(10000);
} while(newKey == _key);
}
#override
void initState() {
super.initState();
_collapse();
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('ExpansionTile'),
),
body: new ExpansionTile(
key: new Key(_key.toString()),
initiallyExpanded: false,
title: new Text(this.foos),
backgroundColor: Colors.gray,
children: [
new ListTile(
title: const Text('One'),
onTap: () {
setState(() {
this.foos = 'One';
_collapse();
});
},
),
new ListTile(
title: const Text('Two'),
onTap: () {
setState(() {
this.foos = 'Two';
_collapse();
});
},
),
new ListTile(
title: const Text('Three'),
onTap: () {
setState(() {
this.foos = 'Three';
_collapse();
});
},
),
]
),
),
);
}
}
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'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.
// 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"),
));