Flutter Access parent Scaffold from different dart file - dart

I have this:
final GlobalKey<ScaffoldState> _scaffoldkey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
key: _scaffoldkey,
drawer: Menu(),
appBar: AppBar(
title: Container(
child: Text('Dashboard'),
),
bottom: TabBar(
tabs: <Widget>[
...
],
),
),
body: TabBarView(
children: <Widget>[
...
],
),
),
);
}
}
Now, the drawer: Menu() is imported from another menu.dart file, which looks like this:
class Menu extends StatelessWidget {
final GlobalKey<ScaffoldState> drawerKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return new Drawer(
key: drawerKey,
child: new ListView(
children: <Widget>[
new ListTile(
dense: true,
title: new Text('My Text'),
onTap: () {
// On tap this, I want to show a snackbar.
scaffoldKey.currentState.showSnackBar(showSnack('Error. Could not log out'));
},
),
],
),
);
}
}
With the above approach, I get
NoSuchMethodError: The method 'showSnackBar' was called on null.
An easy solution is to tuck the entire menu.dart contents in the drawer: ... directly.
Another way I'm looking at is being able to reference the parent scaffold in order to display the snackbar.
How can one achieve that?
Why can't one even just call the snackbar from anywhere in Flutter and compulsorily it has to be done via the Scaffold? Just why?

You should try to avoid using GlobalKey as much as possible; you're almost always better off using Scaffold.of to get the ScaffoldState. Since your menu is below the scaffold in the widget tree, Scaffold.of(context) will do what you want.
The reason what you're attempting to do doesn't work is that you are creating two seperate GlobalKeys - each of which is its own object. Think of them as global pointers - since you're creating two different ones, they point to different things. And the state should really be failing analysis since you're passing the wrong type into your Drawer's key field...
If you absolutely have to use GlobalKeys for some reason, you would be better off passing the instance created in your outer widget into your Menu class as a member i.e. this.scaffoldKey, but this isn't recommended.
Using Scaffold.of, this is what your code would look like in the onTap function:
onTap: () {
// On tap this, I want to show a snackbar.
Scaffold.of(context).showSnackBar(showSnack('Error. Could not log out'));
},

You can achieve this functionality by using builder widget you don't need to make separate GlobalKey or pass key as a parameter. Just wrap a widget to Builder widget
class CustomDrawer extends StatelessWidget {#override Widget build(BuildContext context) {
return new Drawer(
child: new ListView(
children: <Widget>[
new Builder(builder: (BuildContext innerContext) {
return ListTile(
dense: true,
title: new Text('My Text'),
onTap: () {
Navigator.of(context).pop();
Scaffold.of(innerContext).showSnackBar(SnackBar(
content: Text('Added added into cart'),
duration: Duration(seconds: 2),
action: SnackBarAction(label: 'UNDO', onPressed: () {}),
));
}
);
})
],
),
);}}

From your first question
In other to reference the parent scaffold in the menu widget you can pass the _scaffoldkey to the menu widget as parameter and use ScaffoldMessenger.of() to show snackbar as shown below
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// Root Widget
#override
Widget build(BuildContext context) {
return MaterialApp(
// App name
title: 'Flutter SnackBar',
// Theme
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Test(title: 'SnackBar'),
);
}
}
class Test extends StatefulWidget {
final String? title;
final GlobalKey<ScaffoldState> _scaffoldkey = new GlobalKey<ScaffoldState>();
Test({#required this.title});
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
key: widget._scaffoldkey,
drawer: Menu(parentScaffoldkey:widget._scaffoldkey),
appBar: AppBar(
title: Container(
child: Text('Dashboard'),
),
bottom: TabBar(
tabs: <Widget>[
Tab(text:"Home"),
Tab(text:"About")
],
),
),
body: TabBarView(
children: <Widget>[
Text("Home"),
Text("About")
],
),
),
);
}
}
Menu part as shown
class Menu extends StatelessWidget {
final parentScaffoldkey;
Menu({this.parentScaffoldkey});
#override
Widget build(BuildContext context) {
return new Drawer(
child: new ListView(
children: <Widget>[
new ListTile(
dense: true,
title: new Text('My Text'),
onTap: () {
// On tap show a snackbar.
// ScaffoldMessenger will call the nearest Scaffold to show snackbar
ScaffoldMessenger.of(this.parentScaffoldkey.currentContext).showSnackBar(SnackBar(content:Text('Error. Could not log out')));
},
),
],
),
);
}
}
Also,you have to call snackbar via Scaffold because it provides the SnackBar API and manages it

Related

How to change background color of Scaffold widget programatically for whole application

I am new to the flutter application development and got stuck in a problem.My application contains near about 5-6 screens and all the screens contains the scaffold widget like this.
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF332F43)
);
}
Now on all the screens i have the same concept and design like this and all the screens will share same backGround color.Now i have a button in all screens i.e. Change Theme button and on the button click of that Change Theme button i want to change all the screens Scaffold widget to be changed.Now how can i achieve this ? Please help me in my problem.
Flutter has predefined way to change background color of scaffold across app.
Just change it in MaterialApp Widget inside of your main.dart (main file).
MaterialApp(
title: 'Flutter',
theme: ThemeData(
scaffoldBackgroundColor: const Color(0xFF332F43),
),
);
Color color = Colors.blue; // make it at root level
void main() {
runApp(MaterialApp(home: Page1()));
}
In your page1 class, import above file.
class Page1 extends StatefulWidget {
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: color,
appBar: AppBar(title: Text("Page 1")),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (c) => Page2())),
child: Text("Go to Page 2"),
),
RaisedButton(
child: Text("Change color"),
onPressed: () => setState(() => color = Colors.red),
),
],
),
),
);
}
}
In your page2 class, import first file.
class Page2 extends StatefulWidget {
#override
_Page2State createState() => _Page2State();
}
class _Page2State extends State<Page2> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: color,
appBar: AppBar(title: Text("Page 2")),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () => Navigator.pop(context),
child: Text("Back"),
),
RaisedButton(
child: Text("Change color"),
onPressed: () => setState(() => color = Colors.green),
),
],
),
),
);
}
}

Update body through default template class

Anyone here know Flutter/Dart
i have a defaultLayout which looks a little bit like this
final title = Data.appTitle;
var pages = [HomeScreen(), SearchPage(), LivePage(), AccountPage()];
String _currentRoute;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: childViewBody(), //each class that extends Default() can modify this method to change body
//...Other Layout. BotAppBar is a custom widget
bottomNavigationBar: BotAppBar(
onTap: (index) {
setState(() {
//HERE... How can I make that whenever the BotAppBarItems are selected,.. the body is updated
});
},
items: [
BotAppBarItem(iconData: Icons.home, tooltip: Data.homeTitle),
BotAppBarItem(iconData: Icons.search, tooltip: Data.searchTitle),
BotAppBarItem(iconData: Icons.near_me, tooltip: Data.liveTitle),
BotAppBarItem(iconData: Icons.account_circle, tooltip: Data.accountTitle),
],
),
);
}
//Child class will modify this area to update screen.
childViewBody() {
return Column(
children: <Widget>[
Text('Placeholder')
],
);
}
... And this should update for any class that's extended it
for example
class HomePage extends Default {//...super}
class HomePageState extends DefaultState {
#override
String get title => Data.homeTitle; //updates title in parent class
childViewBody() {
return Column{
//Build HomeScreen Body here
}
}
}
Diagram.png
so how would i modify Default class that whenever the BotAppBar is selected, the body is updated... even if the current class loaded is a child class
You can use HeroAnimations
to open a new window over your previous window as this is how SearchWindows should be displayed to the user.
Here's a tutorial to implement it.
If HeroAnimations sounds bit too much,
use Navigation
Directions:
Create two screens
Navigate to the second screen using Navigator.push
Return to the first screen using Navigator.pop
Example
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: FirstScreen(),
));
}
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Launch screen'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}

