In flutter, how would I save the TextBox input from generated textboxes? - json

Below is my full code, the only thing you need to do to run it is adding "http: any" to dependencies in pubspec.yaml.
What the code does is grab JSON input from source, and for each entry in the json feed create a card. Now for each question, I want the user to provide an answer, so when I add the button to the bottom, it brings up a popup box showing the answers (which I'll later post back to my server).
It would look something like:
Q1: "three"
Q2: "thirty"
Q3: "Some movie character"
Q4: "Walter White"
(I'll do the validation to my answers later)
My json is posted below the code.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<List<Question>> fetchQuestions(http.Client client) async {
final response =
await client.get('https://jsonblob.com/api/jsonBlob/a5885973-7f02-11ea-b97d-097037d3b153');
// Use the compute function to run parsePhotos in a separate isolate
return compute(parseQuestion, response.body);
}
// A function that will convert a response body into a List<Photo>
List<Question> parseQuestion(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Question>((json) => Question.fromJson(json)).toList();
}
class Question {
final int id;
final String question;
final String answer;
Question({this.id, this.question, this.answer});
factory Question.fromJson(Map<String, dynamic> json) {
return Question(
id: json['id'] as int,
question: json['question'] as String,
answer: json['answer'] as String
);
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final appTitle = 'Isolate Demo';
return MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Padding(
child: FutureBuilder<List<Question>>(
future: fetchQuestions(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? QuestionList(questions: snapshot.data)
: Center(child: CircularProgressIndicator());
},
),
padding: EdgeInsets.fromLTRB(1.0, 10.0, 1.0, 10.0),
),
);
}
}
class QuestionList extends StatelessWidget {
final List<Question> questions;
QuestionList({Key key, this.questions}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: questions.length,
itemBuilder: (context, index) {
return Column(
children: <Widget>[
Container(
constraints: BoxConstraints.expand(
height: Theme.of(context).textTheme.display1.fontSize * 1.1 +
200.0,
),
color: Colors.blueGrey,
alignment: Alignment.center,
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text(questions[index].id.toString()),
subtitle: Text(questions[index].question),
),
new TextFormField(
decoration: new InputDecoration(
labelText: "Answer Question",
fillColor: Colors.white,
border: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(25.0),
borderSide: new BorderSide(
),
),
),
validator: (val) {
if(val.length==0) {
return "Type your answer here";
}else{
return null;
}
},
keyboardType: TextInputType.text,
style: new TextStyle(
fontFamily: "Poppins",
),
),
/*
// make buttons use the appropriate styles for cards
ButtonBar(
children: <Widget>[
RaisedButton(
color: Colors.lightBlue,
hoverColor: Colors.blue,
child: const Text('Open'),
onPressed: () {/* ... */},
),
],
),
*/
],
),
)),
],
);
},
);
}
}
[
{
"id" : 1,
"question" : "what is one plus two",
"answer": "three"
},
{
"id" : 2,
"question" : "what is five times 6",
"answer": "thirty"
},
{
"id" : 3,
"question" : "Who said show me the money",
"answer": "Cuba Gooding Jnr"
},
{
"id" : 4,
"question" : "who said I am the danger",
"answer": "Walter White"
}
]

Consider attaching a TextEditingController to the TextFormField through the controller property. You can access the text currently in the TextField using the controller's text field.

Related

How to display a nested json file in listview in Flutter

