How can I have my AppBar in a separate file in Flutter while still having the Widgets show?
AndroidMobileDartFlutterAppbarAndroid Problem Overview
I am currently building a Flutter app that recommends restaurants around the area. However, I've gotten myself in quite the kerfuffle.
I want my app to have the code for the AppBar separate from the code for each screen for the sake of organization and cleanliness. So, I built KainAppBar.dart as the AppBar code. It is shown here:
import 'package:flutter/material.dart';
import 'package:gradient_app_bar/gradient_app_bar.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
GoogleSignIn _googleSignIn = GoogleSignIn(
signInOption: SignInOption.standard,
);
class KainAppBar extends StatelessWidget {
final String title;
KainAppBar(this.title);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: new GradientAppBar(
centerTitle: true,
title: new Text('Kain',
style: TextStyle(
fontFamily: 'Quiapo', fontSize: 36.0, fontWeight: FontWeight.w600
)),
backgroundColorStart: Colors.red[400],
backgroundColorEnd: Colors.red[900],
),
drawer: new Drawer(
child: ListView(
children: <Widget>[
new UserAccountsDrawerHeader(
decoration: BoxDecoration(
color: Colors.red[800],
),
accountName: new Text('Guest'),
accountEmail: new Text('[email protected]'),
currentAccountPicture: new CircleAvatar(
backgroundImage: new NetworkImage('https://avatarfiles.alphacoders.com/848/84855.jpg'),
),
),
new ListTile(
title: new Text('Restaurants'),
leading: Icon(Icons.restaurant_menu),
onTap: (){
Navigator.of(context).pop();
Navigator.of(context).pushNamed('/restaurant_screen');
},
),
new ListTile(
title: new Text('Nearby'),
leading: Icon(Icons.near_me),
onTap: (){
Navigator.of(context).pop();
Navigator.of(context).pushNamed('/nearby_screen');
},
),
new ListTile(
title: new Text('Request Restaurant'),
leading: Icon(Icons.library_add),
onTap: (){
Navigator.of(context).pop();
Navigator.of(context).pushNamed('/request_screen');
},
),
new ListTile(
title: new Text('Settings'),
leading: Icon(Icons.settings),
onTap: (){},
),
new ListTile(
title: new Text('About'),
leading: Icon(Icons.info_outline),
onTap: (){},
),
new ListTile(
title: new Text('Logout'),
leading: Icon(Icons.power_settings_new),
onTap: (){
_googleSignIn.disconnect();
FirebaseAuth.instance.signOut().then((value) {
Navigator.of(context).pushReplacementNamed('/login');
}).catchError((e) {
print(e);
});
},
),
],
),
),
body: new Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(50.0, 160.0, 50.0, 0.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
],
),
)
],
),
);
}
}
For some of my screens, I can declare it with no problem. Here is the code for home_screen.dart:
class HomeScreen extends StatefulWidget {
@override
HomeScreenState createState() {
return HomeScreenState();
}
}
class HomeScreenState extends State<HomeScreen>{
@override
noSuchMethod(Invocation invocation) {
return super.noSuchMethod(invocation);
}
@override
Widget build(BuildContext context){
return new KainAppBar("Kain");
}
}
However, for my restaurant_screen.dart, I've encountered a problem. For context, what restaurant_screen.dart does is it shows the restaurants included in the app through a TabBar with three options(tabs): Restaurant List, Cuisine List, and History. Which means that apart from the AppBar, it also needs to have a TabBar inside. But I cannot put this TabBar inside KainAppBar.dart because I only need it to show inside restaurant_screen.dart.
Here is my code for the Widget inside restaurant_screen.dart:
@override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
GradientAppBar(
title: KainAppBar("Kain"),
bottom: new TabBar(
labelColor: Colors.white,
controller: tController,
tabs: <Widget>[
new Tab(text: 'List'),
new Tab(text: 'Cuisine'),
new Tab(text: 'Favorites'),
],
),
),
TabBarView(
controller: tController,
children: <Widget>[
new firstpage.RestaurantList(),
new secondpage.CuisineList(),
new thirdpage.RestaurantFavorites(),
],
),
],
);
}
Running the code just shows a black screen. Is there any workaround for this?
Android Solutions
Solution 1 - Android
This is another way of going about it. By doing this you can customize this appbar to the way you want. That way, if you continue with that style, you don't have to recreate it on every page. You create it once and call on it within any widget.
Class
import 'package:flutter/material.dart';
class BaseAppBar extends StatelessWidget implements PreferredSizeWidget {
final Color backgroundColor = Colors.red;
final Text title;
final AppBar appBar;
final List<Widget> widgets;
/// you can add more fields that meet your needs
const BaseAppBar({Key key, this.title, this.appBar, this.widgets})
: super(key: key);
@override
Widget build(BuildContext context) {
return AppBar(
title: title,
backgroundColor: backgroundColor,
actions: widgets,
);
}
@override
Size get preferredSize => new Size.fromHeight(appBar.preferredSize.height);
}
Implementation within desired page
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: BaseAppBar(
title: Text('title'),
appBar: AppBar(),
widgets: <Widget>[Icon(Icons.more_vert)],
),
body: Container());
}
Solution 2 - Android
Let's have a widget.dart
like so:
import 'package:flutter/material.dart';
class ReusableWidgets {
static getAppBar(String title) {
return AppBar(
title: Text(title),
);
}
}
Let's keep using this class to get appbar
in all our screens like so:
import 'package:filter_chip/widgets.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: new ReusableWidgets().getAppBar('Hello World'),
body: Text(
'Flutter Demo Home Page'),
),
);
}
}
Solution 3 - Android
Implement PreferredSizeWidget
and override the method like this (kToolbarHeight
from material.dart is the default height used by AppBar
). Also you can set height as you want.
class NavBar extends StatelessWidget implements PreferredSizeWidget {
@override
Widget build(BuildContext context) {
return AppBar(
title: Text('Hello'),
);
}
@override
Size get preferredSize => Size.fromHeight(kToolbarHeight);
}
Solution 4 - Android
You can wrap AppBar into a function that return AppBar.
headerNav.dart
import 'package:flutter/material.dart';
AppBar headerNav({String title}){
return AppBar(
title: Text(title),
);
}
homePage.dart
import 'package:flutter/material.dart';
import 'package:myapp/components/headerNav.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: headerNav(text:'Home Page'),
body: Container(
child: Text('Home'),
),
);
}
}
Solution 5 - Android
To define your own AppBar
, you need to implement preferredSize
from the PreferredSizeWidget
class. But if you want to keep the default size, you can mimic the original AppBar
by creating an internal instance and reference its size like this:
class MyFancyAppBar extends StatelessWidget implements PreferredSizeWidget {
final String title;
const MyFancyAppBar({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return AppBar(...);
}
static final _appBar = AppBar();
@override
Size get preferredSize => _appBar.preferredSize;
}
Live Demo
Solution 6 - Android
if you want to make a custom appbar in a different file so that it can be shared you can just make your new app bar extend it and then pass your customization to super(), in your case your KainAppBar will extend the GradientAppBar like below how MyAppBar extends AppBar.
Ref: https://www.youtube.com/watch?v=cLbNY2nCuYw
class MyAppBar extends AppBar {
MyAppBar()
: super(
elevation: 0,
centerTitle: true,
actions: <Widget>[
IconButton(
onPressed: () {},
icon: Icon(Icons.search),
color: Colors.white,
)
]);
}
Solution 7 - Android
Here is an example of creating a AppBar widget
import 'package:flutter/material.dart';
class ListTitleBar extends StatefulWidget implements PreferredSizeWidget {
final String _left;
final String _right;
ListTitleBar(this._left, this._right);
@override
State<StatefulWidget> createState() => new ListTitleBarState(_left, _right);
@override
Size get preferredSize {
return new Size.fromHeight(20.0);
}
}
class ListTitleBarState extends State<ListTitleBar> {
String _leftTitle;
String _rightTitle;
ListTitleBarState(this._leftTitle, this._rightTitle);
@override
Widget build(BuildContext context) {
return new Container(
decoration: new BoxDecoration(
color: Colors.redAccent,
border: new Border.all(color: Colors.black),
),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
///Left Column Title
new Column(
children: <Widget>[
new Container(
color: Colors.redAccent,
padding: const EdgeInsets.all(10.0),
child: new Text(_leftTitle,
style: new TextStyle(
color: Colors.white,
fontSize: 18.0
),
),
)
],
),
///Right Column Title
new Column(
children: <Widget>[
new Container(
color: Colors.redAccent,
padding: const EdgeInsets.all(10.0),
child: new Text(_rightTitle,
style: new TextStyle(
color: Colors.white,
fontSize: 18.0
),
),
)
],
),
],
),
);
}
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
}
Solution 8 - Android
First and foremost thing you should know is that the App bar Widget is not like every other widget, AppBar implements the class PreferredSizeWidget which means that AppBar must have a preferred size.
Begin with making a file named CustomAppBar
and then implement the PreferredSizeWidget
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
final String title;
final Widget child;
final Function onPressed;
final Function onTitleTapped;
@override
final Size preferredSize;
CustomAppBar({@required this.title, @required this.child, @required this.onPressed, this.onTitleTapped})
: preferredSize = Size.fromHeight(60.0);
ShapeBorder kBackButtonShape = RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(30),
),
);
@override
Widget build(BuildContext context) {
return SafeArea(
child: Column(
children: <Widget>[
// SizedBox(height: 30,),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Hero(
tag: 'topBarBtn',
child: Card(
elevation: 10,
shape: kBackButtonShape,
child: MaterialButton(
height: 50,
minWidth: 50,
elevation: 10,
shape: kBackButtonShape,
onPressed: onPressed,
child: child,
),
),
),
// SizedBox(
// width: 50,
// ),
Hero(
tag: 'title',
transitionOnUserGestures: true,
child: Card(
elevation: 10,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(30),
),
),
child: InkWell(
onTap: onTitleTapped,
child: Container(
width: MediaQuery.of(context).size.width / 1.5,
height: 50,
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 30),
child: Text(
title,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 25,
// color: Colors.black54,
),
),
),
),
),
),
),
)
],
),
],
),
);
}
}
And implement this according to your needs
class _MyHomePageState extends State<MyHomePage> {
Widget kBackBtn = Icon(
Icons.arrow_back_ios,
);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: CustomAppBar(title: "Custom App Bar", child: kBackBtn, onPressed: null),
);
}
}
Solution 9 - Android
This is the effective way to make Custom app Bar, you can call CustomAppBar.dart on every screen in your app
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math.dart' as math;
import 'package:flutter_a/Screens/utils.dart';
import 'package:flutter/cupertino.dart';
class CustomAppBar extends StatefulWidget with PreferredSizeWidget {
final Size preferredSize;
CustomAppBar( {Key key})
: preferredSize = Size.fromHeight(56.0),
super(key: key);
@override
_CustomAppBarState createState() => _CustomAppBarState();
}
class _CustomAppBarState extends State<CustomAppBar> {
@override
Widget build(BuildContext context) {
return AppBar(
shadowColor: Colors.grey[200],
bottomOpacity: 1.0,
title: Text(
'YOUR CUSTOM APPBAR',
style: TextStyle(color: Colors.white, fontSize: 16),
),
actions: <Widget>[],
elevation: 0,
backgroundColor: Colors.cyan,
automaticallyImplyLeading: true,
);
}