Not getting the scaffold context in flutter custom app bar - flutter

I have written this flutter widget which I wanted to implement a custom app bar along with a drawer. Here is my starting widget
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Food App',
theme: ThemeData(
primaryColor: kPrimaryColor,
scaffoldBackgroundColor: Colors.white,
),
home: Scaffold(
bottomNavigationBar: BottomNavBar(),
body: HomeScreen(),
drawer: CustomDrawer(),
appBar: homeAppBar(context, (context) {
Scaffold.of(context).openDrawer();
}),
));
}
}
I have implemented a callback in the app bar icon button
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:food_app/constants.dart';
AppBar homeAppBar(BuildContext context, Function function) {
return AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: SvgPicture.asset("assets/icons/menu.svg"),
onPressed: () {
function(context);
},
),
title: RichText(
text: TextSpan(
style: Theme.of(context)
.textTheme
.headline6
.copyWith(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: "Makana",
style: TextStyle(color: ksecondaryColor),
),
TextSpan(
text: "Food",
style: TextStyle(color: kPrimaryColor),
),
],
),
),
actions: <Widget>[
IconButton(
icon: SvgPicture.asset("assets/icons/notification.svg"),
onPressed: () {},
),
],
);
}
It gives me an error
════════ Exception caught by gesture ═══════════════════════════════════════════
The following assertion was thrown while handling a gesture:
Scaffold.of() called with a context that does not contain a Scaffold.
No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). This usually happens when the context provided is from the same StatefulWidget as that whose build function actually creates the Scaffold widget being sought.
There are several ways to avoid this problem. The simplest is to use a Builder to get a context that is "under" the Scaffold. For an example of this, please see the documentation for Scaffold.of():
https://api.flutter.dev/flutter/material/Scaffold/of.html
A more efficient solution is to split your build function into several widgets. This introduces a new context from which you can obtain the Scaffold. In this solution, you would have an outer widget that creates the Scaffold populated by instances of your new inner widgets, and then in these inner widgets you would use Scaffold.of().
A less elegant but more expedient solution is assign a GlobalKey to the Scaffold, then use the key.currentState property to obtain the ScaffoldState rather than using the Scaffold.of() function.
The context used was: MyApp
When the exception was thrown, this was the stack
I have tried it like this also
AppBar homeAppBar(BuildContext context) {
return AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: SvgPicture.asset("assets/icons/menu.svg"),
onPressed: () {
Scaffold.of(context).openDrawer();
},
),
title: RichText(
text: TextSpan(
style: Theme.of(context)
.textTheme
.headline6
.copyWith(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: "Makana",
style: TextStyle(color: ksecondaryColor),
),
TextSpan(
text: "Food",
style: TextStyle(color: kPrimaryColor),
),
],
),
),
actions: <Widget>[
IconButton(
icon: SvgPicture.asset("assets/icons/notification.svg"),
onPressed: () {},
),
],
);
}
Though it is under the scaffold from the main.dart, it still throws the same error. I tried Builder in app bar but it is not acceptable in AppBar return type. I am a new bee into the flutter world. Please help!

The problem is that the context that you are passing to the homeAppBar function does not have access to a Scaffold.
#override
// The following context that is the one that you are passing to the function is above the scaffold and hence does not finds any scaffold "above" it.
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Food App',
theme: ThemeData(
primaryColor: kPrimaryColor,
scaffoldBackgroundColor: Colors.white,
),
home: Scaffold(
bottomNavigationBar: BottomNavBar(),
body: HomeScreen(),
drawer: CustomDrawer(),
appBar: homeAppBar(context, (context) {
There are two solutions either wrap the homeAppBar inside a Builder like follows:
Builder(builder: (ctx)=>homeAppBar(ctx),)
Or just instead of using a function refactor the widget to use a class which is the one I would recommend for optimization reasons see this answer for a thorough explanation.

Related

How to change the textTheme in the ThemeData class?

I cannot change the color of the TextTheme property in the ThemeData class even though the code doesn't give any error.
Here is what I have done so far:
import 'package:flutter/material.dart';
void main() => runApp(BMICalculator());
class BMICalculator extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
appBarTheme: AppBarTheme(
backgroundColor: Color(0xff0a0e21),
),
scaffoldBackgroundColor: Color(0xff0a0e21),
textTheme: TextTheme(bodyText1: TextStyle(color: Colors.blue)),//the text remains white in the app.
),
home: InputPage(),
);
}
}
class InputPage extends StatefulWidget {
#override
_InputPageState createState() => _InputPageState();
}
class _InputPageState extends State<InputPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('BMI CALCULATOR'),
),
body: Center(
child: Text('Body Text'),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => print('object'),
),
);
}
}
What am I doing wrong?
You have declared the themes successfully, but are not using it. Here is the replacement code snippet:
body: Center(
child: Text('Body Text', style: Theme.of(context).textTheme.bodyText1),
)
You need to specify the theme in style attribute for the necessary changes to take place.
Try passing textTheme: ThemeData value
here is an example for textTheme
textTheme: ThemeData.light().textTheme.copyWith(
headline6: TextStyle(
fontFamily: 'OpenSans',
fontSize: 18,
fontWeight: FontWeight.bold,
),
Just match your implementation with mine, here I have implemented textTheme for titles.
now if I want to refer to this headline6 theme I can do
Theme.of(context).textTheme.headline6
As much as I know there are a-lot of different properties in TextTheme class, such as bodytext1, bodytext2, headline1, headline2, caption etc etc...
And you can configure many of them to use them later, but there is no catalog or confirmation that which part of the app it will be applied to, i.e. Out of all the places that uses the text widget, to which bodytext1 will be applied to it is not known...but overtime you will get the feel for it.
But for now you have to call it manually where-ever you want to use that specific text configuration, such as in your code you have to do it like:
return Scaffold(
appBar: AppBar(
title: Text('BMI CALCULATOR'),
),
body: Center(
child: Text('Body Text', style: Theme.of(context).textTheme.bodyText1,),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => print('object'),
),
);
The main point of adding theme is code-reusability and making it dynamic which is obviously achieved by this too.

error when I use raisedButton in a widget

I am writing my first flutter app and i created two classes.
Those two classes represent two pages that i wish to navigate between and I am facing an issue with the "raisedButton".
The first class has 2 widgets and the button only works in one of them! whenever I try to cut+ paste the button to the desired widget => I get the following error:
The method 'findAncestorStateOfType' was called on null.
Receiver: null
Tried calling: findAncestorStateOfType()
I don't know what they mean by that.
Any help is appreciated.
class FirstRoute extends StatelessWidget {
Widget _contactList(String name, String image,[BuildContext context]) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.red,
backgroundImage: AssetImage(image),
radius: 35.0,
child: Text(nameInitial,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0)),
),
title: Text(name),
trailing: RaisedButton(
textColor: Colors.white,
color: Colors.blue,
child: Text('Details'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Details()),
);
},
),
),
);
}
you can use a named route by calling the Navigator.pushNamed(context,'/home'); Named routes allow for parameter passing '/customer/:${customer.id}'. the routing paths are defined onGenerateRoute attribute assigned to routing information
class _AppWidgetState extends State<AppWidget>{
bool _bright=false;
_brightnessCallback().
{setState(()=>_bright=!_bright);debugPrint("brightnessCallback fired");}
#override
Widget build(BuildContext context){
return MaterialApp(
title: "Flutter demo",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHome2Widget(),
onGenerateRoute: handleRoute
);
}}
Route<dynamic> handleRoute(RouteSettings routeSettings)
{
List<String> nameParam= routeSettings.name.split(":");
String name=nameParam[0];
//int id=int.tryParse(nameParam[1]);
Widget childWidget;
if(name=="/Home")
{
childWidget=MyHome2Widget();
}else if(name=="/Summary")
{
childWidget=SummaryWidget();
}
return MaterialPageRoute(
builder:(context)=>childWidget
);
}

Flutter: How to open Drawer programmatically