i'm learning flutter recently, and i have a problem. How display nested json file in listview in flutter ?
On internet i see a lots of example but it's with an url of an api and i don't want use api. I'm in local.
I think the problem is not my parse, but i don't know so, below you can see my code.
array.json
[
{
"jp": {
"name": "jp",
"password": "pawwordTest",
"maxtun": 0,
"email": "jp#france.fr",
"date": {
"build": "test1",
"first_cnx": "test2"
}
}
}
]
array.dart
class JP {
final String name;
final String password;
final int maxtun;
final String email;
final Date date;
JP({
required this.name,
required this.password,
required this.maxtun,
required this.email,
required this.date,
});
factory JP.fromJson(Map<String, dynamic> json){
return JP(
name: json['name'],
password: json['password'],
maxtun: json['maxtun'],
email: json['email'],
date: Date.fromJson(json['date']),
);
}
}
class Date{
final String build;
final String firstCNX;
Date({required this.build, required this.firstCNX});
factory Date.fromJson(Map<String, dynamic> json){
return Date(
build: json['build'],
firstCNX: json['first_cnx']
);
}
}
And event_page.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'dart:async' show Future;
//import 'package:flutter/material.dart' show rootBundle;
import 'package:array_json/array.dart';
import 'package:flutter/services.dart';
class EventPage extends StatefulWidget {
const EventPage({Key? key}) : super(key: key);
#override
State<EventPage> createState() => _EventPageState();
}
class _EventPageState extends State<EventPage> {
List<JP> testJson = [];
Future<void> readJson() async{
final String response = await rootBundle.loadString("assets/array.json");
final informationData = await json.decode(response);
var list = informationData['jp'] as List<dynamic>;
setState(() {
testJson = [];
testJson = list.map((e) => JP.fromJson(e)).toList();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leadingWidth: 100,
leading: ElevatedButton.icon(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.arrow_back_ios,
color: Colors.blue,
),
label: const Text("Back",
style: TextStyle(color: Colors.blue),
),
style: ElevatedButton.styleFrom(
elevation: 0,
backgroundColor: Colors.transparent,
),
),
centerTitle: true,
title: const Text("Load and Read JSON File",
style: TextStyle(color: Colors.black54),
),
backgroundColor: Colors.white,
),
body: Column(
children: [
Padding(padding: EdgeInsets.all(15.0),
child: ElevatedButton(onPressed: readJson,
child: const Text("Load Informations")),
),
Expanded(
child: ListView.builder(
itemCount: testJson.length,
itemBuilder: (BuildContext context, index){
final x = testJson[index];
return Container(
padding: EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("test : ${x.name}"),
Text(x.password),
Text(x.maxtun.toString()),
Text(x.email),
const SizedBox(
height: 5.0,
),
const Text("Date : ",
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold),
),
Text(x.date.build),
Text(x.date.firstCNX),
const SizedBox(
height: 5.0,
),
],
),
);
}
),
),
],
),
);
}
}
Help me please, i'm sure, i'm not missing much but it's the question
informationData['jp'] can't be a List, it's a Map, try this code instead:
Future<void> readJson() async{
final String response = await rootBundle.loadString("assets/array.json");
final informationData = json.decode(response) as List;
setState(() {
testJson = informationData.map<JP>((e) => JP.fromJson(e['jp'])).toList();
});
}
actually the problem you have is that the json file has a list and you are accessing it like map so that is causing issue change your json file content with this that will solve your problem
{
"jp": [
{
"name": "jp",
"password": "pawwordTest",
"maxtun": 0,
"email": "jp#france.fr",
"date": {
"build": "test1",
"first_cnx": "test2"
}
}
]
}

i have a problem showing the json data in the drop-down list flutter

