'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.
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 |