'I want to build a dynamic Tab Bar and corresponding pages which contains a list of items corresponding to that tab using complex JSON data in flutter

I'm trying to build a dynamic Tab bar using flutter that displays tabs with the tab names corresponding to my JSON data and then for each key I want to map the values of it as ListView in the corresponding Tabs. The JSON data is retrieved from a local assets file. I've tried multiple ways but still getting errors. Also, the code for ListView is working I,m just not able to figure out how to map it with JSON. Any help would be really appreciated.I'm sharing a reference of what I'm trying to build

This is my Last attempted code

JSON File

[
    {
        "Vegetables": [
            {
                "name": "Potato",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/potato.jpg",
                "description": "Very Healthy but high fat"
            },
            {
                "name": "Cabbage",
                "minOrder": 1,
                "pricePerKg": 10,
                "imageUrl": "assets/images/cabbage.jpg",
                "description": "Very Healthy but be aware"
            },
            {
                "name": "Lady Finger",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/lady_finger.jpg",
                "description": "Very Healthy green vegetable healthy for eyesight"
            },
            {
                "name": "Spinach",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/spinach.jpg",
                "description": "Very Healthy green leafy"
            },
            {
                "name": "Cauliflower",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/cauliflower.jpg",
                "description": "Very Healthy keeps doctor away"
            },
            {
                "name": "Capsicum",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/capsicum.jpg",
                "description": "Very Healthy and green vegetable"
            }
        ]
    },
    {
        "Fruits": [
            {
                "name": "Banana",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/banana.jpg",
                "description": "Very Healthy helps in gaining weight"
            },
            {
                "name": "Orange",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/orange.jpg",
                "description": "Very Healthy and citrus"
            },
            {
                "name": "Apple",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/apple.jpg",
                "description": "Very Healthy keeps doctor away"
            }
        ]
    },
    {
        "Bakery": [
            {
                "name": "Cake",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/apple.jpg",
                "description": "Very Healthy keeps doctor away"
            },
            {
                "name": "Cookies",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/apple.jpg",
                "description": "Very Healthy keeps doctor away"
            }
        ]
    },
    {
        "Fish": [
            {
                "name": "Gold Fish",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/apple.jpg",
                "description": "Very Healthy keeps doctor away"
            },
            {
                "name": "Luna",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/apple.jpg",
                "description": "Very Healthy keeps doctor away"
            }
        ]
    },
    {
        "Poultry": [
            {
                "name": "White Egg",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/apple.jpg",
                "description": "Very Healthy keeps doctor away"
            },
            {
                "name": "Brown Egg",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/apple.jpg",
                "description": "Very Healthy keeps doctor away"
            },
            {
                "name": "Chicken meat",
                "minOrder": 1.5,
                "pricePerKg": 12.5,
                "imageUrl": "assets/images/apple.jpg",
                "description": "Very Healthy keeps doctor away"
            }
        ]
    }
]

Dart Code

import 'dart:convert';

import 'package:asset_app/navigation/custom_bottom_nav_bar.dart';
import 'package:flutter/material.dart';

class ParsingJson extends StatefulWidget {
  const ParsingJson({Key key}) : super(key: key);

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

class _ParsingJsonState extends State<ParsingJson> {
  Future<String> jsonString;
  List _cards;
  Map _data;
  int _initPosition = 1;

  @override
  void initState() {
    super.initState();
    jsonString = DefaultAssetBundle.of(context)
        .loadString("assets/json_file/categories.json");
    // .then((d) {
    // _cards = json.decode(d);
    // setState(() => _data = _cards[0]);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("JSON parsing demo"),
        ),
        body: SafeArea(
            child: FutureBuilder(
                future: jsonString,
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    // Decode the JSON
                    Map newData = json.decode(snapshot.data.toString())[0];
                    return CustomTabView(
                      initPosition: _initPosition,
                      itemCount: (newData.keys as List).length + 1,
                      tabBuilder: (context, index) =>
                          Tab(text: (newData.keys.toList())[index]),
                      pageBuilder: (context, index) =>
                          Center(child: Text("data[index]")),
                      onPositionChange: (index) {
                        print('current position: $index');
                        _initPosition = index;
                      },
                      onScroll: (position) => print('$position'),
                    );
                  }
                  return CircularProgressIndicator();
                })));
  }
}

