Fetching data from internet is one of the most common operations that every developers should master. In Flutter, this operation is pretty straightforward. In this post we will learn how to handle data from fetching to displaying it on our mobile apps.
First thing first, we need to know where we are going to fetch the data from. We will use Dummy Rest API Example. You can always choose another source of data, but for the purpose of this post this API key is more than enough. Also, we don't need to register anything to make an API calls to this dummy API.
Second, we need to install the required plugins for this app through
pubspec.yaml
.dependencies:
json_annotation: ^2.4.0
http: 0.12.0+4
dev_dependencies:
build_runner: ^1.0.0
json_serializable: ^3.0.0
Then run
flutter pub get
Now we are good to go.
Just like other OOP languages, we need to create model class for our incoming data. Let's start by creating
models.dart
.Since most data are JSON typed when you fetch it from the internet, we will need to handle it because JSON data are not automatically parsed. To do so we will use json_serializable plugin, which will do the dirty jobs for us.
import 'package:json_annotation/json_annotation.dart';
part 'model.g.dart';
@JsonSerializable()
class Employees {
final String id;
@JsonKey(name: 'employee_name')
final String employeeName;
@JsonKey(name: 'employee_salary')
final String employeeSalary;
@JsonKey(name: 'employee_age')
final String employeeAge;
@JsonKey(name: 'profile_image')
final String profileImage;
Employees({this.id, this.employeeName, this.employeeSalary, this.employeeAge, this.profileImage});
factory Employees.fromJson(Map<String, dynamic> json) => _$EmployeesFromJson(json);
Map<String, dynamic> toJson() => _$EmployeesToJson(this);
}
There will be errors because we refer to a non-existent variable and file. In order to wipe those errors, run build runner to generate additional dart file for our model:
pub run build_runner build
In this part, we will separate the UI and logic of the apps. Let's start by creating
network_helper.dart
Handling requests in Flutter is easy. Take a look at the code below:
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../models/model.dart';
const url = "http://dummy.restapiexample.com/";
class GetEmployee {
String getPath() {
return url + "api/v1/employees";
}
Future<List<Employees>> getEmployees() async {
final res = await http.get(getPath());
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
List data = json['data'];
return data.map((employees) => new Employees.fromJson(employees)).toList();
} else {
throw Exception('Failed to fetch data');
}
}
}
First we import
dart:convert
. We use this package in order to use jsonDecode()
method. This method will be responsible for parsing our json to be readable by dart.Notice that we use
Future
in the beginning of our declaration. This will tell Flutter that we want to return a Future
object when we call getEmployees()
. A
Future
is used to represent a value that will be available in some time in the future. For a more advanced explanation, read here.After we receive the data we fetched from the internet, we need to update our widget so that it can display the data in our app.
import 'package:flutter/material.dart';
import '../../models/model.dart';
import '../../controllers/network_helper.dart';
class HomeScreen extends StatefulWidget {
static const String id = "home_screen";
static const String title = "Home";
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(HomeScreen.title),
),
body: Center(
child: _employeesData()
)
);
}
}
Here, we are dealing with asynchronous data binding. When the app start, the widgets were built as immutable for performance reasons. Therefore, we need to let flutter know which widgets may change during the runtime. This is where state management takes place.
In our app, we set
HomeScreen
as a stateful widget. This gives flutter information that HomeScreen
may need to rebuild during runtime. In this case, HomeScreen
will receive data that we fetched previously. But we need a method to handle the data we receive. Take a look at the code below:FutureBuilder _employeesData(){
return FutureBuilder<List<Employees>>(
future: GetEmployee().getEmployees(),
builder: (BuildContext context, AsyncSnapshot<List<Employees>> snapshot){
if (snapshot.hasData) {
List<Employees> data = snapshot.data;
return _employees(data);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
);
}
Now, we create a widget that will be responsible to handle the asynchronous process, the
FutureBuilder
. This widget will call the operation to fetch the data that we have created before, and as soon as the return value comes, it will call the function to build the widget to display our data.ListView _employees(data) {
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return Card(
child: _tile(data[index].employeeName, data[index].employeeSalary, Icons.work)
);
}
);
}
ListTile _tile(String title, String subtitle, IconData icon) {
return ListTile(
title: Text(title,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20,
)),
subtitle: Text(subtitle),
leading: Icon(
icon,
color: Colors.blue[500],
),
);
}
That's it! Please put a comment if you have any questions. The source of this article is available on github
Thank you for reading!