Getting started with Service Locator Pattern in Flutter.
The service locator pattern is a design pattern in software engineering used to encapsulate the processes involved in obtaining a service with a strong abstraction layer. The basic idea of a service locator pattern is to have a central registry (also knows as a Service Locator) that holds all the services that an application might need. It offers advantages like adding codes to the application at run-time without re-compiling, separating functionality in codebases, and removing code redundancy (Don’t Repeat Yourself).
In this tutorial, we will learn how to use the service locator pattern in flutter to consume multiple API services. For the backend services, we will be using the JSONPlaceholder. JSONPlaceholder is a free online API service.
You can code along by cloning this repository (main branch)
here. If you prefer to view the complete code, checkout to the dev branch of this same repository.
In this tutorial, we will focus on implementations only. The project UI has already been set up.
Prerequisite
We will be needing the following:
- Basic knowledge of dart and flutter.
- Flutter SDK
- Visual Studio Code or any Editor/IDE of your choice.
- Either IOS Simulator, Android Studio, or Chrome web browser to run our application.
Folder Structure
Let’s go over some of the key directories and files from above:
screens:
a folder to store the screens of our application.utils:
a folder to store reusable classes.widgets:
a folder to store building blocks of our application.main.dart:
the entry point of our application.
Running the Project
We need to navigate to the project location, open the terminal, and install project dependency.
Then run the project using the command below:
The command above will run the application on the selected device.
The application consists of screens to display a list of users and todos.
Let’s code
With the project up and running, we can start creating files and installing dependencies needed to create a service locator pattern in flutter.
Step 1
We need to add the flutter’s http and get_it packages to our codebase. The http package is a future-based library for making HTTP requests, while get_it is a package for creating service locators in flutter and dart projects.
Open the pubspec.yaml
file in the root directory, navigate to the dependencies
section, add the snippet below and save.
PS: Visual Studio Code installs the dependencies for us automatically when we save the file. We might need to stop our project and run flutter pub get to install the dependencies for other editors.
Step 2
We need to create a models
folder in the lib
directory and, in the folder, create a user_model.dart
and todo_model.dart
file. We will use the classes in these files to describe selected properties JSONPlaceholder users and JSONPlaceholder todos endpoint returns.
We also included a factory method to parse the data we obtain from the API.
Factory in dart lets us return an existing instance of a class instead of creating a new one. This process helps us improve application performance.
Step 3
We also need to create an api_response.dart
inside the models
folder to handle API responses and errors.
The APIResponse
class takes a Generic of type T to type the data the API returns. The class also has isError
and errorMessage
property to handle any error the application might encounter.
Step 4
We need to create a services
folder in the lib
directory and, in the folder, create a user_service.dart
file.
We need the user_service.dart
file to handle API calls to our user’s endpoint.
The above code performs these tasks:
- Import required dependencies.
- Creates a
UserService
class with a method to get a list of users. - Returns a type of
APIResponse
with parsed data (JSON format) when the request is successful and with errors if there are any.
Step 5
We also need to create a todo_service.dart
in the service
directory file to handle API calls to our todos endpoint.
Finally, Service Locator and Consuming the Services
With the services for handling API calls sorted out, we need to set up a service locator to manage all the services (UserService and TodoService) in our application.
Setting up Service Locator
To set up a service locator, we need to modify main.dart
file in the lib
directory
We imported the get_it package, UserService, and TodoService class. Then created a serviceLocator
function, and registered our services using one of the inbuilt GetIt
method( registerLazySingleton
). registerLazySingleton
creates an instance of the required service only on the first call of the service and, in turn, reduces app start-up time.
GetIt
has multiple methods that cater to different use cases. We can read more about the methods here.
Finally, we need to include the serviceLocator
function on the app start-up function.
Consuming the services
Get List of Users
First, we need to modify user_screen.dart
in the screen
directory as shown below:
We modified our code by:
- Importing required dependencies.
- Creating an instance of our UserService using the
GetIt
service locator. Declaring variables to initialize the API response, loading state, and length of the response. - Creating a
_fetchUser
method to make API call and manage states. Then, we called the function inside theinitState
.initState
initializes the function when the widget is inserted in the widget tree. - Wrapping the
ListView.builder
widget with aBuilder
widget. TheBuilder
helps us write a better conditional statement and avoid nested ternary operators. We also made provision for errors, loading state, and updated theUserCard
with the currentuser
object.
PS:
UserCard
will show an error about auser
parameter not defined. We will fix this by updating theuser_card.dart
as shown below
We initialize the UserCard
class with a required constructor and then modify the name, email, and username accordingly.
With this, our outcome should be like this.
Get List of Todos
We will also modify todo_screen.dart
in the screen
directory as shown below:
The bang(!) and question mark(?) in front of variables tells the compiler to relax the non-null constraint error (Meaning the parameter can be null)
Following the same pattern we did for users, we modified by:
- Importing required dependencies.
- Creating an instance of our TodoService using the
GetIt
service locator. Declaring variables to initialize the API response, loading state, and length of the response. - Creating a
_fetchTodo
method to make API call and manage states. Then, we called the function inside theinitState
.initState
initializes the function when the widget is inserted in the widget tree. - Wrapping the
ListView.builder
widget with aBuilder
widget. TheBuilder
helps us write a better conditional statement and avoid nested ternary operators. We also made provision for errors, loading state, and updated theTodoCard
with the currenttodo
object.
PS:
TodoCard
will show an error about atodo
parameter not defined. We will fix this by updating thetodo_card.dart
as shown below:
We also wrapped the nested Column
with a Flexible
widget to resolve RenderFlex Overflowed
errors.
With the error fixed, our outcome should be like this:
Conclusion
This post discussed the process of setting up a service locator pattern in flutter applications. We also learned how to separate the user interface from the logic and set up services without multiple instantiations of classes.
You may find these resources helpful: