Creating services to do the work in your Flutter app

Suragch
Suragch
Jan 13 · 11 min read

A guide to using the GetIt package

Get your employees to do the heavy work.

If you are reading this article again for a quick refresher, scroll down to the Summary section at the bottom.

In a previous article I talked about an easy way to architect a Flutter app using the provider_architecture package by FilledStacks. The main idea was to use the MVVM pattern to remove any logic from the UI layout by putting it in a view model. This makes for a much cleaner and more maintainable app.

The View is the the widget tree for a single page.

It would be a mistake, though, to have the view models do all the work. Their main job is just to get the data ready to display in a view. The heavy work should be passed off to services. This is a further separation of concerns, which is a win for maintainable apps.

In that last article I mentioned services, but I didn’t talk much about how to make them. In this article I’ll cover services in more depth and show you how to use them in your own Flutter app. This topic is independent of MVVM, and you can use the service pattern even if you are using a different architectural style.

What are services?

Services are just normal Dart classes. Don’t think of them in the Android sense of the word, that is, a long-running background task. They are just classes that you write to do some specialized task in your app. You don’t even have to call them services. I’m just calling them that because that’s what FilledStacks calls them. You could call them helpers or repositories, but services is a good name and it matches the idea of microservices. Instead of being spread out across the network, though, they are contained within your app.

Here are a few common examples you might write a service to handle:

  • Read and write to local storage (database, shared preferences, files)
  • Access a web API
  • Log in a user
  • Perform some heavy calculation
  • Wrap Firebase or some other third-party package

The purpose of a service

The purpose of a service is to isolate some task and hide its implementation details from the rest of the app.

Think about why this is important.

Let’s say you’re using shared preferences to save some user data. You need to save and retrieve that data at multiple places across your app.

Calling shared preferences from multiple classes

You find yourself needing to save quite a bit of data, so you decide to use a SQL database instead of shared preferences. You go through and replace all the references to shared preferences with database code. PAIN. But you forget one place. BUG.

Later the maintainer of that database Pub package stops maintaining it. There is a more popular database package out now anyway, so you switch packages and update all of the references in the code. PAIN. And in one place you mess up a parameter. BUG.

Your app is getting more popular, but your users are starting to request syncing to multiple devices. So you decide that rather than saving the data in local storage, you should be saving it to your server. You make the change. All over your app. PAIN. You don’t want that pain, so you task it to the new guy. BUG.

Your app grows in popularity, but the load on your server starts to affect performance. You’d like to change to a cloud based solution but your backend is built with the same architectural style as your frontend. The cost of change has just too painful and there are far too many bugs. So your company shrivels up and dies. The end.

That was a contrived example, but the point is that when you have tight coupling to some function scattered around your code, it makes it difficult and error prone to change.

Here is where services come in. You make a new class and call it something like . The rest of the classes in the app don’t know how it works internally. They just call methods on the service to save and retrieve data.

The service is a black box to the code

This makes it easy to change. You want to switch from shared preferences to a database? No problem. Just change the code inside the service class. Same for switching third-party database packages or even switching to a web API. Updating the service code automatically affects everywhere the service is used inside the app.

It gets better than that. You can declare your service as an abstract class with methods that you want to make available to your app. Then extend the abstract service class with concrete implementations.

Implementations are a breeze to switch

This allows you to easily swap around implementations. You can also create a “fake” implementation that just returns some hard coded data. Doing so allows you to focus on the rest of your app and leave coding the service for later. It’s also a way to divide up the work if there is a team developing the app.

You implementation might itself rely on several other services. For example, your might use a local storage service for some types of data and a network service for other types of data. Again, this is an implementation detail that doesn’t matter to the rest of the app.

Example

I’ll show you how to create an actual service. Once you’ve done it a couple of times it becomes pretty easy. I’m going to be referring to the same example app that I showed you in A beginner’s guide to architecting a Flutter app. In that app we were using the MVVM style architecture. Here is the architecture again:

The view model will use the service to get some data and then notify its listeners (the view) so that the UI gets updated. It’s the same principle if you are using the BLoC pattern instead of view models.

Here are the steps you should follow when setting up a service:

1. Define your service with an abstract class

We’ll create a storage service that will save and retrieve an integer. Where that data is saved doesn’t matter at this point. We are just defining how we want our app to interact with the service.

Create a file called storage_service.dart in the folder. Paste in the following code:

Since the counter app data is an integer, we have two methods, one to save an and another to get it. This is the blueprint for what our actual service implementation will do.

This abstract class made a boundary, and we are free to work on either side of that boundary. We could work on implementing the storage service, or we could just use the storage service in our app as if it already worked. We’ll go ahead and implement the service first this time.

2. Implement the abstract service class

During development, I would probably start by creating a fake service that returns fake data so that I could get on with building the rest of the app before I worried about how I would implement the real service.

Fake implementation

Here is a fake implementation. It doesn’t actually save anything and it returns dummy data when you ask for it.

Create a file called storage_service_fake.dart. Paste in the following code:

Shared preferences implementation

This implementation will save to and retrieve data from shared preferences. Read this post about the details of using the shared_preferences package.

Create a new file called storage_service_shared_pref.dart. Paste in the following code:

