Flutter Lazy loading on Listview inside another Listview - flutter

How to apply Lazy Loading or other better option to load more than 5000 of topping list in the Listview.builder inside another Listview.builder?
Below is post.json which will be loaded from local assets
{
"Food": [
{
"name": "Cake",
"id": "0001",
"description": [
{
"category": "AAA",
"Size": "Regular",
"topping": []
},
{
"category": "BBB",
"Size": "Small",
"topping": []
},
{
"category": "CCC",
"Size": "Medium",
"topping": [
{
"ingredient": "Chocolate with Sprinkles",
"price": "$70"
},
{
"ingredient": "Maple",
"price": "$99"
},
{
"ingredient": "Blueberry",
"price": "$123"
}, ... // more than 5000 of topping list
]
}
]
},
{
"name": "Raised",
"id": "0002",
"description": ... // same structure as above
}
]
}
Below is the Main file to display List of foods
void main() {
runApp(const MyApp());
}
// Load local Json file
Future<Post> getFoodList() => Future.delayed(Duration(seconds: 1), () async {
final getResponse = await rootBundle.loadString('assets/post.json');
var data = jsonDecode(getResponse);
return Post.fromJson(data);
});
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: FutureBuilder<Post>(
future: getFoodList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var getFoodName = snapshot.data!.food;
return SingleChildScrollView(
child: Wrap(
children: [
for (final food in getFoodName!)
GestureDetector(
child: Text(foodName.name.toString()),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => foodDetail(food: food)));
},
),
],
),
);
} else {
return Text('Loading');
}
},
),
),
],
),
);
}
}
Food Detail page - display more than 5000 of topping list
class FoodDetail extends StatefulWidget {
final Food food;
const FoodDetail({super.key, required this.food});
#override
State<FoodDetail> createState() => _FoodDetailState();
}
class _FoodDetailState extends State<FoodDetail> {
late ScrollController controller;
// This example is taken from Flutter ListView lazy loading
List<String> items = List.generate(100, (position) => 'Hello $position');
#override
void initState() {
super.initState();
controller = ScrollController()..addListener(_scrollListener);
}
#override
void dispose() {
controller.removeListener(_scrollListener);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: Column(
children: [
// This is the first ListView.build to display category and size under description
ListView.builder(
itemCount: widget.food.description!.length,
itemBuilder: (context, index) {
// IF category is AAA || BBB, display the size
if (widget.food.description![index].category == "AAA" || widget.food.description![index].category == "BBB") {
return Text(widget.food.description![index].size.toString());
}
// IF category is CCC display the List of Topping (Note: this topping list is more than 5000)
else {
// This is the second ListView.build to display the topping information
// How do I apply ScollController or Lazy Loading to load 5000 list of topping?
return ListView.builder(
controller: controller,
itemCount: widget.food.description![index].topping!.length,
itemBuilder: (context, position) {
return Column(
children: [
Text(widget.food.description![index].topping![position].ingredient.toString())
Text(widget.food.description![index].topping![position].price.toString())
],
);
});
}
}),
],
),
)));
}
// This example is taken from Flutter ListView lazy loading
void _scrollListener() {
if (controller.position.extentAfter < 500) {
setState(() {
items.addAll(List.generate(42, (index) => 'Inserted $index'));
});
}
}
}
Output of the workflow:

I think your best bet is to try to try to refactor it in a way to use only a single ListView.builder. To do this you need to flatten the data you are working with in some way. Here is an example that also uses nested data that transforms it in a way so a single ListView displays it. I'm not sure if it's the best way to do it but I think you could do something similar for your code maybe. This might give you an idea of how to do it:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
final data = const [
{
"name": "A",
"subnames": ["A1", "A2", "A3", "A4", "A5"]
},
{
"name": "B",
"subnames": ["B1", "B2", "B3", "B4", "B5"]
},
{
"name": "C",
"subnames": ["C1", "C2", "C3", "C4", "C5"]
},
];
const MyApp({super.key});
#override
Widget build(BuildContext context) {
final newData = data
.map((e) => [
{"type": 1, "name": e["name"]},
for (final name in (e["subnames"] as List))
{"type": 2, "name": name}
])
.expand((e) => e.toList())
.toList();
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.builder(
itemCount: newData.length,
itemBuilder: (context, index) {
if (newData[index]['type'] == 1) {
return Text(
newData[index]['name'],
style: const TextStyle(color: Colors.red),
);
} else {
return Text(
newData[index]['name'],
style: const TextStyle(color: Colors.blue),
);
}
}),
)),
);
}
}
Output:

Related

Flutter Dynamic TabBar for data coming from FireStore Collection

I have a FireStore collection named "products" and in there I have documents consisting of product data such as name, price, and category. It follows the structure like this
{"name": "Milk Shake Strawberry",
"price": "250",
"category": "Drinks"
},
{"name": "Swiss Roll",
"price": "150",
"category": "Cake"
}
.
I want to create the UI to show Each category as a Tab (ex: Drinks Tab, Cake Tab) and inside that tab, I want to show products related to that exact category.
How can I achieve this in Flutter?
Try this
class ProductCategoryTabs extends StatefulWidget {
#override
_ProductCategoryTabsState createState() => _ProductCategoryTabsState();
}
class _ProductCategoryTabsState extends State<ProductCategoryTabs> {
List<String> _tabs = [];
Map<String, List<Product>> _products = {};
#override
void initState() {
super.initState();
// Fetch the list of categories and products from Firestore
Firestore.instance.collection('products').getDocuments().then((snapshot) {
snapshot.documents.forEach((document) {
var product = Product.fromFirestore(document);
if (!_tabs.contains(product.category)) {
_tabs.add(product.category);
}
if (_products[product.category] == null) {
_products[product.category] = [product];
} else {
_products[product.category].add(product);
}
});
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: _tabs.length,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: _tabs.map((String tab) {
return Tab(text: tab);
}).toList(),
),
),
body: TabBarView(
children: _tabs.map((String tab) {
return ListView.builder(
itemCount: _products[tab].length,
itemBuilder: (BuildContext context, int index) {
return ProductTile(product: _products[tab][index]);
},
);
}).toList(),
),
),
);
}
}

How to appy Lazy Loading on the local JSON data

I have a local JSON file with a 1000 list. How to apply the Lazy Loading or scrollController function in the Listview.builder?
P/S: I have tried this Flutter ListView lazy loading, but I don't know how to modify it. For example: List items = List.generate(100, (position) => 'Hello $position'); how to change this part to the data that loaded from local JSON file?
Below is post.json file
{
"Food": [
{
"name": "Cake",
"id": "0001",
"description": [
{
"category": "AAA",
"Size": "Regular",
"topping": "Chocolate with Sprinkles"
},
{
"category": "AAA",
"Size": "Small",
"topping": "Chocolate only"
},
{
"category": "BBB",
"Size": "Regular",
"topping": "Maple"
},
{
"category": "BBB",
"Size": "Small",
"topping": "Blueberry"
},
{
"category": "CCC",
"Size": "Medium",
"topping": "Strawberry"
},
{
"category": "CCC",
"Size": "small",
"topping": "banana"
},
{
....another 1000 list
}
]
},
{
"name": "Raised",
"id": "0002",
"description": ... // same structure as above
}
]
}
Below is the Main file
void main() {
runApp(const MyApp());
}
// Load local Json file
Future<Post> getFoodList () async {
final getResponse = await rootBundle.loadString('assets/post.json');
var data = jsonDecode(getResponse);
return Post.fromJson(data);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: FutureBuilder<Post>(
future: getFoodList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var getFoodName = snapshot.data!.food;
return SingleChildScrollView(
child: Wrap(
children: [
for (final food in getFoodName!)
GestureDetector(
child: Text(foodName.name.toString()),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => foodDetail(food: food)));
},
),
],
),
);
} else {
return Text('Loading');
}
},
),
),
],
),
);
}
}
Below is the food detail page. As there will be a 1000 lists of food details, how do I apply lazy loading, scrollController?
class FoodDetail extends StatefulWidget {
final Food food;
const FoodDetail({super.key, required this.food});
#override
State<FoodDetail> createState() => _FoodDetailState();
}
class _FoodDetailState extends State<FoodDetail> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
itemCount: widget.food.description!.length,
itemBuilder: (context, index) {
return Column(
children: [
Text(widget.food.description![index].category.toString())
Text(widget.food.description![index].topping.toString())
],
);
}
),
],
),
)));
}
}

Flutter build TabBarView items via server response

in one of our mobile application we have a simple screen which we want to make TabBarView pages via result of server response, for example if server response this collection:
{
"categories": [
{
/* has 3 children */
"category_item": {
"id": 1,
"title": "sample"
},
"products": [
{
"id": 1,
"title": "sample 1"
},
{
"id": 2,
"title": "sample 1"
},
{
"id": 3,
"title": "sample 1"
}
]
},
/* has 2 children */
{
"category_item": {
"id": 2,
"title": "test"
},
"products": [
{
"id": 1,
"title": "test 1"
},
{
"id": 2,
"title": "test 1"
}
]
}
]
}
we should have 2 Tabs as sample and test with some children inside TabBarView relatives with parent category, for example first index of array has 3 children and second index of array has 2 children.
our problem is TabController that when we retrieve data from server, screen has been built and we can't manage this action. I mean this tab and TabBarView children should be built with response data. basically time I passed zero to TabController because server doesn't response in this time and after getting data I get error on length of that,
class SelectClothesScreenState extends State<SelectClothesScreen> with SingleTickerProviderStateMixin {
TabController _tabController;
CategoryProducts _cats;
List<Products> _productList = [];
#override
void initState() {
super.initState();
_getCats();
}
bool _isLoading = true;
_getCats() async {
final CategoryProducts response = await Api.apiCatList();
List<Products> _p = [];
setState(() {
_cats = response;
_productList = _p;
_isLoading = false;
});
_tabController = TabController(length: _cats == null ? 0 : _cats.categories.length ?? 0, vsync: this);
}
Widget loadingView() {
return CircularProgressIndicator();
}
#override
Widget build(BuildContext context) {
var fullWidth = MediaQuery.of(context).size.width;
return Scaffold(
resizeToAvoidBottomInset: false,
body: Column(
children: [
//address and search icon and close icon
Container(
height: 50,
child: _cats != null && _isLoading
? loadingView()
: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.all(8),
itemCount: _cats == null ? 0 : _cats.categories.length,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
print(_cats.categories.length);
return GestureDetector(
onTap: () {
print(index);
_tabController.animateTo(index, duration: Duration(milliseconds: 750), curve: Curves.ease);
},
child: CatItem(catView: _cats.categories[index].categoryItem),
);
},
),
),
//content grid list product and btn confirm
Container(
width: fullWidth,
child: TabBarView(controller: _tabController, children: [
//grid product list
...List.generate(_cats == null ? 0 : _cats.categories.length, (index) {
return Container(
width: fullWidth,
child: GridView.builder(
shrinkWrap: true,
itemCount: _productList.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return Container(color: Colors.black);
//return ProductView(productView: _productList[index]);
},
),
);
}),
]),
),
],
));
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
}
}
how can I set correct implementation for length of TabController after retrieving data from server? thanks in advance

Flutter: How to filter data from JSON based on TabBar and ToggleButtons?

I have a JSON like this:
[
{
"continentName": "NA",
"isDayTime": true,
"seasonName": "Spring",
"cityName": "United States",
"xAlign": 45.4,
"yAlign": 69,
"cityTemperature": 27
},
{
"continentName": "NA",
"isDayTime": true,
"seasonName": "Spring",
"cityName": "Canada",
"xAlign": 35.7,
"yAlign": 53,
"cityTemperature": 16
},
{
"continentName": "NA",
"isDayTime": true,
"seasonName": "Summer",
"cityName": "Mexico",
"xAlign": 87.8,
"yAlign": 41.8,
"cityTemperature": 28
},
{
"continentName": "NA",
"isDayTime": false,
"seasonName": "Summer",
"cityName": "Cuba",
"xAlign": 55.3,
"yAlign": 88.8,
"cityTemperature": 27
},
{
"continentName": "EU",
"isDayTime": true,
"seasonName": "Winter",
"cityName": "Germany",
"xAlign": 33.8,
"yAlign": 38.8,
"cityTemperature": 3
}
]
I want to display the filtered data as follows:
The 1st filter is TabBar ("continentName")
The 2nd filter is ToggleButtons ("isDayTime") => requiring at least one selection
The 3rd filter is ToggleButtons ("listSeason") => mutually exclusive selection, but allows for none of the buttons to be selected.
When start the page, by default, Tabbar is selected as "NA", the first toggleButtons ("isDayTime") is selected as "Day" => I want that if click on "Spring" => it will display the satisfying data, specifically here will be "United States" and "Canada"
So please help me, this is main file:
import 'package:ask/model/temperature_model.dart';
import 'package:ask/services/temperature_service.dart';
import 'package:flutter/material.dart';
class CityTemperature extends StatefulWidget {
CityTemperature() : super();
#override
_CityTemperatureState createState() => _CityTemperatureState();
}
class _CityTemperatureState extends State<CityTemperature> {
List<Temperature> _temperature = [];
List<bool> isDayTime = [true, false];
List<bool> listSeason = [false, false, false, false];
#override
void initState() {
super.initState();
TemperatureServices.getTemperature().then((temperature) {
setState(() {
_temperature = temperature;
});
});
}
#override
Widget build(BuildContext context) {
return Container(
child: DefaultTabController(
length: 4,
child: Scaffold(
appBar: AppBar(
title: Text('Temperature'),
bottom: TabBar(tabs: [
Tab(child: Text('NA')),
Tab(child: Text('EU')),
Tab(child: Text('Africa')),
Tab(child: Text('Asia')),
]),
),
body: Column(children: [
Center(
child: ToggleButtons(
children: [Text('Day'), Text('Night')],
onPressed: (int index) {
setState(() {
for (int buttonIndex = 0; buttonIndex < isDayTime.length; buttonIndex++) {
if (buttonIndex == index) {
isDayTime[buttonIndex] = true;
} else {
isDayTime[buttonIndex] = false;
}
}
});
},
isSelected: isDayTime)),
SizedBox(height: 5),
Center(
child: ToggleButtons(
children: [Text('Spring'), Text('Summer'), Text('Autumn'), Text('Winter')],
onPressed: (int index) {
setState(() {
for (int buttonIndex = 0; buttonIndex < listSeason.length; buttonIndex++) {
if (buttonIndex == index) {
listSeason[buttonIndex] = !listSeason[buttonIndex];
} else {
listSeason[buttonIndex] = false;
}
}
});
},
isSelected: listSeason)),
SizedBox(height: 5),
Expanded(
child: TabBarView(children: [
Column(children: [ // How to display the satisfying data
for (Temperature temp in _temperature)
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(temp.cityName),
Text('${temp.cityTemperature.toString()}° C'),
],
)
]),
Column(), // How to display the satisfying data
Column(), // How to display the satisfying data
Column(), // How to display the satisfying data
]),
)
]))));
}
}
Edit 1:
I want to add 2 things as follows:
1. Add background image in TabBarView for each _tabs and each
isDayTime
For each continentName, there will be 2 images for Day or Night.
Because it is an image, I think I will put it in Assets for users to load faster. Besides, to avoid creating more data on json => I will create the filename of image as: "na_day.png" or "na_true.png" and access it by: Image.asset('assets/${temp.continentName}_${isDayTime}.png') or something like that
2. Display cityName on background image based on X Y percent position of image
I use data from JSON: xAlign & yAlign to determine the position of cityName on the image (JSON updated)
As far as I know, it seems the best way is used IntrinsicHeight, Stack and Align to do like this:
class DisplayCountry extends StatelessWidget {
final List<Temperature> countries;
DisplayCountry({this.countries});
#override
Widget build(BuildContext context) {
return Column(children: [
for (Temperature temp in countries) // I don't know where to put this
IntrinsicHeight(
child: Stack(children: [
Image.asset('assets/${temp.continentName}_${isDayTime}.png'.asset), // Or something like this
Align(
alignment: Alignment(temp.xAlign / 100 * 2 - 1, temp.yAlign / 100 * 2 - 1),
child: Text(temp.cityName),
),
]),
)
]);
}
}
extension AssetsExtension on String {
String get asset => this.toLowerCase().replaceAll(" ", "_").replaceAll("'", "_");
}
So please help me update class DisplayCountry to be able to combine the 2 things above
something like this
class CityTemperature extends StatefulWidget {
CityTemperature() : super();
#override
_CityTemperatureState createState() => _CityTemperatureState();
}
class _CityTemperatureState extends State<CityTemperature> {
List<Temperature> _temperature = [];
List<String> _tabs = [];
Map<String, bool> isDayTime = {'Day': true, 'Night': false};
Map<String, bool> listSeason = {'Spring': false, 'Summer': false, 'Autumn': false, 'Winter': true};
#override
void initState() {
super.initState();
var response = json.decode(jsonFile);
_temperature = List<Temperature>.from(response.map((x) => Temperature.fromJson(x)));
_tabs = _temperature.map<String>((x) => x.continentName).toSet().toList();
/*
TemperatureServices.getTemperature().then((temperature) {
setState(() {
_temperature = temperature;
});
});*/
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: _tabs.length,
child: Scaffold(
appBar: AppBar(
title: Text('Temperature'),
bottom: TabBar(
tabs: _tabs.map((String name) => Tab(text: name)).toList()
),
),
body: Column(children: [
Center(
child: ToggleButtons(
children: isDayTime.keys.map((key) => Text(key)).toList(),
onPressed: (int index) {
String indexKey = isDayTime.keys.toList()[index];
setState(() {
isDayTime.updateAll(
(key, value) => key == indexKey ? true : false);
}
);
},
isSelected: isDayTime.values.toList())),
SizedBox(height: 5),
Center(
child: ToggleButtons(
children: listSeason.keys.map((key) => Text(key)).toList(),
onPressed: (int index) {
String indexKey = listSeason.keys.toList()[index];
setState(() {
listSeason.updateAll(
(key, value) => key == indexKey ?
!listSeason[indexKey] : false);
});
},
isSelected: listSeason.values.toList())),
SizedBox(height: 5),
Expanded(
child: TabBarView(
children: _tabs.map((String name) {
return DisplayCountry(
countries: List<Temperature>.from(_temperature)
..retainWhere((temperature) =>
temperature.continentName == name
&& temperature.isDayTime == isDayTime['Day']
&& temperature.seasonName == listSeason.keys.firstWhere(
(k) => listSeason[k] == true, orElse: () => 'Nothing'))
);
}).toList()
),
)
]
)
)
);
}
}
class DisplayCountry extends StatelessWidget{
final List<Temperature> countries;
DisplayCountry({this.countries});
#override
Widget build(BuildContext context){
return Column(
children: [
for(Temperature temp in countries)
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(temp.cityName),
Text('${temp.cityTemperature.toString()}° C'),
],
)
]
);
}
}
I create a list called _tabs with all the continentName of _temperatures, then added toSet and toList. toSet converts it to a set, a set is an iterable that doesn't allow repeated values, and then I converted it back to list, that way I have a list of unique continentName (NA, EU, etc).
In DefaultTabController I add _tabs.length and in tabView I create a list of _tab.map, which creates a list of widgets of DisplayCountry, I use retainwhere to keep only the ones that satisfies the conditions (same continentName that the one in the tab, same seasonName that the one selected and isDayTime if it's true is day else night)
UPDATE
class DisplayImage extends StatelessWidget {
final List<Temperature> countries;
final String continentName;
final bool isDayTime;
DisplayImage({this.countries , this.continentName, this.isDayTime});
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Stack(
fit: StackFit.passthrough,
children: [
Image.asset('assets/$continentName_$isDayTime.png'.asset),
fit: BoxFit.cover,),
for (Temperature temp in countries)
Positioned(
left: temp.xAlign * size.width / 100.0,
top: temp.yAlign / 2 * size.height / 100.0,
child: Text('${temp.cityName} ${temp.cityTemperature.toString()}° C'),
)
]);
}
}
and when calling it in the TabView
TabBarView(
children: _tabs.map((String name) {
return DisplayImage(
continentName: name,
isDayTime: isDayTime['Day'],
countries: List<Temperature>.from(_temperature)
..retainWhere((temperature) =>
temperature.continentName == name &&
temperature.isDayTime == isDayTime['Day'] &&
temperature.seasonName ==
listSeason.keys.firstWhere(
(k) => listSeason[k] == true,
orElse: () => 'Nothing')));
}).toList())
As far as I understand you can use the fit property of the stack (StackFit.passthrough) and it will work the same as intrinsicHeight. From the documentation
StackFit.passthrough
For example, if a Stack is an Expanded child of a Row, the horizontal constraints will be tight and the vertical constraints will be loose.
In this case you're using an Expanded in a column so it has horizontal loose and vertical tight. Then do some math and try the Align widget if the positioned doesnt work as you want

