flutter: GlobalKey error when using Drawer - flutter

I`m new to flutter.
After login, the screen goes dark and the following error occurs when moving to the my HomePage. How can I solve this problem?
The following assertion was thrown while finalizing the widget tree:
Multiple widgets used the same GlobalKey.
The key [LabeledGlobalKey#c6bc9] was used by multiple widgets. The parents of those widgets were:
HomePage(state: _HomePageState#8437e)
HomePage(state: _HomePageState#f49a2)
A GlobalKey can only be specified on one widget at a time in the widget tree.
This is my HomePage
class HomePage extends StatefulWidget {
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String uid = FirebaseAuth.instance.currentUser!.uid;
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _willPopCallback,
child: Scaffold(
key: _scaffoldKey,
endDrawer: Drawer(
child: ListView(
children: [
...
],
),
),
appBar: AppBar(
automaticallyImplyLeading: false,
actions: [
IconButton(
onPressed: () {
showProfileDialog(context, uid: uid);
},
icon: const Icon(Icons.account_circle, size: 30)),
IconButton(
onPressed: () {
_scaffoldKey.currentState!.openEndDrawer();
},
icon: const Icon(
Icons.dehaze_sharp,
size: 30,
),
),
const SizedBox(width: 10)
],
),
body: Center(
child:
...
),
),
),
);
}
}
Future<bool> _willPopCallback() async {
return true;
}
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

you can access to key
Scaffold.of(context).openDrawer();
and this will work without create GlobalKey

It means that the key still holds the state of the old widget. Try resetting the global key in dispose of HomePageState class.

Related

How to show next page (Stateless widget) on click only in specific Container in SplitView, not all over the page

I have TestApp, where I have SplitView with 2 horizontal Containers. By clicking button in the first container on the left(blue) I want to show new page (DetailPage widget) but not all over the page, but only in the first Container. Now it shows on the whole screen. What is a best approach to do it?
import 'package:flutter/material.dart';
import 'package:split_view/split_view.dart';
void main() {
runApp(MaterialApp(
title: 'Test',
home: TestApp(),
));
}
class TestApp extends StatelessWidget {
const TestApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SplitView(
children: [
Container(
color: Colors.blue,
child: ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => DetailPage()));
},
child: const Text('CLICK')),
),
Container(color: Colors.yellow),
],
viewMode: SplitViewMode.Horizontal,
indicator: SplitIndicator(viewMode: SplitViewMode.Horizontal),
activeIndicator: SplitIndicator(
viewMode: SplitViewMode.Horizontal,
isActive: true,
),
controller: SplitViewController(limits: [null, WeightLimit(max: 1)]),
),
);
}
}
class DetailPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('')), body: Container(color: Colors.red));
}
}
When pushing a new page you will be overriding the old one, meaning the new page will not have a spiltView, the best way to do this is by changing the widget displayed inside of the splitView like this :
import 'package:flutter/material.dart';
import 'package:split_view/split_view.dart';
void main() {
runApp(MaterialApp(
title: 'Test',
home: TestApp(),
));
}
class TestApp extends StatefulWidget { // I have already changed the widgte to stateful here
const TestApp({Key? key}) : super(key: key);
#override
_TestAppState createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
#override
Widget build(BuildContext context) {
bool Bool;
return MaterialApp(
home: SplitView(
children: [
if (Bool == false){
Container(
color: Colors.blue,
child: ElevatedButton(
onPressed: () {
setState(() {
Bool = !Bool; // this the method for inverting the boolean, it just gives it the opposite value
});
},
child: const Text('CLICK')),
),
}
else{
DetailPage()
},
Container(color: Colors.yellow),
],
viewMode: SplitViewMode.Horizontal,
indicator: SplitIndicator(viewMode: SplitViewMode.Horizontal),
activeIndicator: SplitIndicator(
viewMode: SplitViewMode.Horizontal,
isActive: true,
),
controller: SplitViewController(limits: [null, WeightLimit(max: 1)]),
),
);
}
}
class DetailPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('')), body: Container(color: Colors.red));
}
}
Above I defined a bool called Bool, when rendering the page it checks if Bool is false, in that case it returns the blue widget, if it is true then it returns the red one, and when you click on the button it inverts the bool and updates the page.
Please note that for updating the page you have to use setState which rebuilds the widget, and to use it you have to use a stateful widget since stateless widget is static and cannot be changed.
Also I haven't tested the code because I don't have split_view package, but you should be able to copy and paste it just fine, if you get any errors please let me know.
When you use Navigator.push your routing to a new page and creating a new state. I think you should use showGeneralDialog instead.
showGeneralDialog(
context: context,
pageBuilder: (BuildContext context,
Animation<double> animation, Animation<double> pagebuilder) {
return Align(
alignment: Alignment.centerLeft,
child: Card(
child: Container(
alignment: Alignment.topLeft,
color: Colors.amber,
//show half the screen width
width: MediaQuery.of(context).size.width / 2,
child: IconButton(
icon: const Icon(Icons.cancel),
onPressed: () {
Navigator.pop(context);
}))),
);
});
try to create new Navigator within Container:
GlobalKey<NavigatorState> _navKey = GlobalKey();
home: SplitView(
children: [
Container(
child: Navigator(
key: _navKey,
onGenerateRoute: (_) => MaterialPageRoute<dynamic>(
builder: (_) {
return Container(
color: Colors.blue,
child: ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => DetailPage()));
},
child: const Text('CLICK')),
);
},
),
),),

Getting an exception when I press my FlotaingButton or AppBar IconButton. I already Add a Scffold in _MyAppHomePage

Exception has occurred.
FlutterError (No Scaffold widget found.
MyHomePage widgets require a Scaffold widget ancestor.
The specific widget that could not find a Scaffold ancestor was:
MyHomePage
The ancestors of this widget were:
Semantics
Builder
RepaintBoundary-[GlobalKey#5c844]
IgnorePointer
AnimatedBuilder
FadeTransition
FractionalTranslation
SlideTransition
_FadeUpwardsPageTransition
AnimatedBuilder
RepaintBoundary
FocusTrap
_FocusMarker
Semantics
FocusScope
PrimaryScrollController
_ActionsMarker
Actions
Builder
PageStorage
Offstage
_ModalScopeStatus
UnmanagedRestorationScope
RestorationScope
AnimatedBuilder
_ModalScope-[LabeledGlobalKey<_ModalScopeState>#c4e87]
Semantics
_EffectiveTickerMode
TickerMode
_OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#becc1]
_Theatre
Overlay-[LabeledGlobalKey#3a815]
UnmanagedRestorationScope
_FocusMarker
Semantics
FocusScope
AbsorbPointer
Listener
HeroControllerScope
Navigator-[GlobalObjectKey _WidgetsAppState#0ccea]
IconTheme
IconTheme
_InheritedCupertinoTheme
CupertinoTheme
_InheritedTheme
Theme
AnimatedTheme
_ScaffoldMessengerScope
ScaffoldMessenger
Builder
DefaultTextStyle
CustomPaint
Banner
CheckedModeBanner
Title
Directionality
_LocalizationsScope-[GlobalKey#ed058]
Semantics
Localizations
MediaQuery
_MediaQueryFromWindow
_FocusMarker
Focus
_FocusTraversalGroupMarker
FocusTraversalGroup
_ActionsMarker
DefaultTextEditingActions
_ActionsMarker
Actions
_ShortcutsMarker
Semantics
_FocusMarker
Focus
DefaultTextEditingShortcuts
_ShortcutsMarker
Semantics
_FocusMarker
Focus
Shortcuts
UnmanagedRestorationScope
RestorationScope
UnmanagedRestorationScope
RootRestorationScope
WidgetsApp-[GlobalObjectKey _MaterialAppState#d0f4a]
Semantics
_FocusMarker
Focus
HeroControllerScope
ScrollConfiguration
MaterialApp
MyApp
[root]
Typically, the Scaffold widget is introduced by the MaterialApp or WidgetsApp widget at the top of your application widget tree.)
getting this exception when I click my app FlotaingButton or AppButton.
import 'package:flutter/material.dart';
import './widgets/new_transaction.dart';
import './widgets/transaction_list.dart';
import './models/transaction.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<Transaction> _userTransactions = [
Transaction(
id: 't1',
title: 'New Shoes',
amount: 60.32,
date: DateTime.now(),
),
Transaction(
id: 't2',
title: ' Weekly Groceriese',
amount: 10.22,
date: DateTime.now(),
),
];
void _addNewTransaction(String txTitle, double txAmount) {
final newTx = Transaction(
title: txTitle,
amount: txAmount,
date: DateTime.now(),
id: DateTime.now().toString(),
);
setState(() {
_userTransactions.add(newTx);
});
}
void _startNewTransaction(BuildContext ctx) {
showBottomSheet(
context: ctx,
builder: (_) {
return GestureDetector(
onTap: () {},
child: NewTransaction(_addNewTransaction),
behavior: HitTestBehavior.opaque,
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter App'),
actions: <Widget>[
IconButton(
onPressed: () => _startNewTransaction(context),
icon: Icon(Icons.add),
),
],
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
width: double.infinity,
child: Card(
color: Colors.blue,
elevation: 5,
child: Text('Chart !'),
),
),
TransactionList(_userTransactions),
],
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
onPressed: () => _startNewTransaction(context),
child: Icon(Icons.add),
),
);
}
}
I also tried this way by using Scaffold.of(ctx).showBottomSheet but its giving me some error which I don't understand
Try: showModalBottomSheet instead.
Or if You wish to use showBottomSheet then this is a workaround.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
home: Scaffold(body: MyHomePage()),
);
}
}
class MyHomePage extends StatefulWidget {
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void _startNewTransaction(BuildContext ctx) {
showBottomSheet(
context: ctx,
builder: (_) {
return GestureDetector(
onTap: () {},
child: Container(),
behavior: HitTestBehavior.opaque,
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter App'),
actions: <Widget>[
IconButton(
onPressed: () => _startNewTransaction(context),
icon: Icon(Icons.add),
),
],
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
width: double.infinity,
child: Card(
color: Colors.blue,
elevation: 5,
child: Text('Chart !'),
),
),
],
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
onPressed: () => _startNewTransaction(context),
child: Icon(Icons.add),
),
);
}
}

setState does not update GridView in flutter

I have read the other questions and while they do have similar titles to this one, they don't cover my issue. Please read below.
I'm using Android Studio. I have a simple stateful app with a GridView. Its children are in a list "geza". When I press the button a Text('Hello') is added to the list.
This is done inside the setState() method. The newly added widget will not appear in the grid.
If I do a hot-redeploy from Android studio, then after the redeploy the widget will show up. So it looks like the widget is added to the list, but the Grid is not updated.
What am I doing wrong?
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var geza = <Widget>[ Text('AAA') ];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My Test'),
),
body: Center(
child: GridView.count(crossAxisCount: 2,
children: this.geza
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
this.geza.add(Text('Hello'));
});
},
child: Icon(Icons.add),
),
);
}
}
You could use spread operator (...) to solve this issue.
GridView.count(
crossAxisCount: 2,
children: [
...geza
],
),
I think Flutter treats list of widgets specially, as you can see in this similar case. This solution uses ListView.builder: in your case, you can use a GridView.builder, but you can also use strings instead of widgets inside your geza list to avoid any further problems:
class _MyHomePageState extends State<MyHomePage> {
// Use a list of strings
final List<String> geza = ['AAA'];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My Test'),
),
body: Center(
child: GridView.count(
crossAxisCount: 2,
// Convert to a list of widgets here
children: this.geza.map((v) => Text(v)).toList(),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Add a string to the list
setState(() => this.geza.add('Hello'));
},
child: Icon(Icons.add),
),
);
}
}

How to add navigation route to Card in Flutter

In the code below, I have a method myMenu on a card. How do I navigate to another page when the card is tapped? There are going to be several of these cards which will link to its own page content. Each time I add a function to for an example it gives an error. How do I do it properly?
import 'package:flutter/material.dart';
import 'package:tarjous_app/gridview_demo.dart';
void main(List<String> args) {
runApp(
new MaterialApp(home: TarjousAle(), debugShowCheckedModeBanner: false));
}
class TarjousAle extends StatefulWidget {
#override
_TarjousAleState createState() => _TarjousAleState();
}
class _TarjousAleState extends State<TarjousAle> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: Text("Study Plan"),
backgroundColor: Colors.amber,
),
body: Container(
child: GridView.count(
crossAxisCount: 3,
children: <Widget>[
MyMenu(
title: "Records",
icon: Icons.account_balance_wallet,
shape: Colors.brown,
),
MyMenu(
title: "Academy",
icon: Icons.account_balance,
shape: Colors.grey,
),
],
),
),
);
}
}
class MyMenu extends StatelessWidget {
MyMenu({this.title, this.icon, this.shape});
final String title;
final IconData icon;
final MaterialColor shape;
#override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(9.0),
child: InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => GridViewDemo()),
),
splashColor: Colors.amberAccent,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
icon,
size: 80.0,
color: shape,
),
Text(title, style: new TextStyle(fontSize: 18.0))
],
),
),
),
);
}
}
In the inkwell widget, I add a function that works for all the cards. But what I really want it for each card to navigate to its own page. E.g Records should navigate to its own records page, the same thing for Academy to academy page
You could receive the page in the constructor and then go to that page, like this:
class MyMenu extends StatelessWidget {
MyMenu({this.title, this.icon, this.shape, this.page});
final Widget page;
...
}
Then, in onTap:
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => page),
)
So now you can do this:
MyMenu(
...
page: GridViewDemo1(),
),
MyMenu(
...
page: GridViewDemo2(),
)
Note that to navigate to some page, your context must contain a Navigator instance of parent. So if you try to navigate directly from MaterialApp, you might run into issues. I will not belabour the point here since it was explained very well in this thread, but it is something to keep in mind in case you happen to run into it.
Edited to address comments:
I'd do something like this for your case. Named routes make it easy to specify which route you'd like the card to take you to, which you kind of need to do if you want the same widget to take you to different routes.
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(
new MaterialApp(
home: TarjousAle(),
debugShowCheckedModeBanner: false,
routes: {
GridViewDemo.route: (context) => GridViewDemo(),
AnotherDemo.route: (context) => AnotherDemo(),
},
),
);
}
class TarjousAle extends StatefulWidget {
#override
_TarjousAleState createState() => _TarjousAleState();
}
class _TarjousAleState extends State<TarjousAle> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: Text("Study Plan"),
backgroundColor: Colors.amber,
),
body: Container(
child: GridView.count(
crossAxisCount: 3,
children: <Widget>[
MyMenu(
title: "Records",
icon: Icons.account_balance_wallet,
shape: Colors.brown,
route: GridViewDemo.route
),
MyMenu(
title: "Academy",
icon: Icons.account_balance,
shape: Colors.grey,
route: AnotherDemo.route
),
],
),
),
);
}
}
class MyMenu extends StatelessWidget {
MyMenu({this.title, this.icon, this.shape, this.route});
final String title;
final IconData icon;
final MaterialColor shape;
final String route;
#override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(9.0),
child: InkWell(
onTap: () => Navigator.pushNamed(context, route),
splashColor: Colors.amberAccent,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
icon,
size: 80.0,
color: shape,
),
Text(title, style: new TextStyle(fontSize: 18.0))
],
),
),
),
);
}
}
class GridViewDemo extends StatelessWidget {
static String route = '/demo';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.brown,
appBar: AppBar(title: Text('Grid view demo')),
body: Center(
child: Text('Grid view demo'),
),
);
}
}
class AnotherDemo extends StatelessWidget {
static String route = '/another';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
appBar: AppBar(title: Text('Another demo')),
body: Center(
child: Text('Another demo'),
),
);
}
}
You can read more about the basics of navigation in official docs, and also another docs page if you fancy the named routes.
Wrap the card with GestureDetector and you can use opnTap property.
for more details Official Documentation
Try wrapping your Card in a GestureDetector like below:
GestureDetector (
child: Card(),
onTap: () {},
),
wrap the card with InkWell widget and define your navigator.push in the onTap method.
class CardWidget extends StatelessWidget {
final Function onTapCard;
const CardWidget({Key key, #required this.onTapCard}) : super(key: key);
#override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(9.0),
child: InkWell(
onTap: onTapCard,
splashColor: Colors.amberAccent,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
icon,
size: 80.0,
color: shape,
),
Text(title, style: new TextStyle(fontSize: 18.0))
],
),
),
),
);
}
}
then we have our list here
class CardList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
CardWidget(
onTapCard: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => YourSecondPage()),
),
),
CardWidget(
onTapCard: Navigator.push(
context,
MaterialPageRoute(builder: (context) => YourThirdPage()),
),
),
],
);
}
}

How can I create a Stateful Widget of a drawer whose list item onClick will change the content of a same screen?

I want to create an ItemsDrawer Stateful Widget, The ListTile supposed to change the content of the Item screen, but it is not changing.
Also, how can I use List.builder to create a List of ListTile?
Creating a method itemDrawer() works but now I want to pass the Object into the Drawer.
Drawer itemDrawer(BuildContext context) {
return Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: InkWell(
onTap: () {
setState(() {
item = items[0];
Navigator.of(context).pop();
});
},
child: Text(
'item1',
style: TextStyle(fontSize: 20),
),
),
),
ListTile(
title: InkWell(
onTap: () {
setState(() {
item= items[1];
});
Navigator.of(context).pop();
},
child: Text(
'item2',
style: TextStyle(fontSize: 20),
),
),
),
],
),
);
}
items is a List of class Item.
List<Item> items= [
item1Data,
item2Data,
item3Data,
item4Data,
item5Data,
];
Instead of a method itemDrawer(context), I want to create:
class ItemsDrawer extends StatefulWidget {
final Item item;
ItemsDrawer({Key key, this.item}) : super(key: key);
#override
_ItemsDrawer State createState() => _ItemsDrawerState();
}
class _ItemsDrawerState extends State<ItemsDrawer> {
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
children: <Widget>[
Container(
decoration: BoxDecoration(),
),
ListTile(
title: InkWell(
onTap: () {
setState(() {
widget.item= items[0];
Navigator.of(context).pop();
});
},
child: Text(
'item1',
style: TextStyle(fontSize: 20),
),
),
),
.....
On widget.item it is saying that:
'item' can't be used as a setter because it is final
Update: ItemScreen where the content is shown:
class ItemScreen extends StatefulWidget {
#override
_ItemScreen State createState() => _ItemScreenState();
}
class _ItemScreentate extends State<ItemScreen> {
Item item;
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState();
#override
void initState() {
super.initState();
item = items[0];
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
backgroundColor: kcAppBackground,
appBar: AppBar(
//App Bar Title
title: modifiedAppBarTitleText(item.itemTitle),
.....
drawer: ItemDrawer(key: _scaffoldKey, item: item,),
The error you are getting is because you are trying to change something that was declared final
Add
class _ItemsDrawerState extends State<ItemsDrawer> {
Item item
#override
void initState() {
item = widte.item // maybe copy it... or reinit if you need
super.initState();
}
But if you want to update a widget from another one you might want to check this post out or this one