I want to open Drawer programmatically not by sliding it, how to disable that sliding functionality (touch functionality of Drawer)
Null safe code
Using GlobalKey:
final GlobalKey<ScaffoldState> _key = GlobalKey(); // Create a key
#override
Widget build(BuildContext context) {
return Scaffold(
key: _key, // Assign the key to Scaffold.
drawer: Drawer(),
floatingActionButton: FloatingActionButton(
onPressed: () => _key.currentState!.openDrawer(), // <-- Opens drawer
),
);
}
Using Builder:
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(),
floatingActionButton: Builder(builder: (context) {
return FloatingActionButton(
onPressed: () => Scaffold.of(context).openDrawer(), // <-- Opens drawer.
);
}),
);
}
If you want to disable opening the Drawer using a drag gesture, you can set
Scaffold(
drawerEnableOpenDragGesture: false
// above code ...
)
To disable the slide to open functionality you can set the property drawerEnableOpenDragGesture on Scaffold to false.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
// this to prevent the default sliding behaviour
drawerEnableOpenDragGesture: false,
drawer: Drawer(),
appBar: AppBar(
leading: Builder(builder: (context) => // Ensure Scaffold is in context
IconButton(
icon: Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer()
),
),
)
)
);
}
}
To open the drawer programmatically using Scaffold.of(context) you'll have to ensure (thanks Krolaw !) that the context inside which the call is made is aware of the Scaffold.
A clean way to do it is to wrap the button in a builder.
I've edited the answer to include a minimal full working example.
Scaffold is a widget that implements material design principles, so be aware that to be able to call this method, you'll need to import 'package:flutter/material.dart'; and your widget needs to have a MaterialApp as ancestor.
Codepen demo
As with many Flutter things, there are other solutions to ensure Scaffold is in context.
Error messages are IMO among the best features of flutter framework, allow me to humbly suggest to always read them thoroughly and to explore the documentation they point at.
For instance, this is part of the error message that one gets if calling openDrawer outside of a proper context:
Scaffold.of() called with a context that does not contain a Scaffold.
No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). This usually happens when the context provided is from the same StatefulWidget as that whose build function actually creates the Scaffold widget being sought.
There are several ways to avoid this problem. The simplest is to use a Builder to get a context that is "under" the Scaffold. For an example of this, please see the documentation for Scaffold.of():
https://api.flutter.dev/flutter/material/Scaffold/of.html
A more efficient solution is to split your build function into several widgets. This introduces a new context from which you can obtain the Scaffold. In this solution, you would have an outer widget that creates the Scaffold populated by instances of your new inner widgets, and then in these inner widgets you would use Scaffold.of().
A less elegant but more expedient solution is assign a GlobalKey to the Scaffold, then use the key.currentState property to obtain the ScaffoldState rather than using the Scaffold.of() function.
Calling Scaffold.of doesn't work because the context doesn't contain the Scaffold. Some solutions above have ignored this, others have used GlobalKey. I believe the cleanest solution is wrapping the button in a Builder:
Scaffold(
drawerEnableOpenDragGesture: false, // Prevent user sliding open
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text("Some Title"),
actions: [
Builder(builder: (context) => // Ensure Scaffold is in context
IconButton(
icon: Icon(Icons.settings),
onPressed: () => Scaffold.of(context).openDrawer()
)),
],
),
// TODO ...
)
Here is another example of opening the drawer programmatically from a hamburger icon and without the Appbar:-
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
var scaffoldKey = GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return new MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
key: scaffoldKey,
drawer: new Drawer(
child: new ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Item 1'),
onTap: () {
//Do some stuff here
//Closing programmatically - very less practical use
scaffoldKey.currentState.openEndDrawer();
},
)
],
),
),
body: Stack(
children: <Widget>[
new Center(
child: new Column(
children: <Widget>[],
)),
Positioned(
left: 10,
top: 20,
child: IconButton(
icon: Icon(Icons.menu),
onPressed: () => scaffoldKey.currentState.openDrawer(),
),
),
],
),
),
);
}
}
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(
"Infilon Technologies",
style:
TextStyle(fontFamily: "Poppins", fontWeight: FontWeight.w600),
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.menu),
onPressed: () {
if (_scaffoldKey.currentState.isEndDrawerOpen) {
_scaffoldKey.currentState.openDrawer();
} else {
_scaffoldKey.currentState.openEndDrawer();
}
},
),
],
),
If you are using endDrawer (right to left) in Scaffold, you should use:
Scaffold.of(context).openEndDrawer();
If you are using drawer (left to right) in Scaffold, you should use:
Scaffold.of(context).openDrawer();
You can use this perfect method to open drawer
its Worked with null safty module above flutter 2.12
class DashBoardScreen extends StatefulWidget {
final String? screen;
const DashBoardScreen(this.screen, {super.key});
#override
State<DashBoardScreen> createState() => _DashBoardScreenState();
}
class _DashBoardScreenState extends State<DashBoardScreen> {
DashBoardScreenController controller =
Get.put(getIt<DashBoardScreenController>());
#override
Widget build(BuildContext context) {
controller.scaffoldKey = GlobalKey<ScaffoldState>();
return Obx(() => Scaffold(
key: controller.scaffoldKey,
onDrawerChanged: (isOpened) {
if (!isOpened) {
setState(() {});
}
},
appBar: AppBar(
title: const Text("Test drawer App"),
actions: const [
const Padding(
padding: EdgeInsets.only(right: 20),
child: Icon(Icons.search))
],
leading: UnconstrainedBox(
child: GestureDetector(
onTap: () {
controller.scaffoldKey.currentState!.openDrawer();
},
child: const AbsorbPointer(
absorbing: true,
child: SizedBox(
height: 50,
child: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(
"https://cdn.pixabay.com/photo/2014/07/09/10/04/man-388104_960_720.jpg",
)),
),
),
),
),
),
drawerEdgeDragWidth:
kIsWeb ? MediaQuery.of(context).size.width * 0.2 : null,
drawer: Drawer(
key: controller.scaffoldKey,
child: ListView(
children: [
DropdownMenuItem(onTap: () {}, child: const Text("Add Anime"))
],
)),
body: widget.screen == StringVariables.ADD_ANIME
? AddAnimeFragment(widget.screen!)
: Container(),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Add Anime',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Favourite',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
],
currentIndex: controller.bottomSheetIndex.value,
selectedItemColor: ColorName.primaryColor,
onTap: (s) {
controller.bottomSheetIndex.value = s;
},
),
));
}
}
Simply flow these steps
create a variable in class like this
var scaffoldKey = GlobalKey<ScaffoldState>();
then use this key in your scaffold like this
Scaffold(
key: scaffoldKey,
appBar: AppBar(
automaticallyImplyLeading: false,
leading: IconButton(
onPressed: () {
scaffoldKey.currentState?.openDrawer();
},
icon: Icon(
Icons.menu,
color: ExtraColors.PRIMARY_800,
)),
title: Text(
'${AppStrings.appName}',
),
centerTitle: true,
),
)