this is my first flutter JSON example, I tried to fetch data from the JSON link and display it on a drop-down list.
I am getting the response on the console but the drop-down list doesn't work
any help, please?......................................................................................................................................................................................................................................................................................................................
this is the body
ERROR
this is my code
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
_getfamilyList();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 15, right: 15, top: 5),
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton<String>(
value: _myfamily,
iconSize: 30,
icon: (null),
style: TextStyle(
color: Colors.black54,
fontSize: 16,
),
hint: Text('Select family'),
onChanged: (String newValue) {
setState(() {
_myfamily = newValue;
print(_myfamily);
});
},
items: familysList?.map((item) {
return new DropdownMenuItem(
child: new Text(item['firsName']),
);
})?.toList() ??
[],
),
),
),
),
],
),
),
],
),
);
}
List familysList;
String _myfamily;
String familyInfoUrl =
'http://10.0.2.2:3000/genocheck/user/getmembrefamille/f1';
Future<String> _getfamilyList() async {
await http.get(familyInfoUrl).then((response) {
var data = json.decode(response.body);
print(data);
setState(() {
familysList = data['famille'];
});
});
}
}
as I see the problem is in your JSON response
famille is the first element ,
the list of data is the second element
however in your code familysList = data['famille'];
you are assuming that famille is a key and the list of data is the value and this is not correct
so answer is one of those
1 - make your asignment like familysList = data[0];
2- change your json to be {"famille" :[{"id":8654,"firstname":"some name"]"}
The error caused because although data's type is List that can access by 'int' index, you access like Map.
data['famille']
As your data body, data[0] is 'famille' and data[1] is data what you want.
data[1] is
[
{
"_id": "",
"firstName": "",
}
]
You can copy paste run full code below
Step 1: data[0] is String famille, data[1] is List you want
familysList = data[1];
Step 2: firsName is typo you need firstName
Step 3: DropdownMenuItem need value attribute
items: familysList?.map((item) {
return DropdownMenuItem<String>(
child: Text("${item["firstName"]}"),
value: item["firstName"],
);
})?.toList() ??
working demo
full code
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
_getfamilyList();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 15, right: 15, top: 5),
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton<String>(
value: _myfamily,
iconSize: 30,
icon: (null),
style: TextStyle(
color: Colors.black54,
fontSize: 16,
),
hint: Text('Select family'),
onChanged: (String Value) {
setState(() {
_myfamily = Value;
print("_myfamily $_myfamily");
});
},
items: familysList?.map((item) {
//print(item["firstName"]);
return DropdownMenuItem<String>(
child: Text("${item["firstName"]}"),
value: item["firstName"],
);
})?.toList() ??
[],
),
),
),
),
],
),
),
],
),
);
}
List familysList;
String _myfamily;
String familyInfoUrl =
'http://10.0.2.2:3000/genocheck/user/getmembrefamille/f1';
Future<String> _getfamilyList() async {
String jsonString = '''
[
"famille",
[
{
"_id" : "123",
"firstName":"abc"
},
{
"_id" : "456",
"firstName":"def"
}
]
]
''';
http.Response response = http.Response(jsonString, 200);
var data = json.decode(response.body);
print(data);
setState(() {
familysList = data[1];
});
/*await http.get(familyInfoUrl).then((response) {
var data = json.decode(response.body);
print(data);
setState(() {
familysList = data[1];
});
});*/
}
}
full code 2 for new question
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
class UserElement {
UserElement({
this.id,
this.firstName,
});
String id;
String firstName;
factory UserElement.fromJson(Map<String, dynamic> json) => UserElement(
id: json["_id"] == null ? null : json["_id"],
firstName: json["firstName"] == null ? null : json["firstName"],
);
Map<String, dynamic> toJson() => {
"_id": id == null ? null : id,
"firstName": firstName == null ? null : firstName,
};
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
_getfamilyList();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 15, right: 15, top: 5),
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton<UserElement>(
value: _myfamily,
iconSize: 30,
icon: (null),
style: TextStyle(
color: Colors.black54,
fontSize: 16,
),
hint: Text('Select family'),
onChanged: (UserElement Value) {
setState(() {
_myfamily = Value;
print("_myfamily ${_myfamily.firstName}");
});
},
items: familysList?.map((item) {
return DropdownMenuItem<UserElement>(
child: Text("${item.id} ${item.firstName}"),
value: item,
);
})?.toList() ??
[],
),
),
),
),
],
),
),
],
),
);
}
List<UserElement> familysList = [];
UserElement _myfamily;
String familyInfoUrl =
'http://10.0.2.2:3000/genocheck/user/getmembrefamille/f1';
Future<String> _getfamilyList() async {
String jsonString = '''
[
"famille",
[
{
"_id" : "123",
"firstName":"abc"
},
{
"_id" : "456",
"firstName":"abc"
}
]
]
''';
http.Response response = http.Response(jsonString, 200);
var data = json.decode(response.body);
//print(data);
setState(() {
List<dynamic> listData = data[1];
for (var i = 0; i < listData.length; i++) {
familysList.add(UserElement.fromJson(listData[i]));
}
});
/*await http.get(familyInfoUrl).then((response) {
var data = json.decode(response.body);
print(data);
setState(() {
familysList = data['famille'];
});
});*/
}
}

