How to show SnackBar after page transition - flutter

I want to show SnackBar only once when the page is displayed.
But we can not call showSnackBar in build method.
Is there a handler that called after build?

You could use a StatefulWidget and call showSnackBar in the initState of your State. You will need to add a short delay before triggering showSnackBar. Here is a code sample.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.developer_board),
onPressed: () {
Navigator.of(context).push(
new MaterialPageRoute(builder: (_) => new MySecondPage()),
);
},
),
);
}
}
class MySecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Developer Mode'),
),
body: new MySecondPageBody(),
);
}
}
class MySecondPageBody extends StatefulWidget {
#override
State createState() => new MySecondPageBodyState();
}
class MySecondPageBodyState extends State<MySecondPageBody> {
#override
void initState() {
new Future<Null>.delayed(Duration.ZERO, () {
Scaffold.of(context).showSnackBar(
new SnackBar(content: new Text("You made it! Congrats.")),
);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return new Center(
child: new Text('You are now a developer.'),
);
}
}

Using a StatelessWidget and scheduleMicrotask you can achieve it as well
import 'dart:async';
import 'package:flutter/material.dart';
class App extends StatelessWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
App() {
scheduleMicrotask(() => _scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text('Hey!'),
)));
}
#override
Widget build(BuildContext ctx) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('Look mum!'),
),
body: Container());
}
}

You can also try to attach to addPostFrameCallback (https://api.flutter.dev/flutter/scheduler/SchedulerBinding/addPostFrameCallback.html)
SchedulerBinding.instance.addPostFrameCallback((_) {
//show snackbar here
});

#override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) {
// do what you want here
final snackBar = SnackBar(
content: const Text('Yay! A SnackBar!'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
// Some code to undo the change.
},
),
);
// Find the ScaffoldMessenger in the widget tree
// and use it to show a SnackBar.
ScaffoldMessenger.of(context).showSnackBar(snackBar);
});
}

Related

Flutter: How to close SnackBar when navigating away?

I have an issue. I have a "details" page which can display a SnackBar via ScaffoldMessenger.
This snackbar does not hide, its duration is set to long time because it's supposed to stay visible for a long time or until it's dismissed by user or by navigating away from the view.
The last part is the one I'm having issue with. In my dispose method I try to call ScaffoldMessenger.of(_scaffoldKey.currentContext!).hideCurrentSnackBar() but this does not work and throws error that it can't access the context.
I suspect it's because the context associated with the key is also the same context that is being removed from the widget tree since I'm navigating away from it (by using back button).
I do not want to handle removing of the snackbar in other views. I know I could probably call ScaffoldMessenger.of(context).clearAllSnackBars() every time I would navigate to them but I don't like it for architectural reasons:
The "detail" view owns the snackbar because it's responsible for creating it. It should be also responsible for disposing of it.
In future I might reorganize my views and then I have to remember to
clear the snackbar everywhere. The example I gave you is constrained
example but imagine there's accessible sidebar leading to many
different views. It would mean adding this code to all those views.
So I really want to somehow remove that snackbar when disposing of the DetailPage view. How can I achieve this?
Link to dartpad.dev
import 'package:flutter/material.dart';
void main() {
runApp(App());
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: const HomePage(), routes: {
'/detail': (_) => const DetailPage(),
});
}
}
class HomePage extends StatelessWidget {
const HomePage({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: SizedBox.expand(
child: TextButton(
child: const Text('See detail'),
onPressed: () {
Navigator.pushNamed(context, '/detail');
},
),
),
);
}
}
class DetailPage extends StatefulWidget {
const DetailPage({
Key? key,
}) : super(key: key);
#override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) {
ScaffoldMessenger.of(_scaffoldKey.currentContext!).showSnackBar(SnackBar(
content: const Text('Entered detail page'),
duration: const Duration(days: 1),
action: SnackBarAction(
label: 'Close',
onPressed: () {
ScaffoldMessenger.of(_scaffoldKey.currentContext!)
.hideCurrentSnackBar();
}),
));
});
}
#override
void dispose() {
ScaffoldMessenger.of(_scaffoldKey.currentContext!).hideCurrentSnackBar();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(title: const Text('Detail')),
body: const SizedBox.expand(
child: Center(child: Text('Detail page')),
),
);
}
}
Use WillPopScope widget to remove the snackbar.
This widget allows async code to run before the view is popped of the navigation stack and the context is still present in the widget tree at that moment. You can get rid of the overriden dispose method this way.
You can see it working in this dartpad or just note the code below:
import 'package:flutter/material.dart';
void main() {
runApp(App());
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: const HomePage(), routes: {
'/detail': (_) => const DetailPage(),
});
}
}
class HomePage extends StatelessWidget {
const HomePage({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: SizedBox.expand(
child: TextButton(
child: const Text('See detail'),
onPressed: () {
Navigator.pushNamed(context, '/detail');
},
),
),
);
}
}
class DetailPage extends StatefulWidget {
const DetailPage({
Key? key,
}) : super(key: key);
#override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) {
ScaffoldMessenger.of(_scaffoldKey.currentContext!).showSnackBar(SnackBar(
content: const Text('Entered detail page'),
duration: const Duration(days: 1),
action: SnackBarAction(
label: 'Close',
onPressed: () {
ScaffoldMessenger.of(_scaffoldKey.currentContext!)
.hideCurrentSnackBar();
}),
));
});
}
#override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(title: const Text('Detail')),
body: const SizedBox.expand(
child: Center(child: Text('Detail page')),
),
),
onWillPop: () async {
ScaffoldMessenger.of(_scaffoldKey.currentContext!).hideCurrentSnackBar();
return Future.value(true);
});
}
}
I think the only downside is that hideCurrentSnackBar does not complete with Future so the animation does not finish. Maybe there'd be a way to do it with some sort of Completer.
try{ ScaffoldMessenger.of(context).show()/// show snackbar
}catch(e){
print(e);
}
putting scaffold messenger inside a try catch prevent disposed context usage error

How to call function from another file

I have two different .dart file.
sidebar file, there is a function in this class to something
class SideBar extends StatefulWidget {
#override
_SideBarState createState() => _SideBarState();
}
class _SideBarState extends State<SideBar>
with SingleTickerProviderStateMixin<SideBar> {
void onIconPressed() {
print('123');
}
}
homescreen file;
class HomeScreen extends StatelessWidget {
static String routeName = "/HomeScreen";
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
appBar: AppBar(
leading: Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.menu),
onPressed: (){ }, ///////////////////////where i try to implement function
I want the call this function in another file. What i tried import sidebar.dart file as sidebar. Then call function like sidebar.onIconPressed() But nothing work. I looked widget communication thing but couldnt get it. Is there any easy way to solve this problem.
Edit: The reason why my solution not work because I acces the void which has a setstate. Thats why I always get null message
class SideBar extends StatefulWidget {
#override
_SideBarState createState() => _SideBarState();
}
class _SideBarState extends State<SideBar>
with SingleTickerProviderStateMixin<SideBar> {
#override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.menu),
onPressed: (){
onIconPressed();
},
void onIconPressed() {
print('123');
}
}
class HomeScreen extends StatelessWidget {
static String routeName = "/HomeScreen";
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
appBar: AppBar(
leading: Builder(
builder: (BuildContext context) {
return SideBar(....
Create an object for your class then you would be able to call the function through the object.
//define the object inside HomeScreen class
final SideBar sidebar = SideBar();
then in onPressed call the function sidebar. onIconPressed();
class SideBar extends StatefulWidget {
#override
_SideBarState createState() => _SideBarState();
void onIconPressed() {
print('123');
}
}
class _SideBarState extends State<SideBar>
with SingleTickerProviderStateMixin<SideBar> {
#override
Widget build(BuildContext context) {
return Container();
}
}
class HomeScreen extends StatelessWidget {
static String routeName = "/HomeScreen";
SideBar s = new SideBar();
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
appBar: AppBar(leading: Builder(builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.menu),
onPressed: () {
s.onIconPressed();
});
}))));
}
}

flutter: child widget not rebuilt after parent rebuild

Version:
Flutter-Version: 1.12.14 channel dev
Dart-Version: 2.7.0
Question:
I wan write a Todo App. when i click floatbutton add a new Todo, but in some cases its not work well.
The problem in Scaffold.body, detials in code.
it work well when i use TodoPage(todoList: _todoList).
_pageList.elementAt(_activeIndex) is not work when i submit textfield .
I found the print('Build Home')print after submit but print('Build TodoPage') not print.
why???
My Code:
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget{
#override
Widget build(BuildContext context){
return MaterialApp(
title: 'TodoList',
home: Home(),
);
}
}
class Home extends StatefulWidget{
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home>{
List<String> _todoList = ['a', 'b', 'c'];
TextEditingController _controller;
List<Widget> _pageList;
int _activeIndex;
Widget _curPage;
#override
void initState(){
super.initState();
_activeIndex = 0;
_pageList = [TodoPage(todoList: _todoList,), OtherPage()];
_curPage = _pageList[_activeIndex];
_controller = TextEditingController();
}
#override
Widget build(BuildContext context){
print('build Home');
return Scaffold(
appBar: AppBar(title: Text('Todo'),),
body: _pageList.elementAt(_activeIndex), // this is not work
// body: TodoPage(todoList: _todoList,), // this is work well
floatingActionButton: FloatingActionButton(
onPressed: _openDlg,
child: Icon(Icons.add),
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.list), title: Text('Todo')),
BottomNavigationBarItem(icon: Icon(Icons.favorite), title: Text('Other')),
],
currentIndex: _activeIndex,
selectedItemColor: Colors.blue,
onTap: _onMenuTap,
),
);
}
_onMenuTap(int index){
setState(() {
_activeIndex = index;
});
}
_openDlg(){
showDialog(
context: context,
builder: (BuildContext context){
return SimpleDialog(
children: <Widget>[
TextField(
controller: _controller,
),
SimpleDialogOption(
child: FloatingActionButton(child: Text('submit'), onPressed: _addTodo,),
)
],
);
}
);
}
_addTodo(){
print(_controller.text);
setState(() {
_todoList.add(_controller.text);
});
}
}
class TodoPage extends StatefulWidget{
TodoPage({Key key, this.todoList}): super(key: key);
List<String> todoList;
_TodoPageState createState() => _TodoPageState();
}
class _TodoPageState extends State<TodoPage>{
#override
void initState(){
super.initState();
}
#override
Widget build(BuildContext context){
print('build TodoPage');
return Column(
children: _buildTodoList(),
);
}
List <Widget> _buildTodoList(){
return widget.todoList.map((todo){
return Text(todo, style: TextStyle(fontSize: 30),);
}).toList();
}
}
class OtherPage extends StatelessWidget{
#override
Widget build(BuildContext context){
return Center(child: Text('Other Page'));
}
}
That is logical.
You are reusing an existing instance of a Widget, and widgets are immutable.
As such, the framework notice that the instance of the widget did not change and doesn't call build to optimize performances.
Your problem being, you violated the rule of widgets being immutable, which makes this optimization break your app.
What you did:
class MyState extends State<MyStatefulWidget> {
SomeWidget myWidget = SomeWidget()..someProperty = "initial value";
void onSomething() {
setState(() {
myWidget.someProperty = "new value";
});
}
#override
Widget build(BuildContext context) {
return myWidget;
}
}
What you should instead do:
class MyState extends State<MyStatefulWidget> {
SomeWidget myWidget = SomeWidget(someProperty: "initial value");
void onSomething() {
setState(() {
myWidget = SomeWidget(someProperty: "new value");
});
}
#override
Widget build(BuildContext context) {
return myWidget;
}
}
Alternatively, just don't cache the widget instance at all.

how to edit value at the child widget

i try to edit the value of the child widget, i can do it with StatefulWidget parent but i want to do it with StatelessWidget parent and without using global value
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(child: Text('addFile'), onPressed: () {}),
FlatButton(child: Text('deleteFile'), onPressed: () {})
],
),
body: Child(),
);
}
}
class Child extends StatefulWidget {
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
var hasFile = true;
#override
Widget build(BuildContext context) {
return hasFile ? Text('has a file') : Text("no File");
}
}
You are thinking the wrong way. Child aka Text() should get its value from a model which is managed by the application or at least managed by the widget above. I would go with the provider package https://pub.dev/packages/provider and do this:
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
class MyState with ChangeNotifier {
String _myText;
MyState(this._myText);
getMyText() => _myText;
void changeText(String newText) {
_myText = newText;
notifyListeners();
}
}
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => MyState("initial Text")),
],
child: Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(
child: Text('addFile'),
onPressed: () {
Provider.of<MyState>(context).changeText("addFile");
}),
FlatButton(
child: Text('deleteFile'),
onPressed: () {
Provider.of<MyState>(context).changeText("deleteFile");
})
],
),
body: Child(),
));
}
}
class Child extends StatelessWidget {
#override
Widget build(BuildContext context) {
MyState myState = Provider.of<MyState>(context);
return Text(myState.getMyText());
}
}
This is coded without IDE support or even compiling and running. But it should get you to the right direction.
You can use BLoC pattern to implement this kind of functionality,
Here is the BLoC class which will handle state of bool
import 'dart:async';
class Bloc {
final _fileController = StreamController<bool>();
changeState(bool val) {
_fileController.sink.add(val);
}
get hasFile => _fileController.stream;
dispose() {
_fileController.close();
}
}
final bloc = Bloc();
Then you can add stream builder in your Stateful Widget, in which you will provide stream of BLoC class.
StreamBuilder updates it's UI according to Stream.
class Child extends StatefulWidget {
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
var hasFile = true;
#override
void dispose() {
bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.hasFile,
initialData: false,
builder: (context, snapshot) {
return snapshot.data ? Text('has a file') : Text("no File");
},
);
}
}
At last you can access BLoC class with your stateless widget as follows
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(
child: Text('addFile'),
onPressed: () {
bloc.changeState(true);
}),
FlatButton(
child: Text('deleteFile'),
onPressed: () {
bloc.changeState(false);
})
],
),
body: Child(),
);
}
}
Full example is as below
import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Homepage(),
);
}
}
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(
child: Text('addFile'),
onPressed: () {
bloc.changeState(true);
}),
FlatButton(
child: Text('deleteFile'),
onPressed: () {
bloc.changeState(false);
})
],
),
body: Child(),
);
}
}
class Child extends StatefulWidget {
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
var hasFile = true;
#override
void dispose() {
bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.hasFile,
initialData: false,
builder: (context, snapshot) {
return snapshot.data ? Text('has a file') : Text("no File");
},
);
}
}
class Bloc {
final _fileController = StreamController<bool>();
changeState(bool val) {
_fileController.sink.add(val);
}
get hasFile => _fileController.stream;
dispose() {
_fileController.close();
}
}
final bloc = Bloc();

App exits without calling any callbacks on back press on device, flutter

App Entry Point:
void main() {
runWhat();}
void runWhat() async{
//getLoggedInSharedPrefs() gets logged in state from SharedPrefs
await getLoggedInSharedPrefs().then((isLoggedIn) {
if(isLoggedIn) {
runApp(Home()); // User is Logged in go to Home;
} else {
runApp(new MyApp()); // Login Screen - separate from Home
}
});
}
In Home, I want to alert User on pressing back and alert if they want to exit out of app. But neither _onWillPop nor dispose get called
Home is a separate screen from MyApp and is not the body of MyApp
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
HomeState homeState() => new HomeState();
return homeState();
}
}
class HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onWillPop,
child: new MaterialApp(.....
#override
void dispose() {
print('dispose: $this');
super.dispose();
}
Future<bool> _onWillPop() {
print("Poppoing Home on will popo");
return showDialog(
context: context,
builder: (context) => new AlertDialog(
title: new Text('Home - Are you sure?'),
content: new Text('Do you want to exit'),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.pop(context),
child: new Text('No'),
),
new FlatButton(
onPressed: () => exit(0),
child: new Text('Yes'),
),
],
),
) ??
false;
}
... }
You need to rearrange how you've set up your app as in your WillPopScope should be within MaterialApp and Scaffold:
App Class
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: Scaffold(
body: HomePage(),
),
);
}
}
Your Page
import 'dart:async';
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: _onWillPop,
child:new Center(
child: new Text("Home Page"),
),
);
}
Future<bool> _onWillPop() {
return showDialog(
context: context,
builder: (context) => new AlertDialog(
title: new Text('Are you sure?'),
content: new Text('Do you want to exit an App'),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.of(context).pop(false),
child: new Text('No'),
),
new FlatButton(
onPressed: () => Navigator.of(context).pop(true),
child: new Text('Yes'),
),
],
),
) ??
false;
}
}
Taking hint from #SnakeyHips I modified my code as below but I needed Scaffold to be stateful for tab navigation
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(fontFamily: 'Georgia'),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
....
#override
Widget build(BuildContext context) {
return Scaffold(body: new WillPopScope(
onWillPop: _onWillPop,
....
}
}