This will save the counter value to shared preferences, that is, to local storage on the user’s device.

Sqflite Database implementation

Feel free to scroll past this section. A database is definitely overkill to save a single integer. I’m including it to make the point that your implementation details don’t matter to the rest of your app.

This implementation will save to and retrieve data from a SQLite database. Read this post about the details of using the sqflite package.

Create a new file called storage_service_database.dart. Paste in the following code:

Like I said, that was overkill.

Network implementation

This implementation will save to and retrieve data from a remote server. I didn’t feel like creating a remote server just for this example, so the following code is untested. However, if you are interested in creating an HTTP server using Dart, then read this introductory article and see my series on Server Side Dart with Aqueduct.

To make HTTP requests in Dart you can use the http package. Read this article for help getting set up.

Create a new file called storage_service_web.dart. Paste in the following code:

There are certainly many more options for implementing the abstract class that we defined above. I hope the examples here have given you a taste for how it’s done, though.

3. Create a service locator

A service locator is a central place to register all of the services that you use in your app. Using it you can access your services from anywhere within your code. It’s generally an alternative to dependency injection.

Some people complain that service locators are an anti-pattern and are hard to test. You can certainly still use services without a service locator if you like. Just inject a service in the constructor of whatever class needs it.

I really like service locators, though, because they’re super easy to use. Trying to do dependency injection with something like ProxyProvider can be quite confusing. I’m a big fan of things being easy to understand. As for being an “anti-pattern,” read this article by Martin Fowler, who commends it as a viable option. (But to balance that you can also read this article.) Regarding being hard to test, that’s not true. In just a minute I’ll show you how to test a class that uses a service locator.

The most popular service locator package for Flutter is GetIt. You can “get it” by by adding the dependency to pubspec.yaml.

Then create a new file called service_locator.dart in the folder. Paste in the following code:

You are creating a global variable here called that you can access from anywhere in your app. This is the service locator. You register the services in the method below it, which we will run at app startup.

Note that we are registering as a lazy singleton. It only gets initialized when it’s first used. If you want it to be initialized on app startup, then use instead. Since it’s a singleton, you’ll always have the same instance of your service.

Also note that we registered as the concrete implementation of . This is where the beauty of using an abstract class comes in. If we want to use one of the other implementations that we wrote above, all we have to do is change this single line of code.

I only registered one service here (), but you could just as easily register multiple services. For example, a login service or a Firebase service.

4. Initialize the service locator

You need to register the services on app startup, so you can do that in main.dart.

Replace the standard

with the following:

This will register any services you have with GetIt before the widget tree gets built.

5. Get your service

Now that the service locator has been initialized and the services registered, you can get a reference to a service anywhere in your app.

Let’s use the view model class example from A beginner’s guide to architecting a Flutter app. It was called counter_viewmodel.dart. Replace it with the following code:

All you have to do to find the service is to tell the service locator the service type: . This is enough for GetIt to get it. After that you can have your service do its work. Note that the code here doesn’t have any reference to the concrete implementation of the service. That’s an internal detail that doesn’t matter.

6. Test classes that use your service

It’s easy to test classes that use the service locator even though there is nothing passed into the constructor that you can mock.

Let’s test the view model class above. The test will be using the mockito package so see this article if you need help getting that set up.

In the folder create a file called counter_viewmodel_test.dart. Paste in the following code:

The key to mocking the service locator is to set to in the method. After that you can replace the service in the view model with a mock.

Trying it out

If you have followed along and adapted the architectural tutorial example app with the code I gave you here, you should be able to run it now with the fake implementation.

The full project is on GitHub if you had any problems.

In service_locator.dart, swap StorageServiceFake for StorageServiceSharedPreferences. Then run the app. Press the increment button a few times and rerun the app. It should use the stored value that it got from the service.

A service is used to save the app state.

Summary

Here is a quick summary without all the explanation. Bookmark this page and come back here when you need a refresher.

Abstract service class

Create a file called my_service.dart. In it make an abstract class that defines the functionality of your service.

Concrete service implementation

Create a file called my_service_impl.dart. Extend your class and implement the required methods.

Service locator

Add the GetIt service locator package to pubspec.yaml.

Create a file called service_locator.dart. Register your service implementation.

Initialize the service locator

Initialize the service locator before you run the app in main.dart.

Use the service

Get a reference to your service from anywhere in your code using the service locator.

Then you can use it within that class like this:

Conclusion

Services are a great tool for isolating functionality from the rest of your app, especially third-party packages that are volatile or that you may want change in the future. Using a service locator like GetIt is a convenient way to provide these services throughout your code.

Thanks to Dane Mackier of FilledStacks for first introducing me to GetIt and service locators. Check out his tutorial here.

Flutter Community

Articles and Stories from the Flutter Community

Suragch

Written by

Suragch

A Flutter and Dart developer with a background in Android and iOS. Follow me on Twitter @suragch1 for new article notifications.

Flutter Community

Articles and Stories from the Flutter Community

More From Medium

More from Flutter Community

More from Flutter Community

More from Flutter Community

Font Features in Flutter

191

More from Flutter Community

More from Flutter Community

Flutter — Shadows & glows

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade