0

'Allo,

My main file is getting up to 1000 lines of code and I can't help but think I could save time by separating the Scaffold into 3 or 4 .dart files. Is this possible?

Between the AppBar and Drawer I'm already up to 500+ lines of code because of all the links and design parameters. I'd like to extricate this code instead of having to scroll through it continually when I'm working on the main body.

Anytime I've tried to take out the drawer and put it in a separate file I get errors everywhere. Problems with 'dynamic' and 'widgets' and return types, etc.

What can I take out the scaffold and reference it to another file?

child: new Scaffold(
  appBar: new AppBar(
    bottom: new TabBar(tabs:[.....]),
    actions: <Widget> [
      new PopupMenuButton<xx>()
      ],),],),  //end appBar
  drawer: new Drawer(......),   //end drawer
  body: TabBarView(....),   //end body
  ),  //end scaffold

I wouldn't mind leaving the main body in this main file but I might also take it out if i had more options. Just want to reduce a 1000+ lines into 2-3 chunks, files of manageable space.

Any ideas?

lubi
  • 167
  • 13
  • See https://stackoverflow.com/questions/51232298/flutter-widgets-extreem-code-indentation/51235410#51235410 for how to easily refactor your widgets. You can then easily move that widget into its own separate file – Rémi Rousselet Sep 05 '18 at 12:10

2 Answers2

1

There is most certainly a way to organize this across different files. In addition to being easier to maintain and test, this may also increase performance if state is involved (because if state changes you have to rebuild the entire tree rather than only rebuilding leaf nodes).

However, this also means that if you have state involved and sprinkled about in your one large build() method then you may have some additional considerations as you organize across files. This is assuming you would create new custom widgets to wrap the various components and you would need to orchestrate the state appropriately.

So with the goal of breaking this build method into different sub Widgets, I recommend you start by breaking it up into functions first:

from:

Widget build(BuildContext context) {
  return new Scaffold(
    appBar: new AppBar(
        bottom: new TabBar(tabs:[.....]),
        actions: <Widget> [
        new PopupMenuButton<xx>()
        ],),],),  //end appBar

    drawer: new Drawer(......),   //end drawer

    body: TabBarView(....),   //end body
  );
}

to:

Widget build(BuildContext context) {
  return new Scaffold(
    appBar: _appBar(),
    drawer: _drawer(),
    body: _body(),
  );
}

Widget _appBar() {
    return new AppBar(
        bottom: new TabBar(tabs:[.....]),
        actions: <Widget> [
        new PopupMenuButton<xx>()
        ],),],);
}

Widget _drawer() {
    ...
}

Widget _body() {
    return TabBarView();
}

At this point, you may start to realize what data/state is being passed around as you will have to add parameters to these new helper methods.

If you have a lot of parameter passing (especially on state that changes), you will have other considerations outside the scope of this answer (and we would need to see what state you are actually dealing with).

The next step is to create a new Widget for each of these methods.

From:

Widget _appBar() {
    return new AppBar(
        bottom: new TabBar(tabs:[.....]),
        actions: <Widget> [
        new PopupMenuButton<xx>()
        ],),],);
}

To:

Widget _appBar(...) {
    return MyAppBar(...);
}

class MyAppBar extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return new AppBar(
        bottom: new TabBar(tabs:[.....]),
        actions: <Widget> [
        new PopupMenuButton<xx>()
        ],),],);
    }
}

You can define MyAppBar in it's own file.

You can also bypass the _appBar(...) method and just construct the new widget in the main build() method (assuming you have no other complex setup):

Widget build(BuildContext context) {
  return new Scaffold(
    appBar: MyAppBar(),
    drawer: MyDrawer(),
    body: _body(), // you might want to keep the body in the same file
  );
}
Ashton Thomas
  • 16,659
  • 5
  • 31
  • 25
  • 1
    Your last example will not compile because `MyAppBar` does not implement `PreferredSizeWidget` – boformer Sep 05 '18 at 12:28
  • @boformer, ah yes. Perhaps it's best to carve out other parts of the AppBar into sub widgets. FWIW, I usually just move AppBar construction to a separate function rather than separate file/widget. And only move reusuable parts to a separate class. You could also try extending AppBar rather than Stateless – Ashton Thomas Sep 05 '18 at 12:59
  • Thanks Ashton. I was wondering about the State factors. Now I'm going to have to go through the AppBar and Drawer and make decisions. So are you just putting the AppBar at the bottom of the file and moving Stateless wedgets to separate dart files if necessary? – lubi Sep 06 '18 at 15:21
  • @lubi, yeah you can move the widget building to other methods or files/widgets as necessary. If you create new widgets/files, they can be Stateless, Stateful or extend other widgets. – Ashton Thomas Sep 06 '18 at 16:19
  • It's not a good idea to extend other widgets. Better use composition – boformer Sep 06 '18 at 18:48
  • Yep, that's a good rule of thumb. Would certainly want to pause and think before extending a core Flutter widget :D – Ashton Thomas Sep 06 '18 at 18:57
1

Easiest way are methods:

Widget build(BuildContext context) {
  return Scaffold(
    appBar: _buildAppBar(),
    ...
  );
}

Widget _buildAppBar() {
  return AppBar(...);
}

You can also use separate widgets. The widget in the appBar slot must implement PreferredSizeWidget:

class TestPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: MyAppBar(),
      body: MyBody(),
    );
  }
}

class MyAppBar extends StatelessWidget implements PreferredSizeWidget {
  @override
  Size get preferredSize => Size.fromHeight(kToolbarHeight); // whatever height you want

  @override
  Widget build(BuildContext context) {
    return AppBar();
  }
}

class MyBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Text('Hello World'),
      ),
    );
  }
}

Of course if place them in a different file, you have to import it:

import 'package:myapp/widgets/some_widget.dart';
boformer
  • 28,207
  • 10
  • 81
  • 66