Mastering HTTP Calls in Flutter: An In-Depth Tutorial on Using Provider

Today we will build a flutter app where API calls will be done with the help of the provider package. Yes, you heard right, without using setState. We will do using the provider package.

If you like to learn by watching videos then there is good news for you. I made a video for the same a few days back.

Let’s learn step by step how we can do this. Before starting anything I will give a file-folder structure idea. I am sure it will help you a lot to visualize lots of things.

├── android
├── ios
├── lib
│   ├── main.dart
│   ├── model
│   │   └── todo.dart
│   ├── provider
│   │   └── todo_provider.dart
│   ├── screens
│   │   └── home_page.dart
│   └── services
│       └── todo_services.dart
|____main.dart
└── pubspec.yaml

Step 1

We will need to use the package called provider and http to complete the tutorial. In order to use that you have to add the package to the file pubspec.yaml file.

pubspec.yaml
name: provider_example
description: A new Flutter project.
version: 1.0.0+1

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.0
  http: ^0.13.5     # Added
  provider: ^6.0.3  # Added

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

Step 2

We will make the API to this endpoint https://jsonplaceholder.typicode.com/todos and render its response. We will start with making the model for its response.

We will get the response like this but there will be more objects in the list.

[
  {
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
  }
]

Let’s create the model for this object and we will keep this model as simple as we could. We will have data members as well as the constructor only. This will help us in focusing more on concepts rather than other things.

model/todo.dart
class Todo {
  final int id;
  final int userId;
  final String title;
  final bool completed;

  Todo({
    required this.id,
    required this.title,
    required this.userId,
    required this.completed,
  });
}

Step 3

In this step, we will create a service for todo, where API calls are done. Let’s create TodoService class for making the API call in this class.

As you already have seen my folder structure earlier. You already know what folder and file need to be created in this step.

We will create a file with the filename as todo_services.dart and the path will be lib/services/todo_services.dart.

services/todo_services.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:provider_api_call_example/model/todo.dart';

class TodoService {
  Future<List<Todo>> getAll() async {
    const url = 'https://jsonplaceholder.typicode.com/todos';
    final uri = Uri.parse(url);
    final response = await http.get(uri);
    if (response.statusCode == 200) {
      final json = jsonDecode(response.body) as List;
      final todos = json.map((e) {
        return Todo(
          id: e['id'],
          title: e['title'],
          userId: e['userId'],
          completed: e['completed'],
        );
      }).toList();
      return todos;
    }
    return [];
  }
}

Step 4

Creating TodoProvider class for keeping the state of API call. All the state data will be here and we will access them using the consumer.

As you already have seen my folder structure earlier. You already know what folder and file need to be created in this step.

We will create a file with the filename as todo_provider.dart and the path will be lib/provider/todo_provider.dart.

provider/todo_provider.dart
import 'package:flutter/material.dart';
import 'package:provider_api_call_example/model/todo.dart';
import 'package:provider_api_call_example/services/todo_services.dart';

class TodoProvider extends ChangeNotifier {
  final _service = TodoService();
  bool isLoading = false;
  List<Todo> _todos = [];
  List<Todo> get todos => _todos;

  Future<void> getAllTodos() async {
    isLoading = true;
    notifyListeners();

    final response = await _service.getAll();

    _todos = response;
    isLoading = false;
    notifyListeners();
  }
}

You might be having a few Questions about this class, we will talk about those at the end of the article.

Although, I said let’s introduce provider in this step we haven’t introduced provider full in this step. As you can see there is no import for the provider package.

Step 5

Let’s make the provider available for the app. In order to do this, we need to wrap our app using ChangeNotifierProvider. So we can be accessed down the tree and.

main
import 'package:provider/provider.dart';
import 'package:provider_api_call_example/provider/todo_provider.dart';
import 'package:provider_api_call_example/screens/home_page.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => TodoProvider(),
      child: const MaterialApp(
        home: HomePage(),
      ),
    );
  }
}

Step 6

Let’s build the User Interface and also consume the state from the provider. We will also call the calling API from this call using initState.

Using Consumer we can consume the Provider, Consumer helps us to rebuild the widget based on notifyListeners().

There are two different UI based on their state

  • When the API is being called and the data is loading. We are showing a circular progress bar.
  • When the API call is completed and the data is available. We are showing the list of todos.
screens/counter.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_api_call_example/provider/todo_provider.dart';

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      Provider.of<TodoProvider>(context, listen: false).getAllTodos();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Provider API'),
      ),
      body: Consumer<TodoProvider>(
        builder: (context, value, child) {
          // If the loading it true then it will show the circular progressbar
          if (value.isLoading) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
          // If loading is false then this code will show the list of todo item
          final todos = value.todos;
          return ListView.builder(
            itemCount: todos.length,
            itemBuilder: (context, index) {
              final todo = todos[index];
              return ListTile(
                leading: CircleAvatar(
                  child: Text(todo.id.toString()),
                ),
                title: Text(
                  todo.title,
                  style: TextStyle(
                    color: todo.completed ? Colors.grey : Colors.black,
                  ),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

Provider Quick Question

  • Why this variable is private _todos?

We don’t want this variable should be updated directly because we want wherever this variable is being used that value also should be updated. If we update the value and don’t call notifyListeners, it will not do so.

  • What is ChangeNotifier?

When we extend this class, we get access to the change notification API which can tell the listener that some change had been made.

  • why are we calling notifyListeners() ?

notifyListeners() comes from the class ChangeNotifier and it tells the listener that this call value has been updated.

As I mentioned we tell the listener about the changes, then how does the listener get a callback

Source Code

In code, you would like to get the source code for this project. You can find it easily on GitHub at this given link.

Github Repository
Download

Counter App using provider

Dart 5