Tab Bar Code

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({
    @required this.itemCount,
    @required this.tabBuilder,
    @required this.pageBuilder,
    this.stub,
    this.onPositionChange,
    this.onScroll,
    this.initPosition,
  });

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

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

  @override
  void initState() {
    _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();
  }

  @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);
    }
  }
}

Error- The following _CastError was thrown building FutureBuilder(dirty, state: _FutureBuilderState#6d218): type '_CompactIterable' is not a subtype of type 'List' in type cast



Solution 1:[1]

Wrong here :

itemCount: (newData.keys as List).length + 1,

Because newData.keys is _CompactIterable so can't cast to list

=> itemCount: newData.keys.toList().length + 1,

Solution 2:[2]

And need to change like this

[
    {
        "data":[
            {   
                "title": "Vegetables",
                "Vegetables": [
                    {
                        "name": "Potato",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy but high fat"
                    },
                    {
                        "name": "Cabbage",
                        "minOrder": 1,
                        "pricePerKg": 10,
                        "description": "Very Healthy but be aware"
                    },
                    {
                        "name": "Lady Finger",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy green vegetable healthy for eyesight"
                    },
                    {
                        "name": "Spinach",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy green leafy"
                    },
                    {
                        "name": "Cauliflower",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy keeps doctor away"
                    },
                    {
                        "name": "Capsicum",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy and green vegetable"
                    }
                ]
            },
            {
                "title": "Fruits",
                "Fruits": [
                    {
                        "name": "Banana",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy helps in gaining weight"
                    },
                    {
                        "name": "Orange",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy and citrus"
                    },
                    {
                        "name": "Apple",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy keeps doctor away"
                    }
                ]
            },
            {
                "title": "Bakery",
                "Bakery": [
                    {
                        "name": "Cake",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy keeps doctor away"
                    },
                    {
                        "name": "Cookies",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy keeps doctor away"
                    }
                ]
            },
            {
                "title": "Fish",
                "Fish": [
                    {
                        "name": "Gold Fish",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy keeps doctor away"
                    },
                    {
                        "name": "Luna",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy keeps doctor away"
                    }
                ]
            },
            {
                "title": "Poultry",
                "Poultry": [
                    {
                        "name": "White Egg",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy keeps doctor away"
                    },
                    {
                        "name": "Brown Egg",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy keeps doctor away"
                    },
                    {
                        "name": "Chicken meat",
                        "minOrder": 1.5,
                        "pricePerKg": 12.5,
                        "description": "Very Healthy keeps doctor away"
                    }
                ]
            }
        ]
    }
]

and code change to this

FutureBuilder(
            future: jsonString,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                // Decode the JSON
                Map newData = json.decode(snapshot.data.toString())[0];
                List<dynamic> data = newData.entries.toList()[0].value;
                return CustomTabView(
                  initPosition: _initPosition,
                  itemCount: data.length,
                  tabBuilder: (context, i) =>
                      Tab(text: "${data[i]['title']}"),
                  pageBuilder: (context, index){
                    var name = data[index]['title'];
                    List<dynamic> itemData = data[index][name];
                    return ListView.builder(
                      itemCount: itemData.length,
                      itemBuilder: (context,int i){
                        return Text("${itemData[i]['name']}");
                      },
                    );
                  },
                  onPositionChange: (index) {
                    // print('current position: $index');
                    // _initPosition = index;
                  },
                  onScroll: (position) => print('$position'),
                );
              }
              return CircularProgressIndicator();
            }
          )
        )
      );

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Hades
Solution 2 San Sila