Flutter HTTP JSON ListView MasterDetail Images Text


Flutter HTTP JSON ListView MasterDetail Images Text Tutorial.

How to download JSON with images and Text via HTTP and bind to a custom listview with images and text. Then open a detail view of the listview when a listview item is clicked.

Demo

Here are demo for this project:

Flutter HTTP JSON ListView Images Text

Here's the detail page:

Flutter HTTP JSON ListView Images Text

JSON data

Here's the json data view. It's available in this url https://raw.githubusercontent.com/Oclemy/SampleJSON/338d9585/spacecrafts.json.

Flutter HTTP JSON ListView Images Text

Video Tutorial.

Here's the corresponding video tutorial.

Video Tutorial

Here we have a video tutorial for this example.

ListView

One of the most commonly used widgets in any mobile app development framework is the ListView. It normally renders items in a linear manner and is always very flexible. A good way of getting a feel of a framework is to render items in a listview. Especially images and text.

That's exactly what we do here. Then listen to listview itemClick events. Thus we show pass the clicked data to a new detail page/screen.

Here are several things you do here with a ListView

(a). How to Create a CustomListView

This ListView will be capable of rendering both images and text. It will render them in beautiful cardviews.

We will make the custom listview extend the StatelessWidget class:

class CustomListView extends StatelessWidget {..}

Inside the class we will have a method called createViewItem(). This method will define for us a single listview view item. Thata is a ListTile and its contents.

  Widget createViewItem(Spacecraft spacecraft, BuildContext context) {
    return new ListTile(
        title: new Card(
            ...

Our ListTiles will have CardViews. Those CardViews will have both image and texts.

(b). How to Listen to ListView itemClicks

Well you just use the onTap() callback.

        onTap: () {
          //..Open Second Page here.
        });

MaterialPageRoute

A MaterialPageRoute is a modal route that replaces the entire screen with a platform-adaptive transition.

(a). How to Create a MaterialPageRoute We create one using the new constructor.

          var route = new MaterialPageRoute(
            builder: (BuildContext context) =>
                new SecondScreen(value: spacecraft),
          );

(b). How to Open a Second Page We use the Navigator class. A Navigator is a widget that manages a set of child widgets with stack discipline.It allows us navigate pages. We invoke the push() method, passing in our MaterialPageRoute.

          Navigator.of(context).push(route);

http

http is the package in flutter that allows us perform our HTTP Requests. In this case we want to perform a HTTP GET request. This means we want to download some data.

First we need to import http package:

import 'package:http/http.dart' show get;

Then we need a jsonEndpoint, basically our url string. Then we invoke the get which is equivalent to http.get:

  final jsonEndpoint =
      "https://raw.githubusercontent.com/Oclemy/SampleJSON/338d9585/spacecrafts.json";

  final response = await get(jsonEndpoint);

We will check if our response is HTTP.OK which universally equals 200 response code.

We are making this operation asynchronous. We will be returning a Future<T> object. That means this is a delayed computation and our result is not going to be immediately available. Instead it is avaialable in the future, once our download is complete.

Future<List<Spacecraft>> downloadJSON() async {
  final jsonEndpoint =
      "https://raw.githubusercontent.com/Oclemy/SampleJSON/338d9585/spacecrafts.json";

  final response = await get(jsonEndpoint);

  if (response.statusCode == 200) {
    List spacecrafts = json.decode(response.body);
    return spacecrafts
        .map((spacecraft) => new Spacecraft.fromJson(spacecraft))
        .toList();
  } else
    throw Exception('We were not able to successfully download the json data.');
}

(a). main.dart

Here's the complete code.

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
import 'dart:convert';

class Spacecraft {
  final String id;
  final String name, imageUrl, propellant;

  Spacecraft({
    this.id,
    this.name,
    this.imageUrl,
    this.propellant,
  });

  factory Spacecraft.fromJson(Map<String, dynamic> jsonData) {
    return Spacecraft(
      id: jsonData['id'],
      name: jsonData['name'],
      propellant: jsonData['propellant'],
      imageUrl: jsonData['imageurl'],
    );
  }
}

class CustomListView extends StatelessWidget {
  final List<Spacecraft> spacecrafts;

  CustomListView(this.spacecrafts);

  Widget build(context) {
    return ListView.builder(
      itemCount: spacecrafts.length,
      itemBuilder: (context, int currentIndex) {
        return createViewItem(spacecrafts[currentIndex], context);
      },
    );
  }

  Widget createViewItem(Spacecraft spacecraft, BuildContext context) {
    return new ListTile(
        title: new Card(
          elevation: 5.0,
          child: new Container(
            decoration: BoxDecoration(border: Border.all(color: Colors.orange)),
            padding: EdgeInsets.all(20.0),
            margin: EdgeInsets.all(20.0),
            child: Column(
              children: <Widget>[
                Padding(
                  child: Image.network(spacecraft.imageUrl),
                  padding: EdgeInsets.only(bottom: 8.0),
                ),
                Row(children: <Widget>[
                  Padding(
                      child: Text(
                        spacecraft.name,
                        style: new TextStyle(fontWeight: FontWeight.bold),
                        textAlign: TextAlign.right,
                      ),
                      padding: EdgeInsets.all(1.0)),
                  Text(" | "),
                  Padding(
                      child: Text(
                        spacecraft.propellant,
                        style: new TextStyle(fontStyle: FontStyle.italic),
                        textAlign: TextAlign.right,
                      ),
                      padding: EdgeInsets.all(1.0)),
                ]),
              ],
            ),
          ),
        ),
        onTap: () {
          //We start by creating a Page Route.
         //A MaterialPageRoute is a modal route that replaces the entire
         //screen with a platform-adaptive transition.
          var route = new MaterialPageRoute(
            builder: (BuildContext context) =>
                new SecondScreen(value: spacecraft),
          );
          //A Navigator is a widget that manages a set of child widgets with
          //stack discipline.It allows us navigate pages.
          Navigator.of(context).push(route);
        });
  }
}

//Future is n object representing a delayed computation.
Future<List<Spacecraft>> downloadJSON() async {
  final jsonEndpoint =
      "https://raw.githubusercontent.com/Oclemy/SampleJSON/338d9585/spacecrafts.json";

  final response = await get(jsonEndpoint);

  if (response.statusCode == 200) {
    List spacecrafts = json.decode(response.body);
    return spacecrafts
        .map((spacecraft) => new Spacecraft.fromJson(spacecraft))
        .toList();
  } else
    throw Exception('We were not able to successfully download the json data.');
}

class SecondScreen extends StatefulWidget {
  final Spacecraft value;

  SecondScreen({Key key, this.value}) : super(key: key);

  @override
  _SecondScreenState createState() => _SecondScreenState();
}

class _SecondScreenState extends State<SecondScreen> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: new Text('Detail Page')),
      body: new Container(
         child: new Center(
            child: Column(
              children: <Widget>[
                Padding(
                  child: new Text(
                    'SPACECRAFT DETAILS',
                    style: new TextStyle(fontWeight: FontWeight.bold,fontSize: 20.0),
                    textAlign: TextAlign.center,
                    ),
                  padding: EdgeInsets.only(bottom: 20.0),
                ),
                Padding(
                  //`widget` is the current configuration. A State object's configuration
                  //is the corresponding StatefulWidget instance.
                  child: Image.network( '${widget.value.imageUrl}'),
                  padding: EdgeInsets.only(bottom: 8.0),
                ),
                Padding(
                  child: new Text(
                    'NAME : ${widget.value.name}',
                    style: new TextStyle(fontWeight: FontWeight.bold),
                    textAlign: TextAlign.left,
                    ),
                  padding: EdgeInsets.all(20.0),
                ),
                Padding(
                  child: new Text(
                    'PROPELLANT : ${widget.value.propellant}',
                     style: new TextStyle(fontWeight: FontWeight.bold),
                    textAlign: TextAlign.left,
                    ),
                  padding: EdgeInsets.all(20.0),
                )
                 ],   ),
        ),
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      theme: new ThemeData(
        primarySwatch: Colors.deepOrange,
      ),
      home: new Scaffold(
        appBar: new AppBar(title: const Text('JSON Images Text')),
        body: new Center(
          //FutureBuilder is a widget that builds itself based on the latest snapshot
          // of interaction with a Future.
            child: new FutureBuilder<List<Spacecraft>>(
            future: downloadJSON(),
            //we pass a BuildContext and an AsyncSnapshot object which is an
            //Immutable representation of the most recent interaction with
            //an asynchronous computation.
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                List<Spacecraft> spacecrafts = snapshot.data;
                return new CustomListView(spacecrafts);
              } else if (snapshot.hasError) {
                return Text('${snapshot.error}');
              }
              //return  a circular progress indicator.
              return new CircularProgressIndicator();
            },
          ),

        ),
      ),
    );
  }
}

void main() {
  runApp(MyApp());
}

(b). pubspec.yaml

We are not using any third party library.

name: json_images
description: A new Flutter project.

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

How to Download and Run.

We are not using any third party library hence all you need to do is copy the main.dart file into your project. The JSON is luckily available online so you just run the app and it will work.

How do You Feel after reading this?

According to scientists, we humans have 8 primary innate emotions: joy, acceptance, fear, surprise, sadness, disgust, anger, and anticipation. Feel free to tell us how you feel about this article using these emotes or via the comment section. This feedback helps us gauge our progress.

Help me Grow.

I set myself some growth ambitions I desire to achieve by this year's end regarding this website and my youtube channel. Am halfway. Help me reach them by:




Recommendations


What do You Think


Previous Post Next Post