How to create Listview dynamically when clicking card items in flutter?

I have a list of categories displayed in listview. Now I am trying to create the subcategories when clicking each of the category list and display it into another listview in flutter.
For each categories , I have to create another list of subcategories dynamically. I have json data and good working category list. I have to create sub categories based on category list.
I have a model class that contains category details and subcategory details as well.
How could i achieve this?
Model Class
class ProductCategoryModel {
String categoryName;
String categoryImage;
String categoryId;
List<SubCategory> subcategory;
ProductCategoryModel(
{this.categoryName,
this.categoryImage,
this.categoryId,
this.subcategory});
factory ProductCategoryModel.fromJson(Map<String, dynamic> json) {
var list = json['children'] as List;
print(list.runtimeType);
List<SubCategory> subCategoryList =
list.map((i) => SubCategory.fromJson(i)).toList();
return ProductCategoryModel(
categoryName: json['name'],
categoryImage: json['image'],
categoryId: json['category_id'],
subcategory: subCategoryList,
);
}
}
class SubCategory {
String subCategoryId;
String subCategoryName;
SubCategory({this.subCategoryId, this.subCategoryName});
factory SubCategory.fromJson(Map<String, dynamic> subJson) {
return SubCategory(
subCategoryId: subJson['SubCategoryModel'],
subCategoryName: subJson['name'],
);
}
}
Json Response :
{
"category_id": "1",
"name": "Vehicle",
"column": "1",
"children": [
{
"category_id": "101",
"name": "Two Wheeler",
"product_count": " (0)"
},
{
"category_id": "102",
"name": "Four Wheeler",
"product_count": " (1)"
}
]
},
I have created this demo.
Check it, then ask me anything if you need.
Trick is here, creating new boolean list based on your categories, then rendering subCategories based on that boolean list conditionally.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Home(),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool success = false;
static List<String> categories = ['1', '2', '3'];
static List<List<String>> subCategories = [
['a', 'b', 'c'],
['d', 'e', 'f'],
['g', 'h', 'i']
];
static List<bool> activeCategories = List.filled(categories.length, false);
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: categories.length,
itemBuilder: (context, index) {
return Column(
children: <Widget>[
SizedBox(
height: 50,
child: Center(
child: RaisedButton(
onPressed: () {
setState(() {
activeCategories[index] =
activeCategories.elementAt(index) == true
? false
: true;
});
},
child: Text(
categories.elementAt(index),
),
),
),
),
activeCategories.elementAt(index)
? ListView.builder(
shrinkWrap: true,
itemCount: subCategories.length,
itemBuilder: (context, subIndex) {
return SizedBox(
height: 50,
child: Center(
child: Text(subCategories
.elementAt(index)
.elementAt(subIndex)),
),
);
},
)
: SizedBox(),
],
);
},
),
);
}
}