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.sstatic.net/2KQFG.png
https://i.sstatic.net/I0gY8.gif
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());
}
}),
),
),
....
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With