Flutter: How to display a snackbar from an appbar action

I'm trying to display a SnackBar after performing an action from the AppBar.
The AppBar cannot be built from a builder so it can't access is Scaffold ancestor.
I know we can use a GlobalKey object to access the context whenever we want, but I would like to know if there is a solution without using the GlobalKey.
I found some github issues and pull-request, but I can't find a solution from them
=> https://github.com/flutter/flutter/issues/4581 and https://github.com/flutter/flutter/pull/9380
Some more context:
I have an Appbar with a PopupMenuButton, which have one item. When the user click on this item I display a dialog which the showDialog method and if the user clicks on "ok" I want to display a SnackBar
You can use the Builder widget
Example:
Scaffold(
appBar: AppBar(
actions: <Widget>[
Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.message),
onPressed: () {
final snackBar = SnackBar(content: Text('Yay! A SnackBar!'));
Scaffold.of(context).showSnackBar(snackBar);
},
);
},
),
],
)
);
The Scaffold.appBar parameter requires a PreferredSizeWidget, so you can have a Builder there like this:
appBar: PreferredSize(
preferredSize: Size.fromHeight(56),
child: Builder(
builder: (context) => AppBar(...),
),
),
An option is to use two contexts in the dialog and use the context passed to the dialog to search for the Scaffold.
When you show a dialog, you are displaying a completely different page/route which is outside the scope of the calling page. So no scaffold is available.
Below you have a working example where you use the scope of the first page.
The problem, though, is that the SnackBar is not removed.
If instead you use a GlobalKey to get the Scaffold the problem is the same.
I would consider not using a Snackbar in this case, because it is associated to the page below. It is even greyed out by the dialog shadow.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
_showDialog(BuildContext context1) {
return showDialog(
context: context1,
builder: (BuildContext context) {
return AlertDialog(
content: Text("Dialog"),
actions: <Widget>[
new FlatButton(
child: new Text("OK"),
onPressed: () => Scaffold.of(context1).showSnackBar(SnackBar(
content: Text("Pressed"),
)),
),
],
);
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Test"),
actions: <Widget>[
PopupMenuButton(
itemBuilder: (BuildContext context) {
return <PopupMenuEntry>[
PopupMenuItem(
child: ListTile(
title: Text('Show dialog'),
onTap: () => _showDialog(context),
),
),
];
},
)
],
),
);
}
}

How can I render only one Widget setState

I have two easy basics example one is Works and second need some of explaining.
So, When I Clik Button in Example one it Works and setStaet rendering the page.
Second Example I using a body function that has setState of Button. When I click button nothing happen even with using setState notes if
the variable inside the parenthesis it does not render.
When I make Variable instance in class it works fine but I need to rendering Only One Button how?
Full code Example One
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(
home: Home(),
));
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool istrue = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Works fine'),),
body: Center(child: body()),
);
}
Widget body() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () {
setState(() {
istrue = !istrue;
});
},
child: Text('Click me'),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(istrue ? 'Boo!' : ''),
),
],
);
}
}
Full code Example two
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(
home: Home(),
));
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool istrue = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Works fine'),),
body: Center(child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
body(),body(),
],
)),
);
}
Widget body() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () {
setState(() {
istrue = !istrue;
});
},
child: Text('Click me'),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(istrue ? 'Boo!' : ''),
),
],
);
}
}
Thanks as sky.
I am having a bit of trouble understanding your question, but I also understand that English is not your first language. You seem to be asking, "How can I render only one button for example #2?". Please let me know if this is not your question so that I, and others, can help you.
This will only render one button,
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(
home: Home(),
));
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool istrue = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Works fine'),),
body: Center(child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () {
setState(() {
istrue = !istrue;
});
},
child: Text('Click me'),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(istrue ? 'Boo!' : ''),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(istrue ? 'Boo!' : ''),
),
],
)),
);
}
}
You are calling a Column widget within another Column widget in the Scaffold widget in your second example. Why would you do that? There are many ways to structure that layout in the flutter API.
You can't have a new Column(new Column(...)); Its redundant.
Edit:
I pulled this from the flutter website at Column class:
The Column widget does not scroll (and in general it is considered an error to have more children in a Column than will fit in the available room). If you have a line of widgets and want them to be able to scroll if there is insufficient room, consider using a ListView.

What is the right way to navigate using a drawer and change only the content of the body?

I'm creating a basic Material App and i have a drawer for navigation.
With the simple way of pushing a route the whole widget is getting replaced and it's like opening a whole new page that includes a whole new drawer.
My goal is to create the atmosphere of a page and a drawer, and when the user taps a drawer item the drawer will collapse and only the content of the page will be replaced.
I have found these two Questions/Answers:
Replace initial Route in MaterialApp without animation?
Flutter Drawer Widget - change Scaffold.body content
And my question is what is the best/correct way to achieve what i'm trying to do?
The 1st method is just creating the illusion by removing the push/pop animation, although it still actually behaves like the original method i described.
The 2nd method actually replaces the content only, and the solution i thought of is instead of changing the text to create multiple Container widgets and changing between them.
Since i'm still new and learning flutter i would like to know what is the right practice to do so.
EDIT:
I created this and it works pretty well. I still don't know about how effective/efficient it is but for now that's exactly what i wanted to achieve:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blueGrey,
),
home: new TestPage(),
);
}
}
class TestPage extends StatefulWidget {
#override
_TestPageState createState() => new _TestPageState();
}
class _TestPageState extends State<TestPage> {
static final Container info = new Container(
child: new Center(
child: new Text('Info')
),
);
static final Container save = new Container(
child: new Center(
child: new Text('Save')
),
);
static final Container settings = new Container(
child: new Center(
child: new Text('Settings')
),
);
Container activeContainer = info;
#override
Widget build(BuildContext context) {
return new Scaffold(
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new Container(child: new DrawerHeader(child: new Container())),
new Container (
child: new Column(
children: <Widget>[
new ListTile(leading: new Icon(Icons.info), title: new Text('Info'),
onTap:(){
setState((){
activeContainer = info;
});
Navigator.of(context).pop();
}
),
new ListTile(leading: new Icon(Icons.save), title: new Text('Save'),
onTap:(){
setState((){
activeContainer = save;
});
Navigator.of(context).pop();
}
),
new ListTile(leading: new Icon(Icons.settings), title: new Text('Settings'),
onTap:(){
setState((){
activeContainer = settings;
});
Navigator.of(context).pop();
}
),
]
),
)
],
),
),
appBar: new AppBar(title: new Text("Test Page"),),
body: activeContainer,
);
}
}
Is not what you are trying to achieve is exactly what in the second answer, No ? :/
Here I am illustrating it by switching colors, but you can apply this to the content itself or basically any other widget.
class NavDrawer extends StatefulWidget {
#override
_NavDrawerState createState() => new _NavDrawerState();
}
class _NavDrawerState extends State<NavDrawer> {
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
Color contentColor = Colors.white;
#override
initState(){
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: scaffoldKey,
appBar: new AppBar(),
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new DrawerHeader(child: new Container()),
new ListTile(title: new Text("Blue"),onTap: (){setState((){contentColor=Colors.blue;Navigator.pop(context);});},),
new ListTile(title: new Text("Red"),onTap: (){setState((){contentColor=Colors.red;Navigator.pop(context);});},),
new ListTile(title: new Text("Green"),onTap: (){setState((){contentColor=Colors.green;Navigator.pop(context);});},),
new ListTile(title: new Text("Yellow"),onTap: (){setState((){contentColor=Colors.yellow;Navigator.pop(context);});},)
],
),
),
body: new Container(
color: contentColor,
),
);
}
}

Resources