Flutter Image Uploader With App Permissions and Compression Using GetIt Services

Roaa Khaddam
Flutter Community
Published in
5 min readDec 12, 2021

Allow image upload in your Flutter app with one line of code in your widget while handling everything from camera & gallery permissions to image compression under the hood with GetIt and the service locator pattern.

TL;DR

Github repo for full code. (see lib/avatar-uploader-tutorial folder)

The final outcome:

Camera option upload, gallery option upload, and camera and gallery options when permission is denied.

What is this about?

Isn’t it awesome when your UI layer widgets are clean and don’t have to worry about any background processes or business logic? In this tutorial, we’re going to create an avatar uploader widget that when pressed:

  1. Shows a modal to give the user “Take Photo” and “Upload From Gallery” options. (Who the hell takes a photo right away 😰 ????).
  2. Then according to the picked option, asks for permission.
  3. If permission was granted, the camera or gallery opens and allows the user to pick an image that then gets compressed (optional) and added into the widget.
  4. If permission was denied the process terminates, and when clicked again, a dialog shows up that gives the user the option to go to app settings.

Enough talk, let’s get to it

source: https://giphy.com

Dependencies

Here are the packages you’ll need to add to your pubspec.yaml.

get_it: latest
flutter_image_compress: latest
image_picker: latest
permission_handler: latest
path_provider: latest

(If you’re not familiar with the service locator pattern, I will write an article about it very soon. But for now, the documentation of the get_it package might help. But you can easily follow along and it will become clearer for you by the end of this tutorial)

Permission Service

We’ll start by setting up the permission service by creating our abstract PermissionService class in the file data/services/permission/permission_service.dart

abstract class PermissionService {
Future requestPhotosPermission();

Future<bool> handlePhotosPermission(BuildContext context);

Future requestCameraPermission();

Future<bool> handleCameraPermission(BuildContext context);
}

For now it contains methods for photos and camera permissions only, but you can add location, audio, or any other permission depending on what your app needs.

We’ll implement this service using the permission_handler package (don’t forget to set it up for Android and iOS, if you have any trouble doing that, don’t hesitate to reach out!).

So we’ll create a file next to the abstract class and name it PermissionHandlerPermissionService.dart

Here the request — functions show the native popup that asks for corresponding permission, and the handle — functions call the request functions and if the permission was denied, they show a dialog that allows the user to go to app settings directly.

You can access the AppAlertDialog code from here.

Initializing GetIt Service

Now in order to use this service, we’ll create a service_locator.dart file inside our services folder and add the following code

final getIt = GetIt.instance;

setupServiceLocator() {
getIt.registerSingleton<PermissionService>
(PermissionHandlerPermissionService());
}

We call that function in our main.dart file:

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

Now to use that service anywhere in your app, you simply have to add the line:

final _permissionService = getIt<PermissionService>();

Why use the service locator pattern?

Can you see what this allows us to do? Imagine if the permission_handler package is no longer sufficient for you and you needed to use another one? All you need to do is create another implementation of your PermissionService abstract class and use another package and call it in your service locator. And this is extremely useful for more major services like the database or http client of your app.

getIt.registerSingleton<PermissionService>
(OtherPackagePermissionService());

Media Service

Let’s proceed by creating our MediaService. For now, I want my media service to allow me to implement uploading an image and compressing it. Later on, according to your app, you can add multiple image upload, video upload, audio upload, and so on. I also need permissions in this service so I will add the created PermissionsService as a dependency. Moreover, my image uploader function needs to know the source of the image in order to determine which permission to ask for, so I will create an AppImageSource Enum for that with the camera and gallery options. Eventually, the uploadImage is the function that we’ll use in our widget.

enum AppImageSource {
camera,
gallery,
}

abstract class MediaServiceInterface {
PermissionService get permissionService;

Future<File?> uploadImage(
BuildContext context,
AppImageSource appImageSource, {
bool shouldCompress = true,
});

Future<File?> compressFile(File file, {int quality = 30});
}

And this is our media_service.dart implementation of the interface

Here’s what we did:

  1. On line 15, we implemented the getter of the getIt permission service.
  2. The local function created on line 17 is used inside our main function uploadImage on line 37 to determine whether or not the process can continue depending on whether the user granted permission or not.
  3. On line 44, we converted our AppImageSource Enum into the value that the package can understand, which in this case is an ImageSource enum with our same values. So I used dart’s new release’s improved Enum syntax to go from one Enum to another.
  4. On line 47, we get the image file using the image_picker package.
  5. On line 53, we compress the image file.

Why use a different Enum?

Now you might ask why I didn’t use the package’s Enum in the first place? Well, the whole point of creating abstractions is that your UI doesn’t have to worry about external dependencies and your abstract service interface won’t depend on any external package, that’s the job of your service implementation only and that’s what makes it easily replaceable. So this way, you don’t have to import the image_picker package in every place you use the uploadImage function in, the abstract service file import is enough. Don’t worry if this is unclear or overwhelming. It will become clearer when we use the service in our UI widget.

Are you still with me? There’s nothing left now but to use the services in the UI, you can do it!

source: https://giphy.com

Implementation of the Services in the UI

Now we can create our UI. I won’t get into detail for the UI code as you can see all of it in the repo. What I did is create an ImagePickerActionSheet widget that allows the user to pick an image source and returns an AppImageSource Enum value if not dismissed, which we can use in our uploadImage after importing the service to our AvatarUploader widget as follows:

You can see the UI code for the AvatarContainer from here.

(See now the benefit of creating our own AppImageSource Enum? If we haven’t done that we would have had to import the image_picker package inside the AvatarUploader & ImagePickerActionSheet & MediaServiceInterface widgets).

Phew! that was a long tutorial, hope you made it to the end or at least found any part useful!

source: https://giphy.com

Summary

Here are the concepts we covered in this tutorial:

  1. Using GetIt to implement the service locator pattern. (Read this classic article to know more)
  2. Abstraction
  3. App permissions
  4. File compression
  5. Some Friends related Gifs 👀

See you in the next one!

https://twitter.com/FlutterComm

--

--

Roaa Khaddam
Flutter Community

Software Engineer | 💙 Flutter / Dart Google Developer Expert