An app without something to show on its screen gets pretty boring, right? So, the question which arises here is that from where can we get the interesting facts to display on our android app? From the cyberspace, of course!
There are thousands of websites which renders the information that can come in handy to spice up our apps through REST, or Representational State Transfer, APIs, which define a way to carry out the web services. Many sites enables us to generate an account to access resources such as images, data and by REST APIs. Almost all the mobile and web apps makes the use of JSON for tasks such as exchanging data with a web server. In this we will talk about how to work with JSON in Dart and Flutter.
Dart, being the fundamental language for any Flutter application is what we will be working in. We’ll approach this in a way that keeps us compatible with Flutter. In this article, we will cover the basics to analyse JSON in Dart. So that by the end you will have an application with a list view whose fields will be filled by data fetched from an API.
Step 1:
To procure the JSON data, first, we have to implement http methods, and in order to employ http methods in Flutter, and for doing so, we will have to import a library which allows some of the common http methods such as GET, POST, PUT, DELETE, etc.
The foremost thing to do here is to go to wer pubspec.yaml file and in the dependencies section, write:
http: ^0.12.0+1
Once, you are done with that, then save the file and flutter will spontaneously run the “flutter packages get” command. This step will be taken into consideration at least while working with the Visual Code Studio.
Step 2:
Now, traverse to wer main.dart file. And make sure to eliminate everything that’s in the file.
Let’s begin to do this by bringing in the libraries that we might require afterwards:
After that, bring in the ‘package:flutter/material.dart’;import ‘package:http/http.dart’ as http. The package which is used first is important for Material components. The second package is the one we insert in our project dependencies.
Now define an entry point in the application as:
void main(){runApp(NewApp())}; Or in short, void main() => runApp(NewApp());
Next, let’s define it as the NewApp class.
Step 3:
What we will do is, write all our code in one class, and the class will have multiple states, so this class will be Stateful.
class NewApp extends StatefulWidget { @override _NewAppState createState() => _NewAppState(); }class _NewAppState extends State<NewApp> { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold() body: Center(), ), ); } }
Step 4:
Now after building up a basic structure, let’s start working on procuring result with the help of an API. For that we’ll use JSONPlaceholder REST API. More specifically, https://jsonplaceholder.typicode.com/photos.
The JSON procured consists of a list of objects, each object with five attributes which are albumId, id, title, url and thumbnailUrl. Let’s just focus on three of the attributes for now. And those are:
- Id
- title,
- and thumbnailUrl
After that, we will form a class with these three attributes along with a constructor. We’ll also include a fromJson() factory method in the Photo class to make it easy to create a Photo object from a JSON object.
class Photo{ int id; String title, thumbnailUrl; Photo({this.id,this.title,this.thumbnailUrl}); factory Photo.fromJson(Map<String, dynamic> json) { return Photo( id: json['id'] as int, title: json['title'] as String, thumbnailUrl: json['thumbnailUrl'] as String, ); } }
Step 5:
Further, let’s create a network request.
Let’s create a function that will derive the JSON response and accordingly and correctly tranfigure it into a List of type Photo.
Flutter has a very great feature known as a Future. Future is a core Dart class for working with async operations. A Future object represents a potential value or error that will be available at some time in the future. Future is always accompanied by a data type (such as, Future<int> or Future<String>, etc.).
We cannot predict when our request will be improved. However, we may get our result after 1 second, 10 seconds or 10 minutes or there even might be chances of not getting a result at all due to some error and instead we might have to just settle down with an error. And that’s exactly why our function is not going to return a List<Photo>, but a Future<List<Photo>>.
The second important thing is the function will be marked async, i.e. it performs its operation asynchronously.
So basically,
Future<List<Photo>> obtainJson() async{ final response = await http.get('https://jsonplaceholder.typicode.com/photos'); //1 String responseBody = response.body; //2 dynamics jsonObject = json.decode(responseBody); //3 final convertedJsonObject = jsonObject.cast<Map<String, dynamic>>(); //4 List<Photo> list = convertedJsonObject.map<Photo>((json) => Photo.fromJson(json)).toList(); //5 return list; //6 }
- On the first line, we performed an http get request and marked it await so further operations in the function body will not be taken place until the getoperation has been returned. An http get operation returns a Future<Response> value. Thus, we’ve stored that value into the “response”
- Next we’ll attain the main body of this response. This body is of type String so we’ll hoard it into a variable “responseBody”of type String.
- Further, we’ll tranform this string into a JSON object. The json.decode() function will undertake a parameter of type string and it will reinstate the resulting JSON body.
- Now that we have a JSON object, and we will transfer it into a Map of type String, dynamic, i.e. Map<String, dynamic>. For that we’ll perform the cast method. And the resulting value will be hoarded in the “convertedJsonObject” variable.
- Now that we have a Map, we’ll pass it on as an argument to the fromJson, which returns an object of Photo. And this is supposed to happen for all the values. Moreover, we want a List of Photo, not just a Photo. And for that we use .toList().
- And finally we will be able to sreturn this list.
Step 6:
Next, a list will be needed for displaying the result. So we will require a ListView Widget, but our ListView might have no values while or before the http request returns any data. Or we might run into an error and not have a list to display, in which case we require some kind of a visual output to stipulate.
Thus, we’ll use a FutureBuilder. FutureBuilder which consists of two properties that’s very essential, one is future and other one is builder.
Here, the attribute future will specify the value, successful or otherwise, whereas, builder comes with switch cases to control each possible outcome.
You just need to make sure to define this variable in the build(_) function of wer _NewAppState.
var futureBuilder = new FutureBuilder( future: obtainJson(), builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: return Text('Press button to start.'); case ConnectionState.active: return Text('Awaiting result...'); case ConnectionState.waiting: return Text('Awaiting result...'); case ConnectionState.done: if (snapshot.hasError) return Text('Error:${snapshot.error}'); return listViewBuilder(context, snapshot.data); default: return Text('Some error occurred'); } } );
Though, we have not yet defined the listViewBuilder(_,_) function. So, let’s perform that in the next step. Also, that function will return a ListView.
Therefore, no matter what the connection state will be, the variable futureBuilder will always have a Widget value.
Step 7:
Now let’s define the listViewBuilder(_,_) function.
Widget listViewBuilder(BuildContext context,List<dynamic> values) { return ListView.builder( itemBuilder: (BuildContext context, int index) { return ListTile( leading: Text((values[index].id).toString()), title: Text((values[index].title).toString()), subtitle: Text((values[index].thumbnailUrl).toString()), ); } ); }
The values that have been passed are nothing but Photo object. So, we can easily access the values of Photo class which comprises of id, .title, and .thumbnailUrl. The .toString() changes these attribute values to string.
Step 8:
The last and perhaps a very crucial step is to make use of the Widget in futureBuilder variable. And for that, you will have to change wer Scaffold body into:
Scaffold( body: Center( child: futureBuilder, ), ),
In case we might have missed something, check the complete main.dart file:
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http;void main() => runApp(NewApp());class NewApp extends StatefulWidget { @override _NewAppState createState() => _NewAppState(); }class _NewAppState extends State<NewApp> { Future<List<Photo>> obtainJson() async { final response = await http.get('https://jsonplaceholder.typicode.com/photos'); String responseBody = response.body; dynamic jsonObject = json.decode(responseBody); final convertedJsonObject = jsonObject.cast<Map<String, dynamic>>(); List<Photo> list = convertedJsonObject.map<Photo>((json) => Photo.fromJson(json)).toList(); return list; } Widget listViewBuilder(BuildContext context, List<dynamic> values { return ListView.builder( itemBuilder: (BuildContext context, int index) { return ListTile( leading: Text((values[index].id).toString()), title: Text((values[index].title).toString()), subtitle: Text((values[index].thumbnailUrl).toString()), ); } ); } @override Widget build(BuildContext context) { var futureBuilder = new FutureBuilder( future: obtainJson(), builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: return Text('Press button to start.'); case ConnectionState.active: return Text('Awaiting result...'); case ConnectionState.waiting: return Text('Awaiting result...'); case ConnectionState.done: if (snapshot.hasError) return Text('Error: ${snapshot.error}'); return listViewBuilder(context, snapshot.data); default: return Text('Some error occurred'); } } ); return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( body: Center( child: futureBuilder, ), ), ); } }class Photo { int id; String title, thumbnailUrl; Photo({this.id, this.title, this.thumbnailUrl}); factory Photo.fromJson(Map<String, dynamic> json) { return Photo( id: json['id'] as int, title: json['title'] as String, thumbnailUrl: json['thumbnailUrl'] as String, ); } }