Select Image from Camera/Gallery, Save Image in App’s Directory in separate Thread, Load Image from App’s Directory to UI.

By | June 7, 2020

In this article, we will how to select an image by capturing from camera or gallery, then we will save the image in app’s temporary directory in a separate thread and then load it from there.

Watch Video Tutorial


For this demo we need three packages.

Add Dependencies

Open pubspec.yaml file and then add the below dependencies.

  • path_provider: ^1.6.9
  • image_picker: ^0.6.7
  • image: ^2.1.4

path_provider for getting access to the app’s directory.
image_picker for open camera or gallery and get the image.
image for encoding and decoding the image.

Make the following changes in your Android and iOS project.

iOS

Add the following keys to your Info.plist file, located in /ios/Runner/Info.plist:
NSPhotoLibraryUsageDescription – describe why your app needs permission for the photo library. This is called Privacy – Photo Library Usage Description in the visual editor.
NSCameraUsageDescription – describe why your app needs access to the camera. This is called Privacy – Camera Usage Description in the visual editor.
NSMicrophoneUsageDescription – describe why your app needs access to the microphone, if you intend to record videos. This is called Privacy – Microphone Usage Description in the visual editor.

Android

API 29+
No configuration required - the plugin should work out of the box.
API < 29 Add android:requestLegacyExternalStorage="true" as an attribute to the tag in AndroidManifest.xml. The attribute is false by default on apps targeting Android Q.

So Let’s start.

Below is the method to select an image from gallery or camera.

Future<PickedFile> _loadImage(ImageSource imageSource) async {
    PickedFile file = await _imagePicker.getImage(source: imageSource);
    return file;
}

Call this function like below to open the camera.

_loadImage(ImageSource.camera)

and to select from gallery.

_loadImage(ImageSource.gallery)

Set the image in the Image Widget using below code.

Image.file(
    File(_imageFile.path),
    filterQuality: FilterQuality.high,
)
[/jva]

Save the image in App's Temporary directory.


Future<File> _saveImageToDisk(Map map) async {
  try {
    String path = map['path'];
    Directory directory = map['directory'];
    File tempFile = File(path);
    Img.Image image = Img.decodeImage(tempFile.readAsBytesSync());
    Img.Image mImage = Img.copyResize(image, width: 512);
    String imgType = path.split('.').last;
    String mPath =
        '${directory.path.toString()}/image_${DateTime.now()}.$imgType';
    File dFile = File(mPath);
    if (imgType == 'jpg' || imgType == 'jpeg') {
      dFile.writeAsBytesSync(Img.encodeJpg(mImage));
    } else {
      dFile.writeAsBytesSync(Img.encodePng(mImage));
    }
    return dFile;
  } catch (e) {
    return null;
  }
}

Here we are decoding the file received from the image picker and then writing it to a temporary file in the app’s temporary directory.
Let’s modify the method like this.

Future<File> _loadImage(ImageSource imageSource) async {
    PickedFile file = await _imagePicker.getImage(source: imageSource);
    File mFile;
    if (null != file) {
      Directory directory = await getTemporaryDirectory();
      Map map = Map();
      map['path'] = file.path;
      map['directory'] = directory;
      mFile = await _saveImageToDisk(map);
    }
    return mFile;
}

But there is an issue with this approach, Let’s say if you are doing some animation or a circular progress added while loading this image. The progress animation will get stuck or the animation will break. The reason for this is we are saving the image in the same thread or isolate in which the UI is drawn.


So how can we solve it???

We can solve it by doing the image save in the file in a separate thread. The easiest way to do it is using the ‘compute’ method. The ‘compute’ method creates a separate thread so that the file saving wont affect the Main UI Isolate thread. 


You can learn more about isolates in my previous articles here and here.
So let’s modify the method like below.

Future<File> _loadImage(ImageSource imageSource) async {
    PickedFile file = await _imagePicker.getImage(source: imageSource);
    File mFile;
    if (null != file) {
      Directory directory = await getTemporaryDirectory();
      //mFile = await _saveImageToDisk(file.path, directory);
      Map map = Map();
      map['path'] = file.path;
      map['directory'] = directory;
      mFile = await compute(_saveImageToDisk, map);
    }
    return mFile;
}

Run the app again and you should see a well performing app without any stuttering animations.

Okay thats it.

Source Code

Here is the link to the complete source code.
https://bitbucket.org/vipinvijayan1987/tutorialprojects/src/ImageSelectionAndSaveToDiskPerformance/


Please leave your valuable comments about this article below.

Thanks for reading…


Leave a Reply

Your email address will not be published. Required fields are marked *