I'm using flutter to develop an e-commerce app.
I'm working on the navDrawer for it and I could use some help with the categories.
I have categories that can have subcategories and the subcategories can also have their own subcategories.
Basically, the data set is an array of unknown dimensions.
I need to make a boolean map for my categories and subcategories so that I can keep track of which ones are open in order to show the subcategories.
Here's an example of the dataset:
{
"id":"41490",
"name":"Electrical Equipment",
"subCategories":[
{
"id":"41492",
"name":"Breakers",
"subCategories":[
{
"id":"167542",
"name":"1 Pole",
"subCategories":[
{
"id":"167577",
"name":"15 Amp",
"subCategories":null
},
{
"id":"167585",
"name":"20 Amp",
"subCategories":null
},
{
"id":"167600",
"name":"30 Amp",
"subCategories":null
},
{
"id":"167606",
"name":"40 Amp",
"subCategories":null
}
]
},
I think recursion is the optimal way to process this dataset but the problem I'm having is that I can't figure out how to have dynamic dimensions for an array in Dart.
I already figured out how to generate my listTiles from the dataset but I can't figure out the boolean map.
Is this even possible or should I look into a different approach?
Here's my code for generating the listTiles from the dataset:
void setCategories(List categories){
_categories = categories;
int catCount = categories.length;
_categoryList = new ListView.builder(
//shrinkWrap: true,
//physics: ClampingScrollPhysics(),
padding:EdgeInsets.all(0.0),
itemCount: catCount,
itemBuilder: (BuildContext context, int index) => buildCategories(context, index),
);
}
Widget buildCategories(BuildContext context, int index){
if(_categories[index]['subCategories']!=null){
//TODO: call buildSubCategories with depth of 1 parameter
return Container(
height: 30.0,
child: ListTile(
title: Row(
children:[
Text(" "+_categories[index]['name']),
Transform.scale(
scale: 0.75,
child:
Icon(Icons.arrow_back)
)
]
),
onTap: () {
//TODO: implement boolean map here
}
),
padding: EdgeInsets.all(0.0),
margin: EdgeInsets.all(0.0)
);
} else {
return Container(
height: 30.0,
child: ListTile(
title: Text(" "+_categories[index]['name']),
onTap: () {
}
),
padding: EdgeInsets.all(0.0),
margin: EdgeInsets.all(0.0)
);
}
}
Widget buildSubCategories(var parent, int depth){
List subCategoryList = parent['subCategories'];
int subCategoryCount = subCategoryList.length;
if(parent['subCategories']!=null){
//for each subCategory
//if subCategory has subCategories
//recurse subCategory with depth
buildSubCategories(parent['subCategories'], depth++);
//TODO: implement boolean map here
} else {
//
}
}
void generateCategoryBooleanMap(){
//TODO: generate boolean map here
//TODO: boolean map needs to have a undetermined amount of depth levels
}
Any insight is appreciated even if it means I have to use a different paradigm.
Example of using a Set to keep track of which id is open:
void main() {
final idHandler = IdHandler();
print(idHandler.isIdOpen('MyId')); // false
idHandler.openId('MyId');
print(idHandler.isIdOpen('MyId')); // true
idHandler.closeId('MyId');
print(idHandler.isIdOpen('MyId')); // false
idHandler.openId('MyId');
print(idHandler.isIdOpen('MyId')); // true
idHandler.closeAll();
print(idHandler.isIdOpen('MyId')); // false
}
class IdHandler {
final Set<String> _openIds = {};
void openId(String id) => _openIds.add(id);
void closeId(String id) => _openIds.remove(id);
void closeAll() => _openIds.clear();
bool isIdOpen(String id) => _openIds.contains(id);
}
Related
I am trying to display a bar chart in my app with the syncfusion library. It contains 6 bars where the height is defined by a score and the name is a player name. I have the following methods: getBanditBarData() which gets the data from a database in creates a list of BanditData-objects (BanditData class shown below), and barChart() which creates a List of ChartSeries that I can return in the series parameter of my SfCartesianChart.
My problem is that the item of the dataSource: item-line in my barChart()-method gives the following exception:
_TypeError (type 'BanditData' is not a subtype of type 'List<BanditData>')
I've tried nesting an additional List around each BanditData object in the list, and even removing the for-loop of the method. Both changes result in similar errors somewhere in the same method.
Future<List<BanditData>> getBanditBarData() async {
var scores = await database.totalScore();
List<BanditData> banditData = [];
for (var score in scores) {
BanditData bandit = BanditData(score['name'], "", score['score']);
banditData.add(bandit);
}
return banditData;
}
List<ChartSeries> barChart(data) {
var barList = <ChartSeries>[];
for (var item in data) {
barList.add(BarSeries<BanditData, String>(
dataSource: item,
xValueMapper: (BanditData b, _) => removeBanditSuffix(b.name),
yValueMapper: (BanditData b, _) => b.score,
animationDuration: 2000));
}
return barList;
}
The BanditData-class is very simple and looks like this:
class BanditData {
BanditData(this.name, this.date, this.score);
final String name;
final String date;
final int score;
}
The setup shown above works when I render my line chart. The methods are very similar:
Future<List<List<BanditData>>> getBanditLineData() async {
var dates = await database.getDistinctDatesList();
var scores = await database.createScoreDataStruct();
List<List<BanditData>> banditData = [];
for (var i = 0; i < scores.length; i++) {
List<BanditData> temp = [];
var intList = scores[i]['scores'];
for (var j = 0; j < scores[i]['scores'].length; j++) {
BanditData bandit = BanditData(scores[i]['name'], dates[j], intList[j]);
temp.add(bandit);
}
banditData.add(temp);
}
return banditData;
}
List<ChartSeries> lineChart(data) {
var lineList = <ChartSeries>[];
for (var item in data) {
lineList.add(LineSeries<BanditData, String>(
dataSource: item,
xValueMapper: (BanditData b, _) => b.date,
yValueMapper: (BanditData b, _) => b.score,
enableTooltip: true,
name: removeBanditSuffix(item[1].name),
width: 3.0,
animationDuration: 2000,
));
}
return lineList;
}
If necessary, here is some more code showing how I build the chart. The above methods is placed inside MyStatsPageState, but figured it would be better to split it up for readability.
Ideally, I should be able to replace series: lineChart(lineData) with series: barChart(barData):
import 'database.dart';
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
class MyStatsPage extends StatefulWidget {
const MyStatsPage({Key? key}) : super(key: key);
#override
MyStatsPageState createState() {
return MyStatsPageState();
}
}
class MyStatsPageState extends State<MyStatsPage> {
late Future<List<List<BanditData>>> _banditLineData;
late Future<List<BanditData>> _banditBarData;
final database = Database();
bool displayLineChart = true;
#override
void initState() {
_banditLineData = getBanditLineData();
_banditBarData = getBanditBarData();
super.initState();
getBanditBarData();
}
#override
Widget build(BuildContext context) {
const appTitle = "Stats";
return Scaffold(
appBar: AppBar(
title: const Text(
appTitle,
style: TextStyle(fontSize: 25, fontWeight: FontWeight.w700),
)),
body: FutureBuilder(
future: Future.wait([_banditLineData, _banditBarData]),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
if (snapshot.hasError) {
return ErrorWidget(Exception(
'Error occured when fetching data from database'));
} else if (!snapshot.hasData) {
return const Center(child: Text('No data found.'));
} else {
final lineData = snapshot.data![0];
final barData = snapshot.data![1];
return Padding(
padding: const EdgeInsets.all(5.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: SfCartesianChart(
primaryXAxis: CategoryAxis(),
enableAxisAnimation: true,
series: lineChart(lineData),
)),
],
));
}
}
}));
}
We have checked the code snippet attached in the query and found that it is a sample-level issue, it occurs due to you have passed the BanditData instead of List. Because dataSource property always supports the list value only. To resolve this, convert the barData to nested lists, or assign a value to the BarSeries dataSource like below.
Code snippet:
List<ChartSeries> barChart(data) {
var barList = <ChartSeries>[];
for (var item in data) {
barList.add(BarSeries<BanditData, String>(
dataSource: [item],
// Other required properties
));
}
return barList;
}
When I click on the button, I need its name to be added to the List and when the button is clicked again, its name is added to the List again, but if 2 names match in the List, then they both need to be deleted. I tried to implement through the for loop but it does not work correctly for me. Tell me how can this be done?
function
List<String> types = [];
void reportButton(int number, String type) {
reportsList[number] = !reportsList[number];
if (reportsList.contains(true)) {
isEnabledC = true;
} else {
isEnabledC = false;
}
types.add(type);
for (var element in types) {
if (element == type) {
types.removeWhere((element) => element == type);
}
}
buttons
SizedBox(
width: size.width / 3,
child: GestureDetector(
onTap: () {
cubit.reportButton(0, 'comment');
// state.type = 'comment';
},
child: ReportButton(
enabled: state.reports[0],
svg: constants.Assets.comment,
text: 'Comment',
),
),
),
SizedBox(
width: size.width / 3,
child: GestureDetector(
onTap: () {
cubit.reportButton(1, 'broken');
// state.type = 'broken';
},
child: ReportButton(
enabled: state.reports[1],
svg: constants.Assets.attention,
text: 'Broken station',
),
),
),
you do not have to loop through the list of string. You can simply check if the list contains the string. If yes, remove it, else add the string
List<String> types = ["apple", "ball", "cat", "dog"];
void main() async {
reportButton(1, "xyz");
}
void reportButton(int number, String type) {
if (types.contains(type)) {
types.removeWhere((element) => element == type);
} else {
types.add(type);
}
print(types);
}
List<String> types = ["boy", "girl", "father", "hog"];
void main() {
reportButton(1, "xyz");
}
void reportButton(int number, String type) {
if (types.contains(type)) {
types.removeWhere((element) => element == type);
}else {
types.add(type);
}
print(types);
}
I am making my own flutter application, but I ran into a problem. I am creating a Breakfast class
class Breakfast {
String foodTitle;
String foodCalories;
Breakfast({this.foodTitle, this.foodCalories});
}
From this class i create an array with objects of that class
class BreakfastFood {
List<Breakfast> _breakfastFoodData = [
Breakfast(
foodTitle: "Bread",
foodCalories: "100",
),
Breakfast(
foodTitle: "Soup",
foodCalories: "50",
),
];
int _foodTitle = 0;
int _foodCalories = 0;
String getFoodTitle() {
return _breakfastFoodData[_foodTitle].foodTitle;
}
String getFoodCalories() {
return _breakfastFoodData[_foodCalories].foodCalories;
}
}
I have created a component which gets the foodtitle and foodCalories and puts them in a widget.
Now i want to make a function that loops through the objects of the _breakfastfoodData and shows them. But I don't know how to loop through the list and show all the objects seperate from eachother.
_breakfastFoodData in your code is private property (because it begins _), you should change to breakfastFoodData.
You use ListView.builder to build your list:
List<Breakfast> datas= new BreakfastFood().breakfastFoodData;
return ListView.builder(
itemCount: datas.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${datas[index].foodTitle}'),
);
},
);
You just have to define your class as here:
class BreakfastFood {
List<Breakfast> _items = [
Breakfast(
foodTitle: "Bread",
foodCalories: "100",
),
Breakfast(
foodTitle: "Soup",
foodCalories: "50",
),
];
List<Breakfast> get items => [..._items];
}
and Then call it threw list view for display all items:
BreakfastFood foodData = BreakfastFood();
ListView.builder(
itemCount: foodData.items.length,
itemBuilder: (ctx, index) {
return ListTile(
title: Text('${foodData.items[index].foodTitle}'),
subTitle: Text('${foodData.items[index].foodCalories}'),
);
}
)
The problem is that when a List Tile is tapped the quantity is incremented for all the list tiles.
I have a stateless widget which has this build method :
final ProductsList productsList = ProductsList(context);
return Scaffold(
body: Center(child: productWidget(productsList, args)));
}
This is the ProductWidget
FutureBuilder productWidget(productsList) {
return FutureBuilder(
future: getProducts,
builder: (context, products) {
switch (products.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
return Scaffold(
body: productsList.build(products.data));
}
},
);
And this is what productsList.build does:
ProductsList(this.context);
Padding getProduct(name) {
int _quantity = Provider.of<Quantity>(context).getQuantity();
return ListTile(
key: UniqueKey(),
onTap: () {
Provider.of<Quantity>(context, listen: false).incrementQuantity();
},
title: Text(name),
trailing: Text("$_quantity"),
),
);
}
ListView build(products) {
List<Widget> _products = new List();
for (var i = 0; i < products.length; i++) {
_products.add(getProduct(products[i].name));
}
return ListView(
children: _products,
);
}
and I am using this changeNotifier :
class Quantity extends ChangeNotifier {
int _quantity = 0;
void incrementQuantity(){
_quantity += 1;
notifyListeners();
}
int getQuantity() {
return _quantity;
}
}
I want to tap a list tile and increment just it's value which is displayed in the trailing, but not of the others.
I am using multi-provider in the main file of the application.
Provider needs to track quantity by product. Your Provider is tracking quantity as a single int so the result you are seeing is correct for your code.
Quantity should be List. You can also set the initial value.
Then
incrementQuantity(int index) {
increment quantity[index] here
}
And
get quantity(int index){
return quantity[index]
}
On a side note, in my opinion, your efforts would benifit greatly by researching using ListTile with Provider.
my use case is to create a list view of articles (each item have the same look, there could be huge amount of articles, e.g. > 10000). I tried with
- ListView with ListView.builder: it supposes only to render the item when the item is displayed
- ScrollController: to determine when to load the next items (pagination)
- then I use List to store the data fetched from restful API using http, by adding the data from http to the List instance
this approach is OK, but in case the user keeps on scrolling pages, the List instance will have more and more items, it can crash with stack Overflow error.
If I don't call List.addAll(), instead I assign the data fetched from api, like: list = data;
I have problem that when the user scroll up, he/she won't be able to see the previous items.
Is there a good approach to solve this? Thanks!
import 'package:flutter/material.dart';
import 'package:app/model.dart';
import 'package:app/components/item.dart';
abstract class PostListPage extends StatefulWidget {
final String head;
DealListPage(this.head);
}
abstract class PostListPageState<T extends PostListPage> extends State<PostListPage> {
final int MAX_PAGE = 2;
DealListPageState(String head) {
this.head = head;
}
final ScrollController scrollController = new ScrollController();
void doInitialize() {
page = 0;
try {
list.clear();
fetchNextPage();
}
catch(e) {
print("Error: " + e.toString());
}
}
#override
void initState() {
super.initState();
this.fetchNextPage();
scrollController.addListener(() {
double maxScroll = scrollController.position.maxScrollExtent;
double currentScroll = scrollController.position.pixels;
double delta = 200.0; // or something else..
if ( maxScroll - currentScroll <= delta) {
fetchNextPage();
}
});
}
#override
void dispose() {
scrollController.dispose();
super.dispose();
}
void mergeNewResult(List<PostListItem> result) {
list.addAll(result);
}
Future fetchNextPage() async {
if (!isLoading && mounted) {
page++;
setState(() {
isLoading = true;
});
final List<PostListItem> result = await doFetchData(page);
setState(() {
if (result != null && result.length > 0) {
mergeNewResult(result);
} else {
//TODO show notification
}
isLoading = false;
});
}
}
Future doFetchData(final int page);
String head;
List<PostListItem> list = new List();
var isLoading = false;
int page = 0;
int pageSize = 20;
final int scrollThreshold = 10;
Widget buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isLoading ? 1.0 : 0.0,
child: new CircularProgressIndicator(),
),
),
);
}
#override
Widget build(BuildContext context) {
ListView listView = ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (BuildContext context, int index) {
if (index == list.length) {
return buildProgressIndicator();
}
if (index > 0) {
return Column(
children: [Divider(), PostListItem(list[index])]
);
}
return PostListItem(list[index]);
},
controller: scrollController,
itemCount: list.length
);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(head),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
},
),
// action button
IconButton(
icon: Icon(Icons.more_horiz),
onPressed: () {
},
),
]
),
body: new RefreshIndicator(
onRefresh: handleRefresh,
child: listView
),
);
}
Future<Null> handleRefresh() async {
doInitialize();
return null;
}
}
in my case, when the list length is 600, I start to get stack overflow error like:
I/flutter ( 8842): Another exception was thrown: Stack Overflow
I/flutter ( 8842): Another exception was thrown: Stack Overflow
screen:
enter image description here
somehow flutter doesn't show any more details of the error.
I wrote some sample code for a related question about paginated scrolling, which you could check out.
I didn't implement cache invalidation there, but it would easily be extendable using something like the following in the getPodcast method to remove all items that are more than 100 indexes away from the current location:
for (key in _cache.keys) {
if (abs(key - index) > 100) {
_cache.remove(key);
}
}
An even more sophisticated implementation could take into consideration the scroll velocity and past user behavior to lay out a probability curve (or a simpler Gaussian curve) to fetch content more intelligently.