Load data from internet to a GridView in Flutter with Retry Logic, JSON parsing and Error handling in Flutter

By | December 21, 2018

I am assuming that you already have some understanding about Flutter.
If you have no information about Flutter SDK, then I could recommend first going to flutter.io and get the initial knowledge.

Our Final app will look like this.

You can watch the complete video tutorial from here…

App will implement

  • GridView
  • JSON Implementation
  • Error Handling
  • Retry Implementation

At first we will create the cell for the GridView in our App.

Don’t be scared about the long set of code, most of them are just for styling purposes, since all views in Flutter are Widgets, even for applying padding, we have to add Padding Widget.
So be cool and start…

Go ahead and create a dart file named cell_model.dart

Cell Model

we are using this sample service from the web to get the data.
[https://jsonplaceholder.typicode.com/photos].

This is the sample output from the above url.

[
  {
    "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"
  },
  {
    "albumId": 1,
    "id": 2,
    "title": "reprehenderit est deserunt velit ipsam",
    "url": "https://via.placeholder.com/600/771796",
    "thumbnailUrl": "https://via.placeholder.com/150/771796"
  }
]

We will be mapping the above output to the below model and apply it to the GridView.

class CellModel {
  int albumId;
  int id;
  String title;
  String url;
  String thumbnailUrl;

  CellModel({this.albumId, this.id, this.title, this.url, this.thumbnailUrl});

  factory CellModel.fromJson(Map<String, dynamic> json) {
    return CellModel(
        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);
  }
}

Cell For the GridView

Now Let’s create the view for each cell in the GridView.
Create a new dart file named “cell.dart” and copy the below contents to it.

We have a Text and an Image in each cell of the GridView.


import 'package:flutter/material.dart';
import 'cell_model.dart';

class Cell extends StatelessWidget {
  const Cell(this.cellModel);
  @required
  final CellModel cellModel;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: Padding(
          padding:
              new EdgeInsets.only(left: 0.0, right: 0.0, bottom: 0.0, top: 0.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              Image.network(cellModel.thumbnailUrl,
                  width: 150.0,
                  height: 150.0,
                  alignment: Alignment.center,
                  fit: BoxFit.fill),
              Padding(
                padding: EdgeInsets.all(10.0),
                child: Text(
                  cellModel.title,
                  textAlign: TextAlign.center,
                  overflow: TextOverflow.ellipsis,
                  style: TextStyle(fontWeight: FontWeight.w500),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Ok. Now we have the cell’s model and the View for the GridView Cell.

Now Lets create the service call

Service call

To make service calls we have to add a package to flutter.
For that, open pubspec.yaml which is present in the root of your project.

dependencies:
  flutter:
    sdk: flutter

  http: "0.11.3+17"

Now we will write the service call. Let’s create a new dart file named “services.dart” and copy the below content to it.

import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'cell_model.dart';
import 'constants.dart';

class Services {
  static Future<List<CellModel>> fetchHomeData() async {
    final response = await http.get(APPURLS.SAMPLE_URL);
    try {
      if (response.statusCode == 200) {
        List<CellModel> list = parsePostsForHome(response.body);
        return list;
      } else {
        throw Exception(MESSAGES.INTERNET_ERROR);
      }
    } catch (e) {
      throw Exception(MESSAGES.INTERNET_ERROR);
    }
  }

  static List<CellModel> parsePostsForHome(String responseBody) {
    final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
    return parsed.map<CellModel>((json) => CellModel.fromJson(json)).toList();
  }
}

Don’t worry about the errors now, we will be fixing it soon.

We have a constants file where we define the constants. Let create another file named “constants.dart”.

Constants

import 'package:flutter/material.dart';

class APPURLS {
  static const String SAMPLE_URL =
      "https://jsonplaceholder.typicode.com/photos";
}

class MESSAGES {
  static const String INTERNET_ERROR = "No Internet Connection";
  static const String INTERNET_ERROR_RETRY =
      "No Internet Connection.\nPlease Retry";
}

class COLORS {
  // App Colors //
  static const Color DRAWER_BG_COLOR = Colors.lightGreen;
  static const Color APP_THEME_COLOR = Colors.green;
}

OK now, we have all the prerequisites.

We will create one more dart file for the Common components we will use in the application, such as AppBar, Retry Button, Progress-bar and the GridView.

Create a new file named “common_comp.dart”

Common components

import 'package:flutter/material.dart';
import 'constants.dart';
import 'cell.dart';
import 'cell_model.dart';

class ComComp {
  static Padding text(String text, FontWeight fontWeight, double fontSize,
      List padding, Color color, TextOverflow overflow) {
    return Padding(
      padding: new EdgeInsets.only(
          left: padding[0],
          right: padding[1],
          top: padding[2],
          bottom: padding[3]),
      child: Text(
        text,
        textAlign: TextAlign.left,
        overflow: overflow,
        style: TextStyle(
          fontWeight: fontWeight,
          fontSize: fontSize,
          color: color,
        ),
      ),
    );
  }

  static AppBar getAppBar(Color color, String title) {
    return AppBar(
      backgroundColor: COLORS.APP_THEME_COLOR,
      title: Center(
        child: new Text(title.toUpperCase()),
      ),
      actions: <Widget>[],
    );
  }

  static CircularProgressIndicator circularProgress() {
    return CircularProgressIndicator(
      valueColor: new AlwaysStoppedAnimation<Color>(COLORS.APP_THEME_COLOR),
    );
  }

  static GestureDetector internetErrorText(Function callback) {
    return GestureDetector(
      onTap: callback,
      child: Center(
        child: Text(MESSAGES.INTERNET_ERROR),
      ),
    );
  }

  static Padding homeGrid(
      AsyncSnapshot<List<CellModel>> snapshot, Function gridClicked) {
    return Padding(
      padding:
          EdgeInsets.only(left: 20.0, right: 20.0, bottom: 20.0, top: 30.0),
      child: GridView.builder(
        shrinkWrap: true,
        itemCount: snapshot.data.length,
        gridDelegate:
            SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
        itemBuilder: (BuildContext context, int index) {
          return GestureDetector(
            child: Cell(snapshot.data[index]),
            onTap: () => gridClicked(context, snapshot.data[index]),
          );
        },
      ),
    );
  }

  static FlatButton retryButton(Function fetch) {
    return FlatButton(
      child: Text(
        MESSAGES.INTERNET_ERROR_RETRY,
        textAlign: TextAlign.center,
        overflow: TextOverflow.ellipsis,
        style: TextStyle(fontWeight: FontWeight.normal),
      ),
      onPressed: () => fetch(),
    );
  }
}

HomeScreen with GridView

Here is the complete screen that implements the GridView and the Retry Logic.

Launch this Widget from the main.dart

import 'package:flutter/material.dart';
import 'cell_model.dart';
import 'common_comp.dart';
import 'constants.dart';
import 'services.dart';

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool isHomeDataLoading;

  @override
  void initState() {
    super.initState();
    isHomeDataLoading = false;
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      backgroundColor: Colors.white,
      appBar: ComComp.getAppBar(COLORS.APP_THEME_COLOR, "Home"),
      body: Center(
        child: Container(
          child: FutureBuilder<List<CellModel>>(
            future: Services.fetchHomeData(),
            builder: (context, snapshot) {
              return snapshot.connectionState == ConnectionState.done
                  ? snapshot.hasData
                      ? ComComp.homeGrid(snapshot, gridClicked)
                      : ComComp.retryButton(fetch)
                  : ComComp.circularProgress();
            },
          ),
        ),
      ),
    );
  }

  setLoading(bool loading) {
    setState(() {
      isHomeDataLoading = loading;
    });
  }

  fetch() {
    setLoading(true);
  }
}

gridClicked(BuildContext context, CellModel cellModel) {
  // Grid Click
}

Main Widget

Finally launch our Home Page from the main.dart file.

import 'package:flutter/material.dart';
import 'home_page.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Demo App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(),
    );
  }
}

And that’s it. Leave your valuable comments below.

Leave a Reply

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