Skip to content Skip to sidebar Skip to footer

Dynamic Children For Tabview In Flutter

I'm trying to build a Tabbed View that has lists as children. Both the Category labels and the lists content will be fetched from a database. I am passing the labels from the calle

Solution 1:

You can use List<E>.generate to achieve this.

import'package:flutter/material.dart';

Say you have a set of categories passed from your caller page. And let's say this is your list of categories.

List<String> categories = ["a", "b", "c", "d", "e", "f", "g", "h"];

Then you can do something like this to achieve what you desire.

classTabsDemoextendsStatefulWidget {
  @override
  _TabsDemoState createState() => _TabsDemoState();
}

class_TabsDemoStateextendsState<TabsDemo> {
  TabController _controller;

  @overridevoidinitState() {
    super.initState();
  }

    @override
    Widget build(BuildContext ctxt) {
      returnnewMaterialApp(
        home: DefaultTabController(
            length: categories.length,
            child: newScaffold(
              appBar: newAppBar(
                title: newText("Title"),
                bottom: newTabBar(
                  isScrollable: true,
                    tabs: List<Widget>.generate(categories.length, (int index){
                  print(categories[0]);
                  returnnewTab(icon: Icon(Icons.directions_car), text: "some random text");

                }),

              ),
            ),

        body: newTabBarView(
             children: List<Widget>.generate(categories.length, (int index){
                print(categories[0]);
                returnnewText("again some random text");

             }),
          )
       ))
      );
  }

You can also set different set of widgets as the Tab's view. You can create a list of pages and follow the same method.

Solution 2:

Absolutely true List<E>.generate best solution to solve.

Problems arise if you need to modify the arrays. They consist in the fact that when modifying an array you do not have the opportunity to use the same controller.

enter image description here

You can use the next custom widget for this case:

import'package:flutter/material.dart';

  voidmain() => runApp(MyApp());

  classMyAppextendsStatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Flutter Demo',
        home: MyHomePage(),
      );
    }
  }

  classMyHomePageextendsStatefulWidget {
    @override
    _MyHomePageState createState() => _MyHomePageState();
  }

  class_MyHomePageStateextendsState<MyHomePage> {
    List<String> data = ['Page 0', 'Page 1', 'Page 2'];
    intinitPosition=1;

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: SafeArea(
          child: CustomTabView(
            initPosition: initPosition,
            itemCount: data.length,
            tabBuilder: (context, index) => Tab(text: data[index]),
            pageBuilder: (context, index) => Center(child: Text(data[index])),
            onPositionChange: (index){
              print('current position: $index');
              initPosition = index;
            },
            onScroll: (position) => print('$position'),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              data.add('Page ${data.length}');
            });
          },
          child: Icon(Icons.add),
        ),
      );
    }
  }

  /// ImplementationclassCustomTabViewextendsStatefulWidget {
    finalint itemCount;
    final IndexedWidgetBuilder tabBuilder;
    final IndexedWidgetBuilder pageBuilder;
    final Widget stub;
    final ValueChanged<int> onPositionChange;
    final ValueChanged<double> onScroll;
    finalint initPosition;

    CustomTabView({
      @requiredthis.itemCount,
      @requiredthis.tabBuilder,
      @requiredthis.pageBuilder,
      this.stub,
      this.onPositionChange,
      this.onScroll,
      this.initPosition,
    });

    @override
    _CustomTabsState createState() => _CustomTabsState();
  }

  class_CustomTabsStateextendsState<CustomTabView> with TickerProviderStateMixin {
    TabController controller;
    int _currentCount;
    int _currentPosition;

    @overridevoidinitState() {
      _currentPosition = widget.initPosition ?? 0;
      controller = TabController(
        length: widget.itemCount,
        vsync: this,
        initialIndex: _currentPosition,
      );
      controller.addListener(onPositionChange);
      controller.animation.addListener(onScroll);
      _currentCount = widget.itemCount;
      super.initState();
    }

    @overridevoiddidUpdateWidget(CustomTabView oldWidget) {
      if (_currentCount != widget.itemCount) {
        controller.animation.removeListener(onScroll);
        controller.removeListener(onPositionChange);
        controller.dispose();

        if (widget.initPosition != null) {
          _currentPosition = widget.initPosition;
        }

        if (_currentPosition > widget.itemCount - 1) {
            _currentPosition = widget.itemCount - 1;
            _currentPosition = _currentPosition < 0 ? 0 : 
            _currentPosition;
            if (widget.onPositionChange is ValueChanged<int>) {
               WidgetsBinding.instance.addPostFrameCallback((_){
                if(mounted) {
                  widget.onPositionChange(_currentPosition);
                }
               });
            }
         }

        _currentCount = widget.itemCount;
        setState(() {
          controller = TabController(
            length: widget.itemCount,
            vsync: this,
            initialIndex: _currentPosition,
          );
          controller.addListener(onPositionChange);
          controller.animation.addListener(onScroll);
        });
      } elseif (widget.initPosition != null) {
        controller.animateTo(widget.initPosition);
      }

      super.didUpdateWidget(oldWidget);
    }

    @overridevoiddispose() {
      controller.animation.removeListener(onScroll);
      controller.removeListener(onPositionChange);
      controller.dispose();
      super.dispose();
    }

    @override
    Widget build(BuildContext context) {
      if (widget.itemCount < 1) return widget.stub ?? Container();

      return Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          Container(
            alignment: Alignment.center,
            child: TabBar(
              isScrollable: true,
              controller: controller,
              labelColor: Theme.of(context).primaryColor,
              unselectedLabelColor: Theme.of(context).hintColor,
              indicator: BoxDecoration(
                border: Border(
                  bottom: BorderSide(
                    color: Theme.of(context).primaryColor,
                    width: 2,
                  ),
                ),
              ),
              tabs: List.generate(
                widget.itemCount,
                    (index) => widget.tabBuilder(context, index),
              ),
            ),
          ),
          Expanded(
            child: TabBarView(
              controller: controller,
              children: List.generate(
                widget.itemCount,
                    (index) => widget.pageBuilder(context, index),
              ),
            ),
          ),
        ],
      );
    }

    onPositionChange() {
      if (!controller.indexIsChanging) {
        _currentPosition = controller.index;
        if (widget.onPositionChange is ValueChanged<int>) {
          widget.onPositionChange(_currentPosition);
        }
      }
    }

    onScroll() {
      if (widget.onScroll is ValueChanged<double>) {
        widget.onScroll(controller.animation.value);
      }
    }
  }

Solution 3:

You can use dynamic children using for loop within your Tabbarview Widget

List<String> categories = ["category 1" , "category 2", "category 3",];
returnTabBarView(
  children:[
    for(var category in categories)
      Text(category), // this widget will show a text with specific category. You can use any other widget
  ],
);

Solution 4:

Null safety version

import 'package:flutter/material.dart';

class CustomTabView extends StatefulWidget {
  final int? itemCount;
  final IndexedWidgetBuilder? tabBuilder;
  final IndexedWidgetBuilder? pageBuilder;
  final Widget? stub;
  final ValueChanged<int>? onPositionChange;
  final ValueChanged<double>? onScroll;
  final int? initPosition;


  CustomTabView({this.itemCount, this.tabBuilder, this.pageBuilder, this.stub,
    this.onPositionChange, this.onScroll, this.initPosition});

  @override
  _CustomTabsState createState() => _CustomTabsState();
}

class _CustomTabsState extends State<CustomTabView> with TickerProviderStateMixin {
  late TabController controller;
  late int _currentCount;
  late int _currentPosition;

  @override
  void initState() {
    _currentPosition = widget.initPosition!;
    controller = TabController(
      length: widget.itemCount!,
      vsync: this,
      initialIndex: _currentPosition,
    );
    controller.addListener(onPositionChange);
    controller.animation!.addListener(onScroll);
    _currentCount = widget.itemCount!;
    super.initState();
  }

  @override
  void didUpdateWidget(CustomTabView oldWidget) {
    if (_currentCount != widget.itemCount) {
      controller.animation!.removeListener(onScroll);
      controller.removeListener(onPositionChange);
      controller.dispose();

      if (widget.initPosition != null) {
        _currentPosition = widget.initPosition!;
      }

      if (_currentPosition > widget.itemCount! - 1) {
        _currentPosition = widget.itemCount! - 1;
        _currentPosition = _currentPosition < 0 ? 0 :
        _currentPosition;
        if (widget.onPositionChange is ValueChanged<int>) {
          WidgetsBinding.instance!.addPostFrameCallback((_){
            if(mounted) {
              widget.onPositionChange!(_currentPosition);
            }
          });
        }
      }

      _currentCount = widget.itemCount!;
      setState(() {
        controller = TabController(
          length: widget.itemCount!,
          vsync: this,
          initialIndex: _currentPosition,
        );
        controller.addListener(onPositionChange);
        controller.animation!.addListener(onScroll);
      });
    } else if (widget.initPosition != null) {
      controller.animateTo(widget.initPosition!);
    }

    super.didUpdateWidget(oldWidget);
  }

  @override
  void dispose() {
    controller.animation!.removeListener(onScroll);
    controller.removeListener(onPositionChange);
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (widget.itemCount! < 1) return widget.stub ?? Container();

    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: <Widget>[
        Container(
          alignment: Alignment.center,
          child: TabBar(
            isScrollable: true,
            controller: controller,
            labelColor: Theme.of(context).primaryColor,
            unselectedLabelColor: Theme.of(context).hintColor,
            indicator: BoxDecoration(
              border: Border(
                bottom: BorderSide(
                  color: Theme.of(context).primaryColor,
                  width: 2,
                ),
              ),
            ),
            tabs: List.generate(
              widget.itemCount!,
                  (index) => widget.tabBuilder!(context, index),
            ),
          ),
        ),
        Expanded(
          child: TabBarView(
            controller: controller,
            children: List.generate(
              widget.itemCount!,
                  (index) => widget.pageBuilder!(context, index),
            ),
          ),
        ),
      ],
    );
  }

  onPositionChange() {
    if (!controller.indexIsChanging) {
      _currentPosition = controller.index;
      if (widget.onPositionChange is ValueChanged<int>) {
        widget.onPositionChange!(_currentPosition);
      }
    }
  }

  onScroll() {
    if (widget.onScroll is ValueChanged<double>) {
      widget.onScroll!(controller.animation!.value);
    }
  }
}

Post a Comment for "Dynamic Children For Tabview In Flutter"