Flutter - fill table with dyanmic data - flutter

In my class, I have a variable named report, that looks like this:
{
"id": 1,
"type": "my type",
"name": "Report 1",
"client_name": "John",
"website": "john.com",
"creation_time": "2019-03-12T22:00:00.000Z",
"items": [{
"id": 1,
"report_id": 1,
"place": "Kitchen",
"type": "sometype",
"producer": "somepro",
"serial_number": "123123",
"next_check_date": "2019-03-19",
"test_result": "Verified",
"comments": "some comments"
}]
}
I want to show the list of items in a table with flutter.
So far, I just created a static table as follows:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(report['name'])),
body: Container(
child: Table(children: [
TableRow(children: [
Text("TITLE 1"),
Text("TITLE 2"),
Text("TITLE 3"),
]),
TableRow(children: [
Text("C1"),
Text("C2"),
Text("C3"),
])
])
)
);
}
}
Couldn't find any examples of how to fill the table rows (the titles can stay static) with my JSON items list.
Each row should be an item from the items JSON array.
Any idea?

You can map items to TableRows. Don't forget to end with toList().
For example:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(report['name'])),
body: Container(
child: Table(
children: (report['items'] as List)
.map((item) => TableRow(children: [
Text(item['id'].toString()),
Text(item['report_id'].toString()),
Text(item['place']),
// you can have more properties of course
]))
.toList())));
}
If you want the static titles that you mentioned, you could use insert on the list you create above, and then you have:
#override
Widget build(BuildContext context) {
var list = (report['items'] as List)
.map((item) => TableRow(children: [
Text(item['id'].toString()),
Text(item['report_id'].toString()),
Text(item['place']),
//...
]))
.toList();
list.insert(
0,
TableRow(children: [
Text("TITLE 1"),
Text("TITLE 2"),
Text("TITLE 3"),
//...
]));
return Scaffold(
appBar: AppBar(title: Text(report['name'])),
body: Container(child: Table(children: list)));
}

What you are looking for is an ItemBuilder. You can find a good example of flutter ItemBuilders here and here. Examples here use the ListView builder though.

Related

Getting this RangeError (index): Invalid value: Valid value range is empty: 1 while trying to populate json data to ListView.builder

I have an API and what I am trying to do is to display the 'CourseTitle' from the JSON into the ExpansionTile title and the corresponding 'Title', 'EndDate', 'QuizStatus' in the children of ExpansionTile.
If data is not available for corresponding 'Title', 'EndDate', 'QuizStatus' etc., only 'CourseTitle' should be added to the ExpansionTile title, and children should be empty.
The first tile is built as expected but the remaining screen shows a red screen with RangeError (index): Invalid value: Valid value range is empty: 1.
I'm aware that this is because of empty data from JSON but couldn't solve the issue.
Here's JSON response:
{
"QuizzesData": [
{
"request_status": "Successful",
"CourseCode": "ABC101",
"CourseTitle": "ABC Course",
"UnReadStatus": [],
"Quizzes": [
{
"QuizID": "542",
"Title": "Test Quiz 01",
"StartDate": "Oct 20, 2022 12:00 AM",
"EndDate": "Oct 31, 2022 11:59 PM",
"IsDeclared": "False",
"Questions": "5",
"TotalMarks": "5",
"MarksObtained": "1",
"MarksObtainedTOCheckQuizResult": "1",
"QuizIsDeclared": "Un-Declared",
"StudentSubmitStatus": "true",
"IsRead": "1",
"QuizStatus": "Attempted"
}
]
},
{
"CourseCode": "DEF101",
"CourseTitle": "DEF Course",
"UnReadStatus": [],
"Quizzes": []
},
{
"CourseCode": "GHI101",
"CourseTitle": "GHI Course",
"UnReadStatus": [],
"Quizzes": []
},
{
"CourseCode": "JKL101",
"CourseTitle": "JKL Course",
"UnReadStatus": [],
"Quizzes": []
},
]
}
Here's the API data:
var listofdata ;
Future quizListingApi() async {
final response = await http.get(Uri.parse('json url'));
if(response.statusCode == 200){
listofdata = jsonDecode(response.body.toString());
}
else{
print(response.statusCode);
}
and the build method:
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(child: FutureBuilder(
future: quizListingApi(),
builder: (context, AsyncSnapshot snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Text('Loading');
}
else{
return ListView.builder(
itemCount: listofdata['QuizzesData'].length,
itemBuilder: (context, index){
return Card(
child: Column(
children: [
ExpansionTile(
title: Text(listofdata['QuizzesData'][index]['CourseTitle']),
children: [
Text(listofdata['QuizzesData'][index]['Quizzes'][index]['Title']),
Text(listofdata['QuizzesData'][index]['Quizzes'][index]['EndDate']),
Text(listofdata['QuizzesData'][index]['Quizzes'][index]['QuizStatus']),
],
),
],
),
);
}
);
}
},
))
],
),
);
}
I also tried answers from other similar threads but couldn't find the solution for this specific type of problem.
Your Quizzes's index are not same as your listofdata, so you need and other for loop for your Quizzes's items:
ExpansionTile(
title: Text(listofdata['QuizzesData'][index]['CourseTitle']),
children: List<Widget>.generate(listofdata['QuizzesData'][index]['Quizzes'].length, (i) => Column(
children: [
Text(listofdata['QuizzesData'][index]['Quizzes'][i]
['Title']),
Text(listofdata['QuizzesData'][index]['Quizzes'][i]
['EndDate']),
Text(listofdata['QuizzesData'][index]['Quizzes'][i]
['QuizStatus']),
],
),),
),

