Using Services in Flutter
Tags: flutter, dart, architecture, mobile
Published: 2021-11-01 14:14:19

One primary criterion for building a fully scalable and maintainable application is the reusability of code. Complex numbers of code repetitions would result in many potential bugs. When there needs to be a change in the package offering a particular service to the app, a painfully long process would be required to completely swap out implementations in the app. That itself is a nightmare for developers and can be the launching pad to failure for a product/company.

This article will introduce you to Services in Flutter, along with their key benefits, and show example code. This article assumes you have a Flutter development environment setup and have been building apps with Flutter. If not, you can check out this guide from Flutter on getting started building Flutter apps.

## Introduction

Services are classes that offer a specific functionality. A Service is a class that uses methods that enable it to provide a specialized feature, emphasizing specialized. A Service class offers just one distinct input to the app. Examples include:


- ApiService
- LocalStorageService
- ConnectivityService
- ThemeService
- MediaService etc.

Notice how each of the services listed out there offers one distinct feature. They keep the codebase as clean as possible and reduce the number of repeated codes on the codebase.

Services abstract functionalities got from a third-party source and reduced the dependence of the entire app on the goodwill of the package maintainer. For an app that depends directly on the packages, if the package maintainers abandon the package and there are no updates, it would break the app. When a decision is made later on to swap the package for another, there would be a lot involved in the process as developers would have to search through the codebase for places the package is used and then manually swap them. This process is painfully long and would cost development time and resources that the app could have better used.

The app does not depend on the 3rd party package but depends on the Service class. Hence, when there is a need for a swap, the only thing that would need to be changed would be functions from the packages the Service depends on.

## Getting started

Let's use the MediaService to fetch an image from the user's phone and displays it on the screen. The first thing is to create a new project.

```
    flutter create intro_to_service
```

Next, import [stacked](https://pub.dev/packages/stacked/install) and [image_picker](https://pub.dev/packages/image_picker) packages, which Flutter would use in the project in the dependencies section of the `pubspec` YAML.

```
    dependencies: 
     image_picker: ^0.8.4+3
     stacked: ^2.2.7
```

In the dev dependencies section, import the [build_runner](https://pub.dev/packages/build_runner) and [stacked generator](https://pub.dev/packages?q=stacked+generator), which would be responsible for generating files from the annotations used in the app.

```
    dev_dependencies:
     build_runner: ^2.1.4
     stacked_generator: ^0.5.5 
```

Next, head over to your `main.dart` file. Clear out the default counter app code, create a new material app, and pass an `HomeView` to the home parameter (The `HomeView` will be created later).

```
    import 'package:flutter/material.dart';
    import 'package:intro_to_services/app/app.locator.dart';
    import 'views/home_view/home_view.dart';
    void main() {
     runApp(MyApp());
    }
    class MyApp extends StatelessWidget {
     @override
     Widget build(BuildContext context) {
      return const MaterialApp(
       title: 'Material App',
       home: HomeView(),
      );
     }
    }
```

We would be making use of the [image_picker](https://pub.dev/packages/image_picker) package and picking the image from the gallery. 

Create a folder named services; inside this folder, create a new file titled `media_service.dart`. This is where the code for setting up the Service would be stored.

Next, create the method to get the image; we provide it with a parameter `fromGallery` to indicate if we would be using the Camera or taking the image from the gallery. The `image_picker` package gives us access to both.

This method would call the `pickImage` function the package gives us access to and use the `fromGallery` parameter to determine if we would get the image from the gallery or use the Camera.

```
    import 'dart:io';
    import 'package:image_picker/image_picker.dart';
    class MediaService {
     final ImagePicker _picker = ImagePicker();
     Future<File?> getImage({required bool fromGallery}) async {
      
      final XFile? image = await _picker.pickImage(
       source: fromGallery ? ImageSource.gallery : ImageSource.camera,
      );
      final File? file = File(image!.path);
      return file;
     }
    }
```

The `getImage` method then returns the file that was selected to be used inside the application. That wraps up the MediaService we would be using. 

Next, set up the locator file and register this Service that any class can use within the codebase. Create a new folder and name it `app`. In this folder, create a file named `app.dart`. This file would hold the setup for registering our services and other dependencies across the app. 

Inside the `app.dart` file, create a class named `AppSetup` and annotate it with the `@StackedApp` annotation. This annotation takes in a few parameters, among which are the routes and dependencies. The various services to be used within the app would be registered within the dependencies block of the `StackedApp` annotation.

```
    import 'package:intro_to_services/services/media_service.dart';
    import 'package:stacked/stacked_annotations.dart';
    @StackedApp(
     dependencies: [
      LazySingleton(classType: MediaService),
     ],
    )
    class AppSetup {}
```

Inside the block, we register the Service as a `LazySingleton`, meaning it won't be initialized until it is used in the application. The classtype is the name of the class, which is MediaService. 

Next, run the command to generate the locator file from the `StackedApp` annotation using the [stacked generator](https://pub.dev/packages?q=stacked+generator).

```
    flutter pub run build_runner build --delete-conflicting-outputs
```

There is a function in this generated file, the `setupLocator()` function, which sets up the environment and registers the Service. We call this function in the main block in the `main.dart` file.

```
    void main() {
     WidgetsFlutterBinding.ensureInitialized();
     setupLocator();
     runApp(MyApp());
    }
```

With this, we have successfully created the Service and registered it in the locator file, making the Service available for use in any part of the codebase. 

Next, set up the `homeView` and its `ViewModel`. Create a new folder in the lib directory titled views. Inside this folder, create the folder which would hold the homeView and homeViewModel files. Name this folder home_view. Create the two files and name them `home_view.dart` and `home_viewmodel.dart`. 

In the `homeViewModel` file, create a class named `HomeViewModel` which extends the `BaseViewModel` from the [stacked](https://pub.dev/packages/stacked/install) package. In this class, we first declare a variable that would access the `MediaService` through the locator. The locator gives us access to the entire Service and its methods. Next, we declare a private nullable image of File type. We then link it to a getter for access by outside classes. 

```
    import 'dart:io';
    import 'package:intro_to_services/app/app.locator.dart';
    import 'package:intro_to_services/services/media_service.dart';
    import 'package:stacked/stacked.dart';
    class HomeViewModel extends BaseViewModel {
     final mediaService = locator<MediaService>();
     File? _image;
     File? get imageFromGallery => _image;
    }
```

Next, set up the method to make the call to the Service and fetch the image. The call to the `getImage` function of the Service returns a file that we pass to the _image variable we created earlier. We then call `notifyListeners` provided by the `BaseViewModel` class from the [stacked](https://pub.dev/packages/stacked/install) package. The `notifyListerners()` call would inform the views bound to this `ViewModel` that there has been a state change and that the views should perform a rebuild. 

```
    Future<void> getImageFromGallery() async {
      _image = await mediaService.getImage(fromGallery: true);
      notifyListeners();
     }
```

Lastly, setting up the view itself. Create a stateless widget named `HomeView` in the home_view.dart file. This widget returns the `viewModelBuilder` widget from stacked, which binds the view to the `ViewModel`. We pass in the `ViewModel` to the `viewModelBuilder` function parameter and a Scaffold to the builder parameter.

```
    import 'package:flutter/material.dart';
    import 'package:stacked/stacked.dart';
    import 'home_viewmodel.dart';
    class HomeView extends StatelessWidget {
     const HomeView({Key? key}) : super(key: key);
     @override
     Widget build(BuildContext context) {
      return ViewModelBuilder<HomeViewModel>.reactive(
       viewModelBuilder: () => HomeViewModel(),
       builder: (context, viewModel, child) {
        return Scaffold();
       },
      );
     }
    }
```

For the UI itself, we create a TextButton and pass the function to get the image from the `ViewModel` in its onPressed Function; this would get the image from the gallery and makes it available for use in the view through the getter we declared in the `ViewModel` class.

```
     TextButton(
       onPressed: () {
        viewModel.getImageFromGallery();
       },
       child: const Text('Fetch Image'),
     )
```

Lastly, if the image is not null, we want to display the image in the view if the user has selected an image. Using the `image.file` from Flutter, we display the selected image from the gallery to the user in the UI.

```
     if (viewModel.imageFromGallery != null)
       Image.file(
        viewModel.imageFromGallery!,
         height: 30,
         width: 30,
       ),
```

Here is the complete code for the home_view.dart view.

```
    import 'package:flutter/material.dart';
    import 'package:stacked/stacked.dart';
    import 'home_viewmodel.dart';
    class HomeView extends StatelessWidget {
     const HomeView({Key? key}) : super(key: key);
     @override
     Widget build(BuildContext context) {
      return ViewModelBuilder<HomeViewModel>.reactive(
        viewModelBuilder: () => HomeViewModel(),
        builder: (context, viewModel, child) {
         return Scaffold(
          body: Center(
           child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
             if (viewModel.imageFromGallery != null)
              Image.file(
               viewModel.imageFromGallery!,
               height: 100,
               width: 100,
              ),
             TextButton(
              onPressed: () {
               viewModel.getImageFromGallery();
              },
              child: const Text('Fetch Image'),
             )
            ],
           ),
          ),
         );
        });
     }
    }
```

Check out the complete code for the sample app [here](https://github.com/Blazebrain/intro_to_services). Don't forget to drop a star on the repo.

## Conclusion

Hurray, you have successfully learned how to create a service, declare it and use it anywhere within your application.

Services reduce the number of codes that would be reused within the application making the codebase cleaner and better organized. Services also protect us from the pain of manually swapping out implementations when there is a change in the third-party package being used within the codebase.

Services offer benefits that speed up the development time and effectively use available resources. Not using them yet? Try them out, and you will see the impact it would make on your codebase.

If you have any questions, don't hesitate to reach out to me on Twitter: [@Blazebrain](https://www.twitter.com/Blazebrain01) or LinkedIn: [@Blazebrain](https://www.linkedin.com/in/david-adegoke).

Cheers!