Flutter Tutorial – Complete GridView Tutorial

By | February 7, 2019

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.

2 thoughts on “Flutter Tutorial – Complete GridView Tutorial

  1. Niall Brennan

    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

    Reply
    1. James Post author

      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(),
      );
      }
      }

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *