How to fetch network data in Flutter (using http and ScopedModel)

Soon Sam Santos
7 min readAug 4, 2019

in this tutorial we are going to fetch Github users from Github API. However, instead of just focusing on http and placing the call in the main file, we are going to use ScopedModel. Github source is in the end of the article.

Add dependencies

  1. Add http: <newer_version> to the dependencies in your pubspec.yaml .
  2. Add scoped_model: <newer_version> to the dependencies in your pubspec.yaml .
  3. You can check the versions here: http and scoped_model.
dependencies:
flutter:
sdk: flutter

# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
scoped_model: ^1.0.1
http: ^0.12.0+2

1. Build the basic structure of the app

Our app is going to be simple, it will have a single page: UsersPage , which will display the Github users.

We will also need a User class to store the users information and a UserModel , where we are going to place the http call to fetch the data.

./main.dart

import 'package:flutter/material.dart';

import 'users.dart';

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Scoped Model Http',
routes: {
'/': (context) => UsersPage(),
},
);
}
}

./users.dart

import 'package:flutter/material.dart';

class UsersPage extends StatefulWidget {
@override
_UsersPageState createState() => _UsersPageState();
}

class _UsersPageState extends State<UsersPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Github Users'),
),
body: _buildBody(),
);
}

Widget _buildBody() {
// TODO
}
}

./model/user.dart

class User {
final String login;
final int id;
final String nodeId;
final String type;

User(this.login, this.id, this.nodeId, this.type);
}

./scoped_model/user_model.dart

import 'package:scoped_model/scoped_model.dart';

class UserModel extends Model {
// TODO
}

2. Implement http call

Step 1. Write your model

UserModel will be the one responsible to hold the list of all users and to have a method which fetch the users data.

./scoped_model/user_model.dart

import 'package:scoped_model/scoped_model.dart';
import 'package:http/http.dart' as http;

import '../model/user.dart';

class UserModel extends Model {
List<User> users = [];

void fetchUsers() async {
try {
http.Response response = await http.get('https://api.github.com/users');
if (response.statusCode == 200 || response.statusCode == 201) {
print(response.body);
}
} catch (error) {
print(error);
}
}
}
  1. When importing http, don't forge to put as http in front of the import statement, this will allow you to call the http methods by using http.<some_method> .
  2. users : This will hold a list of User .
  3. fetchUsers() : This is an async method, it means it is going to execute the next lines only when await line is done. Call http.get(uri) to fetch all users data. In the end, in case the response is successfully we print the json data.

Step 2. Wrap your app into a ScopedModel

I wrote an article with more details about How to use ScopedMode in Flutter. If you do not understand this part, check it out.

./main.dart

...
import 'package:scoped_model/scoped_model.dart';

import 'users.dart';
import './scoped_model/user_model.dart';

...

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final model = UserModel();
return ScopedModel<UserModel>(
model: model,
child: _buildMaterialApp(model),
);
}

MaterialApp _buildMaterialApp(UserModel model) {
return MaterialApp(
title: 'Scoped Model Http',
routes: {
'/': (context) => UsersPage(model),
},
);
}
}

We create a UserModel model variable so we can pass it to our _buildMaterialApp() method and then pass it to the constructor of UsersPage , because we are going to need an instance of our model in this page.

Step 3. Receive the model and make the call

./users.dart

...
import './scoped_model/user_model.dart';

class UsersPage extends StatefulWidget {
final UserModel model; // Receive a model

UsersPage(this.model);

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

class _UsersPageState extends State<UsersPage> {

@override
void initState() {
super.initState();
widget.model.fetchUsers();
}

...
}

Here we are first just receiving a UserModel variable in the constructor of our class.

Second, we use this model to call fetchUsers() method we've created in the step 1.

Note that to access the model from our State<> we need to call widget.model .

Run your app and test it. When you start your application UsersPage will be created and initState() will be called. Then, fetchUsers() will be called and in the end of a successful response it will print the response data.

This will output the following result.

[
{
“login”:”mojombo”,
”id”:1,
”node_id”:”MDQ6VXNlcjE=”,
”avatar_url”:”...",
"gravatar_id":"",
...
"received_events_url":"...",
"type":"User",
"site_admin":false
},
{
"login":"defunkt",
"id":2,
"node_id":"MDQ6VXNlcjI=",
"avatar_url":"...",
"gravatar_i":"..."
}
]

3. Load data into a ListView

Step 1. Save the data into your List

In our user_model we got a response from the code above. This is a List of User , however, the response does not know that each data in the List is a User . Therefore, we should suppose that our result is going to be a List of something we don't know yet. In flutter we use dynamic to refer to it.

./scoped_model/user_model.dart

import 'dart:convert';...void fetchUsers() async {
try {
http.Response response = await http.get('https://api.github.com/users');
if (response.statusCode == 200 || response.statusCode == 201) {
// 1. Create a List of Users
final List<User> fetchedUserList = [];
// 2. Decode the response body
List<dynamic> responseData = jsonDecode(response.body);
// 3. Iterate through all the users in the list
responseData?.forEach((dynamic userData) {
// 4. Create a new user and add to the list
final User user = User(userData['login'], userData['id'],
userData['node_id'], userData['type']);
fetchedUserList.add(user);
});
// 5. Update our list and the UI
_users = fetchedUserList;
notifyListeners();
}
} catch (error) {
print(error);
}
}

The code here is not so complex. Let's understand step by step.

  1. If the response is successful we end up here. We are going to create a new list of users where we are going to store each user from our response.
  2. Now we need to import the dart:convert to decode our response. The response.body will be in Json , we will decode it into a List<dynamic> .
  3. We iterate through all the users in the List<dynamic> responseData .
  4. We create a User with the data we get and add it to the List<User> we've created in the beginning.
  5. We save our List to the private variable we have in our scoped model: List<User> _users .

IMPORTANT: This call is going to take a while, it is asynchronous. When we open our UsersPage it will call fetchUsers() inside initState() . This means the users will start to be fetching since the page is created. Let's suppose our call will take 5 seconds to be completed. Hence, our page will be created, and after 5 seconds the users will be displayed on the screen.

In this 5 seconds, our List<User> _users inside our scoped model is empty yet. Then, we can not display the users. After the 5 seconds we need the scoped model to tell the UsersPage that we've completed the call and we should rebuild the UsersPage . This is the work of notifyListeners() .

Lifecycle for our structure!

Step 2. Load the List into a ListView

When building the body of our UsersPage we have to wrap it into a ScopedModelDescendant to get an instance of UserModel .

We will first of all check if our list of users is empty or not.

In case it is empty, it means we do not finished loading the list yet, then we display a Text saying there is no users (we will change for a CircularProgressIndicator latter).

In case it is not empty, we build the ListView .

./users.dart

Widget _buildBody() {
return ScopedModelDescendant<UserModel>(
builder: (BuildContext context, Widget child, UserModel model) {
if (model.users.length > 0) { // not empty
return _buildListView(model);
} else { // empty
return Center(
child: Text('No users'),
);
}
},
);
}

Widget _buildListView(UserModel model) {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(model.users[index].login),
subtitle: Text(model.users[index].nodeId),
);
},
itemCount: model.users.length,
);
}

To build the ListView we will use ListView.builder because we have lots of data and it helps us to load the data on demand. For the itemCount we pass the length of our users list and for the itemBuilder we just create a ListTile with the login and the nodeId .

4. Display a CircularProgressIndicator

We will create a new variable inside our UserModel : bool _isLoading , it is going to be responsible to tell if we are loading data or if we've finished it.

./scoped_model/user_model.dart

class UserModel extends Model {
bool _isLoading = false;
...

bool get isLoading {
return _isLoading;
}

void fetchUsers() async {
_isLoading = true; // here
notifyListeners();
try {
http.Response response = await http.get('https://api.github.com/users');
if (response.statusCode == 200 || response.statusCode == 201) {
final List<User> fetchedUserList = [];
List<dynamic> responseData = jsonDecode(response.body);
responseData?.forEach((dynamic userData) {
final User user = User(userData['login'], userData['id'],
userData['node_id'], userData['type']);
fetchedUserList.add(user);
});
_users = fetchedUserList;
}
_isLoading = false; // here
notifyListeners();
} catch (error) {
print(error);
_isLoading = false; // here
notifyListeners();
}
}
}

Whenever we update _isLoading variable we call notifyListeners() to tell our page we've changed something.

We set _isLoading to true in the beginning of fetchUsers() method to tell we are loading data. When data is loaded or if we end up in some error we set _isLoading to false to tell we've finished loading data.

./users.dart

Widget _buildBody() {
return ScopedModelDescendant<UserModel>(
builder: (BuildContext context, Widget child, UserModel model) {
Widget content = Text('No users');
if (model.isLoading) {
content = Center(
child: CircularProgressIndicator(),
);
} else if (!model.isLoading && model.users.length > 0) {
content = _buildListView(model);
}
return content;
},
);
}

Here we made small changes. We have by default a Text saying we have no users, then we check if we are loading or not.

If we are loading, display a loading indicator.

If we are not loading and there is users, we display the ListView with the users.

If there is no users, we display the default No Users text.

NOTE: If your project is working weirdly, try to close the app and rerun it. It will probably not work on hot reload.

Github source for the project is here.

--

--

Soon Sam Santos

Flutter and Delphi Developer at Green. Teacher at SoonClass