Zeffry Reynando
Zeffry Reynando

Reputation: 3899

Dynamic number of tabs

I have design like above , my tabbar is inside BottomNavigationBar and i want place the tabbar in my BottomNavigationBar menu. The problem is , i don't want have nested scaffold and i don't want use TabController because my tabs is dynamic , tabs length can be decrease or increase depending of user adding it. If i use TabController and define it inside initState , the tabs can't be increase/decrease because in initState only once define the value.

How can i do this ?

My expectation similiar in below example , when i adding tab and tabMenu it will increase . But get this error

The following assertion was thrown building TabBar(dirty, dependencies: [_InheritedTheme,
_LocalizationsScope-[GlobalKey#a56a4]], state: _TabBarState#fb337):
Controller's length property (2) does not match the number of tabs (3) present in TabBar's tabs
property.
The relevant error-causing widget was:
  TabBar

Dummy Example


class TaskScreen extends StatefulWidget {
  @override
  _TaskScreenState createState() => _TaskScreenState();
}

class _TaskScreenState extends State<TaskScreen> with SingleTickerProviderStateMixin {
  TabController _tabController;

  final List<Tab> _tab = [
    Tab(text: 'Sunday'),
    Tab(text: 'Monday'),
  ];
  final List<Widget> _tabMenu = [
    Text('This is Sunday'),
    Text('This is Monday'),
  ];

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: _tab.length, vsync: this);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        FlatButton(
            onPressed: () {
              setState(() {
                _tab.add(Tab(
                  text: '1',
                ));
                _tabMenu.add(Text('1'));
              });
              print('tab ${_tab.length}');
              print('tabMenu ${_tabMenu.length}');
            },
            child: Text('add')),
        Container(
          color: colorPallete.primaryColor,
          padding: EdgeInsets.only(top: sizes.statusBarHeight(context)),
          child: TabBar(
            tabs: _tab.map((e) => e).toList(),
            controller: _tabController,
          ),
        ),
        Expanded(
          child: TabBarView(
            children: _tabMenu.map((e) => e).toList(),
            controller: _tabController,
          ),
        )
      ],
    );
  }
}

Upvotes: 8

Views: 5073

Answers (1)

Kherel
Kherel

Reputation: 16185

use TickerProviderStateMixin instead of SingleTickerProviderStateMixin and change TabController on changing tabs number;

add isScrollable: true when you don't have space for all your tabs

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

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

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return TaskScreen();
  }
}

class TaskScreen extends StatefulWidget {
  @override
  _TaskScreenState createState() => _TaskScreenState();
}

class _TaskScreenState extends State<TaskScreen> with TickerProviderStateMixin {
  TabController _tabController;

  final List<Tab> _tab = [
    Tab(text: 'Sunday'),
    Tab(text: 'Monday'),
  ];
  final List<Widget> _tabMenu = [
    Text('This is Sunday'),
    Text('This is Monday'),
  ];

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: _tab.length, vsync: this);
  }


  @override
  void dispose() {
    _tabController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        FlatButton(
            onPressed: () {
              setState(() {
                _tab.add(
                  Tab(
                    text: '1',
                  ),
                );
                _tabMenu.add(
                  Text('1'),
                );
                _tabController.dispose();
                _tabController =
                    TabController(length: _tab.length, vsync: this);
              });
              print('tab ${_tab.length}');
              print('tabMenu ${_tabMenu.length}');
            },
            child: Text('add')),
        Container(
          color: Colors.blue,
          padding: EdgeInsets.only(top: 10),
          child: TabBar(
            // isScrollable: true, 
            tabs: _tab.map((e) => e).toList(),
            controller: _tabController,
          ),
        ),
        Expanded(
          child: TabBarView(
            children: _tabMenu.map((e) => e).toList(),
            controller: _tabController,
          ),
        )
      ],
    );
  }
}

Upvotes: 13

Related Questions