Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter - Implementing a Navigation drawer with a TabBarView widget with dynamic Tab view

I'm trying to do what I think is a very simple Flutter app, but I can't figure out what's going wrong with my code.

I have a Flutter app with a Drawer widget. I'm using this widget to make a Navigation drawer, the Drawer has a ListView as a child and the ListView contains the View options (A, B and C) which the user can select.

Since the main page of the app (MyHomePage) extends from StatefulWidget the only thing that I do to load a new view is to call the setState method to assign the new value of my "control variable" (viewName), then I expect that Flutter executes the Widget build(BuildContext context) method of MyHomePage but with the new value of viewName this time.

All of the above works as expected, the problem with this is that in the body field of the Scaffold I have a TabBarView widget, since I want to show to the user a view with two tabs (Tab 1 and Tab 2) per each view (A, B and C).

The TabBarView children are:

-A StatefulTab object for Tab 1

-A simple Center widget for Tab 2

What I want to demonstrate here is that when you tap an option of the Drawer (Load the B view for example) the Tab 2 changes as a expected it's value, but the Tab 1 (that contains a stateful widget) not changes it's value when you tap any other option of the Drawer

Note: I must use the same StatefulTab widget for the 3 views in order to reuse the code, since the only value which changes for the 3 views is the viewName variable.

Here is the code:

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_test/StatefulTab.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(viewName: 'A'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.viewName}) : super(key: key);

  final String viewName;

  @override
  _MyHomePageState createState() => new _MyHomePageState(viewName: viewName);
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  String viewName;

  _MyHomePageState({Key key, this.viewName});

  TabController controller;

  @override
  void initState() {
    super.initState();
    controller = new TabController(
      length: 2,
      vsync: this,
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    var tabs = <Tab>[
      new Tab(icon: new Icon(Icons.home), text: 'Tab 1'),
      new Tab(icon: new Icon(Icons.account_box), text: 'Tab 2')
    ];

    return new Scaffold(
      appBar: new AppBar(
        title: new Text(viewName),
      ),
      body: new TabBarView(controller: controller, children: <Widget>[
        new StatefulTab(viewName: viewName),
        new Center(child: new Text('This is the Tab 2 for view $viewName'))
      ]),
      bottomNavigationBar: new Material(
        color: Colors.blue,
        child: new TabBar(controller: controller, tabs: tabs),
      ),
      drawer: new Drawer(
        child: new ListView(
          children: <Widget>[
            new Padding(
              padding: new EdgeInsets.only(top: 50.0),
            ),
            new ClipRect(
              child: new Column(
                children: <Widget>[
                  new ListTile(
                    title: new Text('Tap to load view: A'),
                    onTap: () => _loadView('A', context),
                  ),
                  new ListTile(
                    title: new Text('Tap to load view: B'),
                    onTap: () => _loadView('B', context),
                  ),
                  new ListTile(
                    title: new Text('Tap to load view: C'),
                    onTap: () => _loadView('C', context),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  _loadView(String view, BuildContext context) {
    Navigator.of(context).pop();
    setState(() {
      if (view == 'A') {
        viewName = 'A';
      } else if (view == 'B') {
        viewName = 'B';
      } else if (view == 'C') {
        viewName = 'C';
      }
    });
  }
}

StatefulTab.dart

import 'package:flutter/material.dart';

class StatefulTab extends StatefulWidget {
  String viewName;

  StatefulTab({Key key, this.viewName}) : super(key: key);

  @override
  StatefulTabState createState() => new StatefulTabState(viewName: viewName);
}

class StatefulTabState extends State<StatefulTab> {
  String viewName;

  StatefulTabState({Key key, this.viewName});

  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new Text('This is the Tab 1 for View $viewName'),
    );
  }
}

How can I tell Flutter that takes the new value for the stateful wdiget of the Tab 1?

Is there a better way to implement a Navigation drawer with dynamic views?

Thanks in advance!

like image 485
SaloGala Avatar asked Aug 01 '17 11:08

SaloGala


2 Answers

I think I found your problem. You keep the viewName as State in your Homepage and additionally in the StatefulTab. This can't really work, because for the StatefulTab the state doesn't change only because the state of the HomePage changes. I came to that conclusion by inserting print statements in the two build methods. The build method of the HomePage acts according to your desired behavior (as you already saw in the header of the scaffold), but the build method of the StatefulTab kept its state.

Further investigating and various print statements in various places led me to the conclusion, that the constructor of the StatefulTabState is not called after one of the drawer buttons is clicked. Here is a working example of your StatefulTab:

class StatefulTab extends StatefulWidget {
  String viewName;

  StatefulTab({Key key, this.viewName}) : super(key: key);

  @override
  StatefulTabState createState() => new StatefulTabState();
}

class StatefulTabState extends State<StatefulTab> {
  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new Text('This is the Tab 1 for View ${widget.viewName}'),
    );
  }
}

Don't hesitate to comment, if you have any questions. It may be beneficial for you to have a look at this tutorial/documentation.

like image 196
Rainer Wittmann Avatar answered Oct 19 '22 03:10

Rainer Wittmann


For implement TabBar and drawer flutter provide us following widget.

1. TabController
2. TabBar
3. Drawer

I found a simple demo of TabBar and drawer.

like image 23
Sunil Avatar answered Oct 19 '22 01:10

Sunil