How to generate a list of cards based on the number of items in json file

I use the following code to generate 10 predefined cards on a flutter screen which doesn't change:
List cards = new List.generate(10, (i)=>new QuestionCard()).toList();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('My First App'),
backgroundColor:new Color(0xFF673AB7),
),
body: new Container(
child: new ListView(
children: cards,
)
)
);
}
}
class QuestionCard extends StatefulWidget {
#override
_QuestionCardState createState() => _QuestionCardState();
}
class _QuestionCardState extends State<QuestionCard> {
#override
Widget build(BuildContext context) {
return Container(
child: Card(
borderOnForeground: true,
color: Colors.green,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const ListTile(
trailing: Icon(Icons.album),
title: Text('Q1'),
subtitle: Text('What is the name of this location?'),
),
new TextFormField(
decoration: new InputDecoration(
labelText: "Answer Question",
fillColor: Colors.white,
border: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(25.0),
borderSide: new BorderSide(
),
),
//fillColor: Colors.green
),
validator: (val) {
if(val.length==0) {
return "Type your answer here";
}else{
return null;
}
},
keyboardType: TextInputType.text,
style: new TextStyle(
fontFamily: "Poppins",
),
),
ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('Save'),
onPressed: () {/* ... */},
),
],
),
],
),
),
);
}
}
My json is simple (questions.json):
{
"Questions":
[
{
"id" : 1,
"quest" : ["question one"]
},
{
"id" : 2,
"quest" : ["question two", "question three"]
},
{
"id" : 3,
"quest" : ["question four"]
},
{
"id" : 4,
"quest" : ["question five", "question six", "question seven"]
}
]
}
So I have 2 questions I need to solve:
1. If I have more than 1 question I'll need to add an additional text box for a response for which I'll use a different card type, 2, 3, 4 max, which I'll need to define once.
But my real question here:
How do I generate the list based on the json response:
Future _loadQuestionAssets() async
{
return await rootBundle.loadString('assets/questions.json');
}
Future loadQuestion() async{
String jsonString = await _loadQuestionAssets();
final jsonResponse = json.decode(jsonString);
Questions question = new Questions.fromJson(jsonResponse);
//List cards = new List.generate(length, generator)
}
try this:
class MyFirstApp extends StatefulWidget{
#override
createState()=> new _appState();
}
class _appState extends State<MyFirstApp>{
List _displayData;
//assuming that you saved your json file in your assets folder
loadJson() async {
String data = await rootBundle.loadString('assets/json/questions.json');
jsonResult = json.decode(data);
print(jsonResult);
setState((){
_displayData = jsonResult["Questions"];
});
}
#override
void initState(){
super.initState();
loadJson();
}
#override
Widget build(BuildContext context){
return Scaffold(
appbar: Appbar(
title: Text("My APP")
),
body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: _displayData == null ? Center(child: CircularProgressIndicator()) :
ListView.builder(
itemcount: _displayData.length,
itembuilder: (context, index){
return Container(
width: MediaQuery.of(context).size.width,
height: 80,
margin: EdgeInsets.only(bottom: 5),
child: Text(_displayData[index]["id"])
);
}
)
)
);
}
}

Flutter - ListView.builder() not working after the JSON fetch

