Until now you have been working with static data. Let’s switch up the gears and bring some “real” data from the internet. In this tutorial you will learn how to make network calls in a flutter application. I assume you have basic understanding of http. You will make GET and POST requests in this tutorial.
The code in this tutorial can be found here.
Adding http dependency
Before anything you need to add the http library in your flutter project. Go to the link to grab the latest version (recommended) or just use the version I am using (not recommended, only if you are super lazy).
Open pubspec.yaml and paste the http dependency.
1 2 3 4 |
dependencies: flutter: sdk: flutter http: ^0.12.0+2 |
After this, open up a terminal and from the root of your project run the following command.
1 |
flutter packages get |
This will make sure all the packages mentioned in the pubspec.yaml are fetched and ready to be used by the project.
Making a GET request
We are going to use the JSON placeholder api for purpose of this tutorial. JSON Placeholder is a service that provides you with endpoints that emit fake JSON data, which is all we need for this tutorial.
For GET request we will hit the /posts api that returns a list of posts (format below). First you will display just the raw text from the response. After that you will parse the response as json and display the posts in a list.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[ { "userId": 1, "id": 1, "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" }, { "userId": 1, "id": 2, "title": "qui est esse", "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla" } ] |
Set up a basic hello world app in flutter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import 'package:flutter/material.dart'; void main() { runApp(HttpApp()); } class HttpApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: HttpExampleWidget(), ); } } class HttpExampleWidget extends StatefulWidget { @override _HttpExampleWidgetState createState() => _HttpExampleWidgetState(); } class _HttpExampleWidgetState extends State<HttpExampleWidget> { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text('Http Example'), ), ); } } |
For this you will need a StatefulWidget, you will see why in just a moment. You will be writing code in HttpExampleWidget. Let’s display the response from the api in a Text widget (you already have that in place, with the text ‘HttpExample’). For this you need to store the response from api somewhere inside the widget’s state, lets store that in a variables named _text.
1 2 3 4 5 6 7 8 9 10 11 12 |
class _HttpExampleWidgetState extends State<HttpExampleWidget> { String _text = "Http Example"; @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text(_text), ), ); } } |
The actual http call will be made in a method named _fetchPosts. This will be an async function. (If you don’t know how asynchronous methods work, read this tutorial).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class _HttpExampleWidgetState extends State<HttpExampleWidget> { String _text = "Http Example"; void _fetchPosts() async { } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text(_text), ), ); } } |
Import the http package.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; ... class _HttpExampleWidgetState extends State<HttpExampleWidget> { String _text = "Http Example"; void _fetchPosts() async { } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text(_text), ), ); } } |
The actually GET request is just one line! Await for the response from api, the returned object is of type Response which contains a property named body that has the data returned from the api. Once response is received, update the _text variable and call setState to make the UI re-render with updated data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class _HttpExampleWidgetState extends State<HttpExampleWidget> { String _text = "Http Example"; void _fetchPosts() async { final response = await http.get('https://jsonplaceholder.typicode.com/posts'); setState(() { _text = response.body; }); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text(_text), ), ); } } |
Now that you have written the _fetchPosts function, you need to call it somewhere. One option is calling it inside build method (wrong, never do something like this), the reason it is a bad practice is because you want the network request to be made once, build method doesn’t get called only once, the framework can call it at multiple occasions that are not under your control, for example when keyboard pops up, build method is triggered. Remember that we made this widget Stateful, we can use the initState method which is only called once when the state is created initially, it is a good place to call the _fetchPosts method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class _HttpExampleWidgetState extends State<HttpExampleWidget> { String _text = "Http Example"; void _fetchPosts() async { final response = await http.get('https://jsonplaceholder.typicode.com/posts'); setState(() { _text = response.body; }); } @override void initState() { super.initState(); _fetchPosts(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text(_text), ), ); } } |
Run the app, once the response from api is received it would look something like this:
Never miss a post from TheTechnoCafe
JSON parsing in Flutter
Right now you are just displaying the response from api as is, it is not very appealing. Lets parse this response and display it inside a ListView. Parsing JSON in flutter is very easy, just use the jsonDecode function from dart:convert package (in-built dart package).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; ... class _HttpExampleWidgetState extends State<HttpExampleWidget> { String _text = "Http Example"; void _fetchPosts() async { final response = await http.get('https://jsonplaceholder.typicode.com/posts'); final List<dynamic> parsedResponse = jsonDecode(response.body); setState(() { _text = response.body; }); } ... } |
The returned response from api is a list of JSON objects(with type Map<String, dynamic>), so jsonDecode returns a List, if response is not a List, it would return a Map<String, dynamic>.
Create a model class to hold a single Post.
1 2 3 4 5 6 7 8 9 10 11 |
class Post { final int userId; final int id; final String title; Post({ this.userId, this.id, this.title, }); } |
Every single JSON object inside the parsed list has a type of Map<String, dynamic> and contains the relevant Post data. You need a way to convert this Map<String, dynamic> into a Post object. A good practice is to write a factory constructor name fromJSON (the name clearly suggest that a Post object will be created from JSON data).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Post { final int userId; final int id; final String title; Post({ this.userId, this.id, this.title, }); factory Post.fromJSON(Map<String, dynamic> json) { return Post( id: json['id'], userId: json['userId'], title: json['title'], ); } } |
Convert the parsed response to List<Post> rather than List<dynamic>.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class _HttpExampleWidgetState extends State<HttpExampleWidget> { String _text = "Http Example"; void _fetchPosts() async { final response = await http.get('https://jsonplaceholder.typicode.com/posts'); final List<Post> parsedResponse = jsonDecode(response.body) .map<Post>((json) => Post.fromJSON(json)) .toList(); setState(() { _text = response.body; }); } ... } |
Display List<Post> using a ListView rather than displaying just plain old text. This would require a bit of refactoring and the resulting code is shown below. Go on and try it on your own, don’t just look at the code, practice it!
Replace the String _text with a List<Post> _posts. Use a ListView.builder to display the content in _posts, finally when response is received, update contents of _posts and call setState.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
class _HttpExampleWidgetState extends State<HttpExampleWidget> { List<Post> _posts = []; void _fetchPosts() async { final response = await http.get('https://jsonplaceholder.typicode.com/posts'); final List<Post> parsedResponse = jsonDecode(response.body) .map<Post>((json) => Post.fromJSON(json)) .toList(); setState(() { _posts.clear(); _posts.addAll(parsedResponse); }); } @override void initState() { super.initState(); _fetchPosts(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ListView.builder( itemCount: this._posts.length, itemBuilder: (context, index) { final post = this._posts[index]; return ListTile( title: Text(post.title), subtitle: Text('Id: ${post.id} UserId: ${post.userId}'), ); }, ), ), ); } } |
There you have it, this is how you fetch real data from network!
Making a POST request
I am not going to show you the entire application code for making a POST request, just the method that would make the actual request.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void _createPost() async { final response = await http.post( 'https://jsonplaceholder.typicode.com/posts', body: jsonEncode( { 'title': 'foo', 'body': 'bar', 'userId': 1, }, ), headers: {'Content-Type': "application/json"}, ); final Post parsedResponse = Post.fromJSON(jsonDecode(response.body)); } |
All you have to do is use the post method from http library instead of the get method (Line 2). post lets you specify body and headers. Headers are of types Map<String, dynamic>, but in case you want to send a body with content-type as application/json, the body needs to be encoded, this is where jsonEncode comes into play, its opposite to jsonDecode, while jsonDecode converts a JSON string response into Map<String, dynamic>, jsonEncode does the opposite, it converts a Map<String, dynamic> to encoded JSON String.
All the code show in this tutorial can be found here.
<< Previous Tutorial – Flutter Crash Course – 5 – Building Lists and Pages
1 Comment
Flutter Crash Course - 5 - Building Lists and Pages - TheTechnoCafe · September 2, 2019 at 7:08 pm
[…] Next Tutorial – Flutter Crash Course – 6 – Networking Basics >> […]