Retrieve specific value from Hive Box

In my app, I am using Hive to store data locally. My box is called "favorites" and I managed to store the data in the box with this code:
_save() {
final recipeData = Recipe(
title: widget.recipeDocument['title'],
id: widget.recipeDocument['id'],
price: widget.recipeDocument['price'],
url: widget.recipeDocument['url'],
servings: widget.recipeDocument['servings'],
calories: widget.recipeDocument['calories'],
carbs: widget.recipeDocument['carbs'],
protein: widget.recipeDocument['protein'],
fat: widget.recipeDocument['fat'],
ingredients: widget.recipeDocument['ingredients'],
instructions: widget.recipeDocument['instructions'],);
print('Generated recipeData final $recipeData');
String json =jsonEncode(recipeData);
print('Generated json $json');
final box = Hive.box('favorites'); //<- get an already opened box, no await necessary here
// save recipe information
final Id = widget.recipeDocument['id'];
box.put(Id,json);
On my favorite page, I want to display the title and price in a ListView.
I get data from the box like this:
body: ValueListenableBuilder(
valueListenable: Hive.box('favorites').listenable(),
builder: (context, box, child) {
var box = Hive.box('favorites');
List post = List.from(box.values);
print('List is $post');
The list contains the following:
[
{
"url": "http for URL",
"title": "Bananabread",
"price": "0,77",
"calories": "234",
"carbs": "12",
"fat": "1",
"id": "1",
"protein": "34",
"servings": 1,
"ingredients": [
"2 bananas",
"30 g flour",
"2 eggs"
],
"instructions": [
"1. mix banana and egg.",
"2. add flour.",
"3. bake and enjoy"
]
}
]
Let's say I only want to retrieve the title and price from that. How do I do so?
I tried this:
return ListView(
padding: const EdgeInsets.all(16),
children: <Widget>[
Text('This shows favorites'),
...post.map(
(p) => ListTile(
title: Text(p[1]),
trailing: Text(p[2]),
),
),
],
);
But this only returns "U" and "R"...so the letters from the word URL, I guess?
Try this. You are accessing the key of the map in the list.
return ListView(
padding: const EdgeInsets.all(16),
children: <Widget>[
Text('This shows favorites'),
...post.map(
(p) => ListTile(
title: Text(p['url'].toString()),
trailing: Text(p['title'].toString()),
),
),
],
);

How to show seperate DataTables and Text for each map in json list?

I want to show json data with DataTable() but i get error:
type '(dynamic) => Column?' is not a subtype of type '(dynamic) => DataTable' of 'f'
My question is how i can resolve the error but most importantly how can i iterate through the list block_data and then for each map show header and title in seperate DataTable() where they are on top of eachother and eachdescription below each DataTable()?
This is the view where i call the method setTestData() which awaits the Future AppContent().getAppContent() and then set the data to the field testObject and i also initialize setTestData() in current state. I can then use testObject to acces the json data.
My goal is to show EACH map from list block_data as SEPERATE DataTable for my usecase i have to do that. The reason why i want to this like that is because i also want to show the description below DataTable as a seperate Text() widget because it can be too long and in my usecase it has to be below the table
I now have this AppView statefull widget which i want to use to show each DataTable() seperatly based on each map from list block_data. I am not sure if i do it the right way but right now i get the error so it is more unclear if i can even achieve my goal this way:
class AppView extends StatefulWidget {
const AppView({Key? key}) : super(key: key);
#override
_AppViewState createState() => _AppViewState();
}
class _AppViewState extends State<AppView> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
late Map<String, dynamic> testObject = {};
setTestData() async {
await AppContent()
.getAppContent()
.then((result) => setState(() => testObject = result));
}
#override
void initState() {
setTestData();
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
key: _scaffoldKey,
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
leading: IconButton(
icon: const Icon(Icons.menu,
size: 40), // change this size and style
onPressed: () => _scaffoldKey.currentState?.openDrawer(),
),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return Column(
children: [
FutureBuilder(
future: AppContent().getAppContent(),
builder: (context, snapshot) {
if (snapshot.hasData) {
debugPrint(testObject["data"]["views"]["books"][0]
["block_data"]
.toString());
return Column(
children: [
Container(
alignment: Alignment.topLeft,
child: (testObject["data"]["views"]["books"]
[0]["block_data"])
.map<DataTable>((object) {
if (object.containsKey("header")) {
return Column(
children: [
DataTable(
horizontalMargin: 0,
columnSpacing: 75,
columns: <DataColumn>[
DataColumn(
label: Container(
padding: EdgeInsets.zero,
child: Text(
object["header"]
.toString(),
)),
),
],
rows: <DataRow>[
DataRow(
cells: <DataCell>[
DataCell(Text(
object['title']
.toString(),
DataCell(Text(object['date'].toString()
))
],
),
],
),
Text(object["description"]
.toString())
],
);
} else {
Container();
}
}).toList()),
Text(
"This is another placeholder for another list")
],
);
} else {
return CircularProgressIndicator();
}
}),
],
);
}, childCount: 1),
),
],
),
),
);
}
}
This is the method AppContent().getAppContent() which grabs the json:
class AppContent {
Future<Map<String, dynamic>> getAppContent() async {
String jsonData = await rootBundle.loadString('assets/test.json');
Map<String, dynamic> data = jsonDecode(jsonData);
return data;
}
}
And this the json which i call:
{
"data": {
"views": {
"books": [
{
"block_type": "list",
"block_data": [
{
"header": "FAQ",
"long_text_type": "description",
"title": "Service fees",
"date": "19-01-2022",
"description": "Information about fees and surcharges."
},
{
"header": "FAQ",
"long_text_type": "description",
"title": "Returns & Refunds",
"date": "03-06-2022",
"description": "How to return products and recieve refunds.."
}
]
}
}
}
}
Edit
I want it to look like the picture below where i have a datable for and description below that. But i want it for each map in list block_data and so i want to show the next map below the description and then show basicly DataTable -> description -> DataTable -> description. But i want to iterate through list block_data and generate DataTable and Text based on maps inside list which could be more than just two maps
There are multiple issues here.
Your JSON has an error (probably a copy/paste error, but double-check it).
Instead of
"block_data": "block_data": [ you should have "block_data": [.
Moreover, a bracket is missing at the end of your JSON file (but again, I guess that it's because you only showed a part of your file to help us investigate your problem)
The error you wrote in your question is related to your .map<DataTable>((object) {
When using the .map, the Object you specify is the return type of your mapping. In your case you're returning a Column and not a DataTable.
If you want to iterate on a list, and create a list of Widgets in return, you can use this instead:
.map<Widget>((object) {
Finally, and the most important point of this answer : you're having problems here because you're not converting your JSON file in a Dart Object you can easily manipulate.
You can simply paste your JSON file on this website : https://javiercbk.github.io/json_to_dart/ and retrieve the code to add to your project.
Then, you'll have a model with a fromJson and a toJson methods.
Thanks to those methods, you'll be able to create Dart Objects from your JSON values, and thus to create your Widgets easily.
With this answer, you should be good to go. Add a comment if I need to add more details.

Routing Problem: "There are multiple heroes that share the same tag within a sub-tree ."

import 'package:flutter/material.dart';
import 'package:ui_mvc/Rings.dart';
class Diamonds extends StatefulWidget {
#override
_DiamondsState createState() => _DiamondsState();
}
class _DiamondsState extends State<Diamonds> {
var dataList= [
{
"name": "A1",
"image":"assets/1.jpg" ,
"location": "Delhi",
},
{
"name": "A2",
"image": "assets/2.jpg" ,
"location": "Delhi",
},
{
"name": "A3",
"image": "assets/3.jpg" ,
"location": "Delhi",
},
{
"name": "A4",
"image": "assets/4.jpg" ,
"location": "Delhi",
},
{
"name": "A5",
"image": "assets/5.jpg" ,
"location": "Delhi",
},
];
#override
Widget build(BuildContext context) {
return GridView.builder(
scrollDirection: Axis.horizontal,
itemCount: dataList.length,
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 1),
itemBuilder: (BuildContext context,int index){
return SingleProd(
prodName: dataList[index]['name'],
prodImage: dataList[index]['image'],
prodLocation: dataList[index]['location'],
);
});
}
}
class SingleProd extends StatelessWidget {
final prodName;
final prodImage;
final prodLocation;
SingleProd({
this.prodName,
this.prodImage,
this.prodLocation,
});
#override
Widget build(BuildContext context) {
return Card(
child: Hero(
tag: prodName,
child: Material(
child: InkWell(
onTap: ()=>
Navigator.of(context).push(
new MaterialPageRoute(builder: (context)=> new Rings())
),
child: GridTile(
footer: Container(
color: Colors.white,
child: ListTile(
title: Text(
prodName,
textAlign: TextAlign.left,
),
subtitle: Text(
prodLocation,
textAlign: TextAlign.left,
),
),
),
child: Image.asset(
prodImage,
fit: BoxFit.fitHeight)
),
),
)
),
);
}
}
So I'm trying to display a horizontal list with 5 tiles. I want each tile to redirect to the same page (for the time being) that's why I call Rings() which is defined in another page. And upon tapping one of the tiles the screen goes black. But it just keeps on showing me this error:
The following assertion was thrown during a scheduler callback:
There are multiple heroes that share the same tag within a subtree.
Within each subtree for which heroes are to be animated (i.e. a PageRoute subtree), each Hero must have a unique non-null tag.
In this case, multiple heroes had the following tag: A
Here is the subtree for one of the offending heroes: Hero
tag: A
state: _HeroState#d0a27
When the exception was thrown, this was the stack:
#0 Hero._allHeroesFor.inviteHero.<anonymous closure> (package:flutter/src/widgets/heroes.dart:265:11)
#1 Hero._allHeroesFor.inviteHero (package:flutter/src/widgets/heroes.dart:276:8)
#2 Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:295:21)
#3 SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5433:14)
#4 Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:308:15)
I have sorted out the problem. As #Jot said prodname wasn't unique in every case plus i made a similar horizontal list using the same data hence it created an error. So upon entering unique prodname like a1, a2 or a,b,c the error was removed.

Creating a Dropdown menu in Flutter

What I am trying to do in my app is to add a dropdown based on the list contents. I have something like this:
[
{
id: val,
displayName: Enter value,
type: string,
value: "any"
},
{
id: si,
displayName: Source,
type: list,
value: [
MO
],
data: [
{id: 1, displayId: MO},
{id: 2, displayId: AO},
{id: 3, displayId: OffNet}
]
}
]
Currently there are 2 entries. What I want to do is display a dropdown containing those options (Enter value and Source) as 2 entries of dropdown:
If Enter value is selected a text box next to it should be displayed, since it has a type of string.
If Source option in dropdown is selected another dropdown containing those entries (MO, AO, Offnet) should be present as a dropdown value, since it has a type of list.
In short, based on the selection of the 1st dropdown a widget to be displayed (either text box or another dropdown) should be chosen.
If anyone knows or previously had done the same please help me with this, Thanks.
I'd make use of StatefulWidget to achieve what you need (if you're not using more advanced state management options). State would be helpful to track user's choices, as well as to decide whether to render a text field or another dropdown (or nothing at all).
I've added a complete working example below. Note that it does not follow best practices in a sense that you would probably want to split it up in separate small widgets for better composability (and readability). However, I've opted for quick-and-dirty approach to fit everything in one place.
Also note that you'd probably want to do some more processing once a user makes a choice. Here, I simply illustrate how to render different widgets based on a user's choice (or more generally, changes in StatefulWidget's state). Hence, this example is used to highlight one principle only.
import 'package:flutter/material.dart';
void main() {
runApp(DropdownExample());
}
class DropdownExample extends StatefulWidget {
#override
_DropdownExampleState createState() => _DropdownExampleState();
}
class _DropdownExampleState extends State<DropdownExample> {
String type;
int optionId;
final items = [
{
"displayName": "Enter value",
"type": "string",
},
{
"displayName": "Source",
"type": "list",
"data": [
{"id": 1, "displayId": "MO"},
{"id": 2, "displayId": "AO"},
{"id": 3, "displayId": "OffNet"}
]
}
];
#override
Widget build(BuildContext context) {
Widget supporting = buildSupportingWidget();
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Dropdown Example")),
body: Center(
child: Container(
height: 600,
width: 300,
child: Row(
children: <Widget>[
buildMainDropdown(),
if (supporting != null) supporting,
],
),
),
),
),
);
}
Expanded buildMainDropdown() {
return Expanded(
child: DropdownButtonHideUnderline(
child: DropdownButton(
value: type,
hint: Text("Select a type"),
items: items
.map((json) => DropdownMenuItem(
child: Text(json["displayName"]), value: json["type"]))
.toList(),
onChanged: (newType) {
setState(() {
type = newType;
});
},
),
),
);
}
Widget buildSupportingWidget() {
if (type == "list") {
List<Map<String, Object>> options = items[1]["data"];
return Expanded(
child: DropdownButtonHideUnderline(
child: DropdownButton(
value: optionId,
hint: Text("Select an entry"),
items: options
.map((option) => DropdownMenuItem(
child: Text(option["displayId"]), value: option["id"]))
.toList(),
onChanged: (newId) => setState(() {
this.optionId = newId;
}),
),
),
);
} else if (type == "string") {
return Expanded(child: TextFormField());
}
return null;
}