I am trying to load the JSON from a local variable in my class file and convert the JSON to a list of objects using PODO class but I don't know why the list isn't getting generated. I am so much frustrated. Please help me where I am doing wrong.
I have tried every possible way by manipulating the code, even the same code works for a different JSON format with its PODO class.
Thank you.
Flutter class source code:
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class Demo extends StatefulWidget {
#override
_Demo createState() => _Demo();
}
class _Demo extends State<Demo> {
final localJson = '''
[
{
"message": "Some message here 1"
},
{
"message": "Some message here 2"
},
{
"message": "Some message here 3"
}
]
''';
Widget getCommentItem({#required PodoClass item}) {
return Text(item.message);
}
Future<List<PodoClass>> fetchComments() async {
return compute(parseJson, localJson);
}
List<PodoClass> parseJson(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<PodoClass>((json) => PodoClass.fromJson(json)).toList();
}
Widget _bodyBuild({#required List<PodoClass> items}) {
return ListView.builder(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.symmetric(vertical: 20, horizontal: 20),
itemCount: items.length,
itemBuilder: (BuildContext ctxt, int index) {
return getCommentItem(item: items[index]);
});
}
Widget body() {
return FutureBuilder<List<PodoClass>>(
future: fetchComments(),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? _bodyBuild(items: snapshot.data)
: Center(child: Text('Loading..'));
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: false,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"Comments",
),
Text("Battle ID", style: TextStyle(fontSize: 12))
])),
body: body());
}
}
// podo class
class PodoClass {
String message;
PodoClass({this.message});
PodoClass.fromJson(Map<String, dynamic> json) {
message = json['message'];
}
}
you must move parseJson function outside the class
it should be top level function
https://api.flutter.dev/flutter/foundation/compute.html
The callback argument must be a top-level function, not a closure or
an instance or static method of a class.
The compute does not work in this case, simply call the function.
I've included a delay which may help you with testing.
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class Demo extends StatefulWidget {
#override
_Demo createState() => _Demo();
}
class _Demo extends State<Demo> {
final localJson = '''
[
{
"message": "Some message here 1"
},
{
"message": "Some message here 2"
},
{
"message": "Some message here 3"
}
]
''';
Widget getCommentItem({#required PodoClass item}) {
return Text(item.message);
}
Future<List<PodoClass>> fetchComments() async {
await Future.delayed(Duration(seconds: 5));
return parseJson(localJson);
}
List<PodoClass> parseJson(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<PodoClass>((json) => PodoClass.fromJson(json)).toList();
}
Widget _bodyBuild({#required List<PodoClass> items}) {
return ListView.builder(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.symmetric(vertical: 20, horizontal: 20),
itemCount: items.length,
itemBuilder: (BuildContext ctxt, int index) {
return getCommentItem(item: items[index]);
});
}
Widget body() {
return FutureBuilder<List<PodoClass>>(
future: fetchComments(),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? _bodyBuild(items: snapshot.data)
: Center(child: Text('Loading..'));
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: false,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"Comments",
),
Text("Battle ID", style: TextStyle(fontSize: 12))
])),
body: body());
}
}
// podo class
class PodoClass {
String message;
PodoClass({this.message});
PodoClass.fromJson(Map<String, dynamic> json) {
message = json['message'];
}
}

How to create a gridview within a listview by flutter with json API

