Below are the things that we are going to do.
1. Fetch data from Webservice
2. Create List of Objects from Webservice
3. Create View for each for in GridView
4. Call Webservice function in Main File with FutureBuilder.
5. Show Progress until data downloads
6. Show Error when some error happens.
7. Add Tap to the GridView Cell.
8. Pass Data to next screen from Row Click
9. Show record count in the HomeScreen TitleBar.
Watch Video Tutorial
So Let’s start one by one.
Below is the free json Webservice that we are going to use.
https://jsonplaceholder.typicode.com/photos
You can see around 5000 records in the below format.
{ "albumId": 1, "id": 1, "title": "accusamus beatae ad facilis cum similique qui sunt", "url": "https://via.placeholder.com/600/92c952", "thumbnailUrl": "https://via.placeholder.com/150/92c952" }, ...
Create the Model
We are going to create a model class for the above record now.
Create a new file named ‘album.dart’ and then create new class with the member variables as above.
class Album { int albumId; int id; String title; String url; String thumbnailUrl; Album({this.albumId, this.id, this.title, this.url, this.thumbnailUrl}); // Return object from JSON // factory Album.fromJson(Map<String, dynamic> json) { return Album( albumId: json['albumId'] as int, id: json['id'] as int, title: json['title'] as String, url: json['url'] as String, thumbnailUrl: json['thumbnailUrl'] as String); } }
The ‘fromJson‘ function creates an Album object from the json object provided to it. We will be using it in the next section during the service call.
Service Call
Add the below library under the dependencies in the ‘pubspec.yaml’ file which is present in your root folder.
import 'dart:convert'; import 'package:http/http.dart' as http; import 'album.dart'; class Services { static Future<List<Album>> getPhotos() async { try { final response = await http.get("https://jsonplaceholder.typicode.com/photos"); if (response.statusCode == 200) { List<Album> list = parsePhotos(response.body); return list; } else { throw Exception("Error"); } } catch (e) { throw Exception(e.toString()); } } // Parse the JSON response and return list of Album Objects // static List<Album> parsePhotos(String responseBody) { final parsed = json.decode(responseBody).cast<Map<String, dynamic>>(); return parsed.map<Album>((json) => Album.fromJson(json)).toList(); } }
Cell View for each row in the GridView
Our Cell will have a rounded Card Widget, with containers aligned to center. Also It will have one image and a Text below it.
This is how the code looks like. I have added some decorations to it, like rounded corners etc which is readable from the code itself.
import 'package:flutter/material.dart'; import 'album.dart'; class AlbumCell extends StatelessWidget { const AlbumCell(this.context, this.album); @required final Album album; final BuildContext context; @override Widget build(BuildContext context) { return Card( color: Colors.white, child: Padding( padding: EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0, top: 10.0), child: Container( alignment: Alignment.center, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Flexible( child: Image.network( album.thumbnailUrl, width: 150, height: 150, ), ), Padding( padding: EdgeInsets.all(10.0), child: Text( album.title, maxLines: 1, softWrap: true, textAlign: TextAlign.center, ), ), ], ), ), ), ); } }
Home Screen or Main UI with GridView
This is the screen that has the GridView. Our requirements for getting data from the service, model classes and parsing is now done.
Now let’s map it to the GridView.
My build function’s body will look like this
body: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: <Widget>[ Flexible( child: FutureBuilder<List<Album>>( future: Services.getPhotos(), builder: (context, snapshot) { // not setstate here // if (snapshot.hasError) { return Text('Error ${snapshot.error}'); } // if (snapshot.hasData) { streamController.sink.add(snapshot.data.length); // gridview return gridview(snapshot); } return circularProgress(); }, ), ), ], ), ... gridview(AsyncSnapshot<List<Album>> snapshot) { return Padding( padding: EdgeInsets.all(5.0), child: GridView.count( crossAxisCount: 2, childAspectRatio: 1.0, mainAxisSpacing: 4.0, crossAxisSpacing: 4.0, children: snapshot.data.map( (album) { child: GridTile( child: AlbumCell(album), ); }, ).toList(), ), ); } circularProgress() { return Center( child: const CircularProgressIndicator(), ); }
Make sure you import the classes we created above.
Then Services.getPhotos() will get the list of Album Objects. If there is some error, we will show a Text With Error. Show a CircularProgress until the UI is waiting for the data.
If everything is OK, then we will show our gridview by calling the gridview(snapshot). The snapshot will have the list of album records.
Add Click to GridView Cell
Wrapping the ‘GridTile‘ in each row with GestureDetector, then you can have onTap function for each cell in the GridView.
Code will change a little bit like this…
return GestureDetector( child: GridTile( child: AlbumCell(album), ), onTap: () { goToDetailsPage(context, album); }, ); ...
Navigate to Next Screen on Row Click
Create a new file named ‘details.dart’ for the new Screen and copy the below contents to it.
import 'package:flutter/material.dart'; import 'album.dart'; class GridDetails extends StatefulWidget { final Album curAlbum; GridDetails({@required this.curAlbum}); @override GridDetailsState createState() => GridDetailsState(); } class GridDetailsState extends State<GridDetails> { // @override Widget build(BuildContext context) { return Scaffold( body: Container( alignment: Alignment.center, margin: EdgeInsets.all(30.0), child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ FadeInImage.assetNetwork( placeholder: "images/no_image.png", image: widget.curAlbum.url, ), SizedBox( height: 30.0, ), OutlineButton( child: Icon(Icons.close), onPressed: () => Navigator.of(context).pop(), ), ], ), ), ); } }
Write the function to navigate To the DetailsScreen.
Make sure to import the details screen. The DetailsScreen will accept only one parameter, the ‘album’ object.
goToDetailsPage(BuildContext context, Album album) { Navigator.push( context, MaterialPageRoute( fullscreenDialog: true, builder: (BuildContext context) => GridDetails( curAlbum: album, ), ), ); }
Update the Title with Record Count
You have to remember one thing here, you cannot call setState inside the FutureBuilder like below…
Flexible( child: FutureBuilder<List<Album>>( future: Services.getPhotos(), builder: (context, snapshot) { // not setstate here // ...
One alternate is ‘streamController’.
import 'dart:async'; .... StreamController<int> streamController = new StreamController<int>(); ... // Title changes appBar: AppBar( title: StreamBuilder( initialData: 0, stream: streamController.stream, builder: (BuildContext context, AsyncSnapshot snapshot) { return Text('${widget.title} ${snapshot.data}'); }, ), ), ...
Complete Main File Code
import 'package:flutter/material.dart'; import 'services.dart'; import 'album.dart'; import 'gridcell.dart'; import 'details.dart'; import 'dart:async'; class GridViewDemo extends StatefulWidget { GridViewDemo() : super(); final String title = "Photos"; @override GridViewDemoState createState() => GridViewDemoState(); } class GridViewDemoState extends State<GridViewDemo> { // StreamController<int> streamController = new StreamController<int>(); gridview(AsyncSnapshot<List<Album>> snapshot) { return Padding( padding: EdgeInsets.all(5.0), child: GridView.count( crossAxisCount: 2, childAspectRatio: 1.0, mainAxisSpacing: 4.0, crossAxisSpacing: 4.0, children: snapshot.data.map( (album) { return GestureDetector( child: GridTile( child: AlbumCell(album), ), onTap: () { goToDetailsPage(context, album); }, ); }, ).toList(), ), ); } goToDetailsPage(BuildContext context, Album album) { Navigator.push( context, MaterialPageRoute( fullscreenDialog: true, builder: (BuildContext context) => GridDetails( curAlbum: album, ), ), ); } circularProgress() { return Center( child: const CircularProgressIndicator(), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: StreamBuilder( initialData: 0, stream: streamController.stream, builder: (BuildContext context, AsyncSnapshot snapshot) { return Text('${widget.title} ${snapshot.data}'); }, )), body: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: <Widget>[ Flexible( child: FutureBuilder<List<Album>>( future: Services.getPhotos(), builder: (context, snapshot) { // not setstate here // if (snapshot.hasError) { return Text('Error ${snapshot.error}'); } // if (snapshot.hasData) { streamController.sink.add(snapshot.data.length); // gridview return gridview(snapshot); } return circularProgress(); }, ), ), ], ), ); } @override void dispose() { streamController.close(); super.dispose(); } }
Complete Source Code
Get the complete source code from the below link…
https://bitbucket.org/vipinvijayan1987/tutorialprojects/src/master/FlutterTutorialProjects/flutter_demo1/lib/widgets/gridview/
Thanks for reading.
Please leave your valuable comments below.
I am quite new and your video was very helpful up to a level / what I missed was an introduction. I suspect that it is aimed at people with more experience than me (sorry about that).
I am can follow along and type the code and can more or less understand the key points of everything but I can really see how I plug this in to a new project.
What I mean is that you never seem to mention the main.dart file – you are working on a gridview_demo.dart and even when I look at your repository it contains what seem to be thousands of files and I suspect that the main.dart file has little to do with this demo.
Could you please share the main.dart file so that I can then run with the
Hi,
main.dart is just an entry point. You can configure any screen there, that’s why i didn’t show it.
import ‘package:flutter/material.dart’;
import ‘widgets/List/ListDemo.dart’;
void main() {
runApp(
new HomeApp(),
);
}
class HomeApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: ‘Flutter Tutorials’,
home: new ListDemo(),
);
}
}
Hi James, Many thanks for the video. I am relatively new and so could be missing something very obvious.
In your details.dart page, where you have a reference to widget (widget.curAlbum.id), where is widget declared?
Thanks!
Brijesh
widget is an inbuilt property of your stateful class. You can access its members from inside it’s State class using widget.
Hello sir thank you for great explanation and i want to create multiple gridview with tabbar using Api data in flutter thanks
Dear Tariq, What is blocking you?
Sir i have different category of books data like history books, general books, naval books … etc and i want to display in gridview separately with help of tab bar so should i create one model class or multiple model class for each that i can access all the books if you give me a rough idea it would be great thank you so much!
well done, very precise and understandable!!! i would like to have one on one tutoring on flutter. kindly inbox me.
mail me @coderzheaven@gmail.com