Flutter back button is hidden on navigation

Why MaterialApp hides back button from AppBar for my new screen. I have many screens in my app, where I need to use ThemeData. To do that, I use MaterialApp in another screen, but I don't see any back arrow on the AppBar().
return MaterialApp(
theme: ThemeData(
fontFamily: 'IranSansLight',
textTheme: TextTheme(
headline: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold, fontFamily: 'IranSansLight'),
title: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic, fontFamily: 'IranSansLight'),
body1: Theme.of(context).textTheme.caption.copyWith(fontFamily: 'IranSansLight', color: Colors.black)),
),
home: ScopedModel(
model: CounterModel(),
child: Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
appBar: AppBar(automaticallyImplyLeading:true,title: Text('aaaa'),),
body: Container(
)),
),
),
);
}
You are using MaterialApp for all individual screens, this is not the correct way, all you need is just one MaterialApp for the entire app.
void main() {
runApp(
MaterialApp(home: HomePage()), // MaterialApp widget should be used only here
);
}
And whenever you need to use Theme in your 2nd screen, use something like:
#override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(
// override whatever you need like
textTheme: TextTheme(...),
),
child: Scaffold(),
);
}

Flutter Text Color Theme doesn't work under ListTile title

I am getting started in Flutter and just trying out stuff.
I set a custom theme, but Text Widgets under the title property of ListTile don't get the right color. Also Icons under the leading property don't get the right color as well.
I tried setting some other colors, and sorted out, that the Problem only exists within that element.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MyApp',
theme: ThemeData(
primaryColor: Colors.black,
scaffoldBackgroundColor: Color(0xff202020),
cardTheme: CardTheme(color: Colors.black),
textTheme: TextTheme(
body1: TextStyle(color: Colors.white),
subtitle: TextStyle(color: Colors.white),
headline: TextStyle(color: Colors.white)),
iconTheme: IconThemeData(color: Colors.white)),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("HomePage"),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
tooltip: "back to the last page.",
onPressed: () {
Navigator.pop(context);
})
),
body: Card(
child: ListTile(
title: Text("Test"),
leading: new Icon(Icons.devices)
),
));
}
}
The Text for the title should appear white as well as the icon, instead it is black. All other Text is white.
The title on ListTile is using subhead textStyle of Theme. So if you want config color of ListTile on ThemeData you need change subhead.
textTheme: TextTheme(
subhead: TextStyle(color: Colors.white),
...)
In order to make use of you're theme you need to use Theme.of(context).
Container(
color: Theme.of(context).accentColor,
child: Text(
'Text with a background color',
style: Theme.of(context).textTheme.title,
),
);
Read more about it here in the cookbook. You are on the right track
https://flutter.dev/docs/cookbook/design/themes
In Flutter 3 which I'm currently using, titleMedium defines the text style for title of ListTiles.
MaterialApp(
theme: ThemeData(
textTheme: Typography().black.copyWith(
titleMedium: const TextStyle(
fontSize: 32,
),
),
)
For example the above theme makes theme relatively large.
Flutter team should provide developers with a reference for these styles. For the moment you can find out which style corresponds to which widget by trial and error.
Just change body1 to bodyText1 in the following location:
C:\src\flutter.pub-cache\hosted\pub.dartlang.org\charts_flutter-0.9.0\lib\src\behaviors\legend\legend_entry_layout
This will solve the issue.