How can I have my AppBar in a separate file in Flutter while still having the Widgets show?

33,439

Solution 1

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

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 3

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 4

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

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

Share:
33,439

Related videos on Youtube

Xavier Paolo Jamito
Author by

Xavier Paolo Jamito

Updated on September 07, 2021

Comments

  • Xavier Paolo Jamito
    Xavier Paolo Jamito over 2 years

    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?

    • absin
      absin over 5 years
      Your KainAppBar returns more than just an appbar, it also has a listview, if you want the appbar code to be in one place, just declare a method in a class, which returns a widget, and put appBar: section there.
    • Xavier Paolo Jamito
      Xavier Paolo Jamito over 5 years
      @AbSin, wouldn't that defeat the purpose of having the code in a single file? If I just declare the AppBar inside a method, then I still have to put the AppBar code inside every all my .dart files instead of just declaring it from a single file?
    • absin
      absin over 5 years
      I don't fully understand. You will just have to call the method, check the answer I posted
  • Xavier Paolo Jamito
    Xavier Paolo Jamito over 5 years
    I get it now. Thanks. However, I'm still running into a problem. The context in my ListTiles are now showing as undefined. Where do I declare the (BuildContext context) here?
  • absin
    absin over 5 years
    Try to wrap the Scaffold() within a MaterialApp in the KainAppBar class like this: return MaterialApp( home: Scaffold(.....),); You need a MaterialApp or a WidgetsApp around your widget. They provide the MediaQuery. When you call .of(context) flutter will always look up the widget tree to find the widget. I think the plugin GradientAppBar requires it.
  • Xavier Paolo Jamito
    Xavier Paolo Jamito over 5 years
    Where should I put MediaQuery?
  • absin
    absin over 5 years
    Nowhere. It isn't there in your code, as I said the GradientAppBar plugin uses it here. Because you are using this plugin, at the root of the widget tree you can have MaterialApp
  • absin
    absin over 5 years
    Please share updated code in the question, I am afk right now, will take a look
  • Xavier Paolo Jamito
    Xavier Paolo Jamito over 5 years
    Thanks for sharing, but the functionality seems off. The code does run, but upon clicking on of the menus in the side menu, the screen does not show.
  • absin
    absin over 5 years
    Are you sure the routes are set properly?
  • absin
    absin over 5 years
    Sounds like you are interacting with routes that have already been removed, anyways check here it works perfectly
  • Ng Sek Long
    Ng Sek Long about 5 years
    Thanks, this save me hours of thinking. Also I think it is good to use static before getAppBar, so we can save some code.
  • Elia Weiss
    Elia Weiss over 4 years
    The problem is that u cannot override the method createState()
  • Elia Weiss
    Elia Weiss over 4 years
    The final variable 'appBar' must be initialized.
  • Luis Parada
    Luis Parada almost 4 years
    This doesn't work for me, I added the getter and implemented PreferredSizeWidget and still getting the error The getter 'preferredSize' was called on null. Receiver: null Tried calling: preferredSize
  • Axort
    Axort over 3 years
    Instead of having an AppBar instance to get its height, you can use kToolbarHeight: @override Size get preferredSize => new Size.fromHeight(kToolbarHeight);
  • Alexandre Jean
    Alexandre Jean almost 3 years
    kToolbarHeight is indeed a top level constant from MaterialApp api.flutter.dev/flutter/material/kToolbarHeight-constant.htm‌​l
  • ItSNeverLate
    ItSNeverLate almost 3 years
    class MyAppBar extends AppBar with PreferredSizeWidget { MyAppBar({Key key, Widget title}) : super( key: key, title: title, // maybe other AppBar properties ); }