I'd like to create a shopping cart app
I had a problem
How to create a GridView within a ListView by flutter with JSON API
I want it exactly like this Example :
https://i.stack.imgur.com/2KQFG.png
https://i.stack.imgur.com/I0gY8.gif
--- Update ----
About SliverGrid
I tried to fetch the products but an error appeared (This is regarding the SliverGrid part)
I/flutter ( 5992): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 5992): The following assertion was thrown building FutureBuilder<List<dynamic>>(dirty, state:
I/flutter ( 5992): _FutureBuilderState<List<dynamic>>#78747):
I/flutter ( 5992): A build function returned null.
I/flutter ( 5992): The offending widget is: FutureBuilder<List<dynamic>>
I/flutter ( 5992): Build functions must never return null. To return an empty space that causes the building widget to
I/flutter ( 5992): fill available room, return "new Container()". To return an empty space that takes as little room as
I/flutter ( 5992): possible, return "new Container(width: 0.0, height: 0.0)".
With regard to ListView (It works fine)..
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
Future<List<dynamic>> getCategoriesApi() async {
http.Response response1 =
await http.get("http://159.89.228.206/");
Map<String, dynamic> decodedCategories = json.decode(response1.body);
//print(response1);
return decodedCategories['categories'];
}
Future<List<dynamic>> getProductsApi() async {
http.Response response =
await http.get("http://159.89.228.206/");
Map<String, dynamic> decodedCategories2 = json.decode(response.body);
// print(response);
return decodedCategories2['last'];
}
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(title: 'Sliver Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ScrollController _scrollController = new ScrollController();
List<dynamic> products;
List<dynamic> categories;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: Column(children: <Widget>[
Expanded(
child: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverToBoxAdapter(
child: SizedBox(
height: 120.0,
child: FutureBuilder(
future: getCategoriesApi(),
builder: (BuildContext context,
AsyncSnapshot<List<dynamic>> snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
Map<String, String> category =
snapshot.data[index].cast<String, String>();
return Card(
child: Container(
height: double.infinity,
color: Colors.grey[200],
child: Center(
child: Padding(
padding: EdgeInsets.all(30.0),
child: Text(category["name"]),
),
),
),
);
},
itemCount: snapshot.data.length,
);
} else {
return Center(child: CircularProgressIndicator());
}
}),
),
),
SliverToBoxAdapter(
child: Container(
child: FutureBuilder(
future: getProductsApi(),
builder: (BuildContext context,
AsyncSnapshot<List<dynamic>> snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
SliverGrid(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.8,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
Map<String, String> product = snapshot
.data[index]
.cast<String, String>();
return Card(
child: Container(
height: double.infinity,
color: Colors.grey[200],
child: Center(
child: Padding(
padding: EdgeInsets.all(30.0),
child: Text(product["name"]),
),
),
),
);
},
childCount: snapshot.data.length,
),
);
} else {
return Center(child: CircularProgressIndicator());
}
}),
),
),
],
),
)
]));
}
}
You can't embed a GridView directly in a ListView unless you play with the height reserved for the GridView. If you want to maintain the scroll for both sections as you show in your images, the best thing is to use a CustomScrollView and Slivers.
After the image is my proposal.
import 'dart:convert';
import 'package:flutter/material.dart';
String productsJson =
'{"last": [{"product_id":"62","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00"}, '
'{"product_id":"61","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00"}, '
'{"product_id":"57","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00"}, '
'{"product_id":"63","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00"}, '
'{"product_id":"64","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00"}, '
'{"product_id":"58","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00"}, '
'{"product_id":"59","thumb":"sandwich.png","name":"Test Tilte","price":"\$55.00"}]}';
String categoriesJson = '{"categories":['
'{"name":"Category 1","image":"icon.png","id":2}, '
'{"name":"Category 2","image":"icon.png","id":4}, '
'{"name":"Category 3","image":"icon.png","id":4}, '
'{"name":"Category 4","image":"icon.png","id":4}, '
'{"name":"Category 5","image":"icon.png","id":6}]}';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: 'Sliver Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ScrollController _scrollController = ScrollController();
List<dynamic> products;
List<dynamic> categories;
#override
initState() {
super.initState();
Map<String, dynamic> decoded = json.decode(productsJson);
products = decoded['last'];
Map<String, dynamic> decodedCategories = json.decode(categoriesJson);
categories = decodedCategories['categories'];
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverToBoxAdapter(
child: SizedBox(
height: 120.0,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
Map<String, String> category =
categories[index].cast<String, String>();
return Card(
child: Container(
height: double.infinity,
color: Colors.grey[200],
child: Center(
child: Padding(
padding: EdgeInsets.all(30.0),
child: Text(category["name"]),
),
),
),
);
},
itemCount: categories.length,
),
),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.8,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
Map<String, String> product =
products[index].cast<String, String>();
return Card(
child: Container(
color: Colors.grey[400],
child: Padding(
padding: EdgeInsets.symmetric(vertical: 30.0),
child: Center(
child: Text("Product ${product["product_id"]}")),
),
),
);
},
childCount: products.length,
),
),
],
),
);
}
}
If you want to retrieve the json from the network you can add/replace the following code. Add a method that returns a Future and then build the ListView using a FutureBuilder.
....
import 'package:http/http.dart' as http;
import 'dart:async';
....
Future<List<dynamic>> getCategories() async {
http.Response response = await http.get("http://159.89.228.206");
Map<String, dynamic> decodedCategories = json.decode(response.body);
return decodedCategories['categories'];
}
...
...
SliverToBoxAdapter(
child: SizedBox(
height: 120.0,
child: FutureBuilder(
future: getCategories(),
builder: (BuildContext context, AsyncSnapshot<List<dynamic>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
Map<String, String> category =
snapshot.data[index].cast<String, String>();
return Card(
child: Container(
height: double.infinity,
color: Colors.grey[200],
child: Center(
child: Padding(
padding: EdgeInsets.all(30.0),
child: Text(category["name"]),
),
),
),
);
},
itemCount: snapshot.data.length,
);
} else {
return Center(child: CircularProgressIndicator());
}
}),
),
),
....
The simple answer to this would be Tabs .
Render Tabs dynamically as per your category and under render GridView in TabView.
Here is Output :
Here is the Code :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Shopping App',
theme: ThemeData(
primarySwatch: Colors.orange,
),
home: ShowProductScreen(),
);
}
}
class Product {
String productId;
String image;
String name;
String price;
Product({this.productId, this.image, this.name, this.price});
}
class Category {
String id;
String name;
String image;
List<Product> productList;
Category({this.id, this.name, this.image, this.productList});
}
class ShowProductScreen extends StatefulWidget {
#override
_ShowProductScreenState createState() => _ShowProductScreenState();
}
class _ShowProductScreenState extends State<ShowProductScreen> with TickerProviderStateMixin {
List<Category> categoryList = List();
TabController _tabController;
#override
void initState() {
super.initState();
//Add data
for (int i = 0; i < 10; i++) {
List<Product> productList = List();
for (int j = 0; j < 50; j++) {
Product product = Product(
productId: "$i-$j",
price: "${(j + 1) * 10}",
name: "Product $i-$j",
image: "assets/image.jpg",
);
productList.add(product);
}
Category category = Category(
id: "$i",
name: "Category $i",
image: "assets/image.jpg",
productList: productList,
);
categoryList.add(category);
}
_tabController = TabController(vsync: this, length: categoryList.length);
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
titleSpacing: 0.0,
title: IconButton(
icon: Icon(
Icons.shopping_cart,
),
onPressed: () {},
),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.menu,
),
onPressed: () {},
)
],
),
body: Column(
children: <Widget>[
Container(
height: 100.0,
child: TabBar(
controller: _tabController,
isScrollable: true,
tabs: categoryList.map((Category category) {
return CategoryWidget(
category: category,
);
}).toList(),
),
),
Expanded(
child: Container(
padding: EdgeInsets.all(5.0),
child: TabBarView(
controller: _tabController,
children: categoryList.map((Category category) {
return Container(
child: GridView.count(
crossAxisCount: 2,
childAspectRatio: 4 / 3,
controller: ScrollController(keepScrollOffset: false),
scrollDirection: Axis.vertical,
children: category.productList.map(
(Product product) {
return ProductWidget(
product: product,
);
},
).toList(),
),
);
}).toList(),
),
),
)
],
),
);
}
}
class CategoryWidget extends StatelessWidget {
final Category category;
const CategoryWidget({Key key, this.category}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(4.0),
child: Image(
image: AssetImage(category.image),
height: 60.0,
),
),
Text(category.name)
],
),
);
}
}
class ProductWidget extends StatelessWidget {
final Product product;
const ProductWidget({Key key, this.product}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(4.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
border: Border.all(
color: Colors.orange,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image(
image: AssetImage(product.image),
fit: BoxFit.contain,
height: 80.0,
),
Text(product.name)
],
),
);
}
}
Hope it Helps!