Saving photo in Flutter (esp. to camera roll)

6,309

Solution 1

You can use gallery_saver from pub.dev/packages/gallery_saver which saves both video and images in gallery/photos both for Android & iOS.

You just need to provide it with a temporary path to a file or url, it saves both local files and those from network.

This is how it's used:

GallerySaver.saveVideo(String path);
GallerySaver.saveImage(String path);

Both functions return true in a case file was successfully saved, and false in any other way.

My team developed this plugin.

Solution 2

Unfortunately I don't think that flutter currently exposes this functionality.

Your best bet is probably to write a plugin or use Platform Channels to perform this. You could use a temporary directory as they do in this example, and then pass the path to android, where you would read the file and insert it into the gallery something like this:

MediaStore.Images.Media.insertImage(
      getContentResolver(), 
      yourBitmap, 
      yourTitle , 
      yourDescription
);`

You might get lucky if you create a feature request in one of the flutter repositories and someone decides that they will help you out by writing the plugin for you.

Solution 3

The error you're getting is because you are trying to save a photo to /data/user/0/com.example.mycameraapp/app_flutter which is a directory, not a file.

You can use the flutter_photokit package to save photos/videos to a user's camera roll/custom album on iOS. You would need to capture the photo to the device's temporary directory or application directory and then from there you can transfer the file to a user's camera roll. Relevant parts of example shown below:

// At the top
import 'package:flutter_photokit/flutter_photokit.dart';

// Function in _CameraAppState
void _captureAndSaveToCameraRoll() async {
  String outputFilePath = '$_appDirectoryPath/test.jpg';
  // Capture the photo to the app directory
  await controller.capture(outputFilePath);
  // Save the photo to the user's camera roll
  FlutterPhotokit.saveToCameraRoll(filePath: outputFilePath);
}

...

// In your build function
floatingActionButton: new FloatingActionButton(
  tooltip: 'Increment',
  child: new Icon(Icons.camera),
  onPressed: _captureAndSaveToCameraRoll,
)

Disclaimer: I am the author of this plugin.

Share:
6,309
Brian Kung
Author by

Brian Kung

Updated on December 04, 2022

Comments

  • Brian Kung
    Brian Kung over 1 year

    I can't figure out how to save to the camera roll (the equivalent of React Native's CameraRoll saveToCameraRoll().

    1. Flutter camera recommends using path_provider to get application directories, but it doesn't seem to have an option to get the camera roll directory path.

    2. I'm getting an exception on CameraController.capture

    The relevant changes (and only the relevant changes, in the form of a diff) are here: https://gist.github.com/briankung/45f9d8438baab59ddcd3b6f3fe811d99

    My whole main.dart is below for easy repro (search QUESTION: to find the relevant portions):

    import 'dart:async';
    import 'dart:io';
    
    import 'package:flutter/material.dart';
    import 'package:camera/camera.dart';
    import 'package:flutter/services.dart';
    import 'package:path_provider/path_provider.dart';
    
    List<CameraDescription> cameras;
    
    Future<Null> main() async {
      SystemChrome.setPreferredOrientations([
        DeviceOrientation.portraitUp,
        DeviceOrientation.portraitDown
      ]);
    
      cameras = await availableCameras();
      runApp(new CameraApp());
    }
    
    class CameraApp extends StatefulWidget {
      @override
      _CameraAppState createState() => new _CameraAppState();
    }
    
    class _CameraAppState extends State<CameraApp> {
      String _appDirectoryPath;
      CameraController controller;
    
      Future<void> _requestAppDirectory() async {
        // QUESTION: `path_provider` doesn't have getCameraRollDirectory()
        Directory _appDirectory = await getApplicationDocumentsDirectory();
    
        setState(() {
          _appDirectoryPath = _appDirectory.path;
        });
      }
    
      @override
      void initState() {
        super.initState();
        _requestAppDirectory();
        controller = new CameraController(cameras[0], ResolutionPreset.medium);
        controller.initialize().then((_) {
          if (!mounted) {
            return;
          }
          setState(() {});
        });
      }
    
      @override
      void dispose() {
        controller?.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        if (!controller.value.initialized) {
          return new Container();
        }
    
        return new MaterialApp(
          title: 'Flutter Demo',
          theme: new ThemeData(
            primarySwatch: Colors.red,
          ),
          home: new Scaffold(
            body: new Center(
              child: new AspectRatio(
                aspectRatio: controller.value.aspectRatio,
                child: new CameraPreview(controller),
              ),
            ),
            floatingActionButton: new FloatingActionButton(
              tooltip: 'Increment',
              child: new Icon(Icons.camera),
              onPressed: () {
                print('capturing');
                print(_appDirectoryPath);
                // QUESTION: this errors out
                controller.capture(_appDirectoryPath);
              },
            ),
            floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
          ),
        );
      }
    }
    

    The log information is as follows:

    I/flutter ( 5471): capturing
    I/flutter ( 5471): /data/user/0/com.example.mycameraapp/app_flutter
    W/LegacyRequestMapper( 5471): convertRequestMetadata - control.awbRegions setting is not supported, ignoring value
    W/LegacyRequestMapper( 5471): Only received metering rectangles with weight 0.
    W/LegacyRequestMapper( 5471): Only received metering rectangles with weight 0.
    I/RequestThread-0( 5471): Received jpeg.
    I/RequestThread-0( 5471): Producing jpeg buffer...
    W/LegacyRequestMapper( 5471): convertRequestMetadata - control.awbRegions setting is not supported, ignoring value
    W/LegacyRequestMapper( 5471): Only received metering rectangles with weight 0.
    W/LegacyRequestMapper( 5471): Only received metering rectangles with weight 0.
    E/flutter ( 5471): [ERROR:topaz/lib/tonic/logging/dart_error.cc(16)] Unhandled exception:
    E/flutter ( 5471): CameraException(IOError, Failed saving image)
    E/flutter ( 5471): #0      CameraController.capture (package:camera/camera.dart:234:7)
    E/flutter ( 5471): <asynchronous suspension>
    E/flutter ( 5471): #1      _CameraAppState.build.<anonymous closure> (file:///Users/briankung/workspace/mobile/flutter/my_camera_app/lib/main.dart:84:24)
    E/flutter ( 5471): #2      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:478:14)
    E/flutter ( 5471): #3      _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:530:30)
    E/flutter ( 5471): #4      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:102:24)
    E/flutter ( 5471): #5      TapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:161:9)
    E/flutter ( 5471): #6      TapGestureRecognizer.acceptGesture (package:flutter/src/gestures/tap.dart:123:7)
    E/flutter ( 5471): #7      GestureArenaManager.sweep (package:flutter/src/gestures/arena.dart:156:27)
    E/flutter ( 5471): #8      _WidgetsFlutterBinding&BindingBase&GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:147:20)
    E/flutter ( 5471): #9      _WidgetsFlutterBinding&BindingBase&GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:121:22)
    E/flutter ( 5471): #10     _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:101:7)
    E/flutter ( 5471): #11     _WidgetsFlutterBinding&BindingBase&GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:64:7)
    E/flutter ( 5471): #12     _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:48:7)
    E/flutter ( 5471): #13     _invoke1 (dart:ui/hooks.dart:134:13)
    E/flutter ( 5471): #14     _dispatchPointerDataPacket (dart:ui/hooks.dart:91:5)
    I/RequestQueue( 5471): Repeating capture request cancelled.
    

    Thanks!

    Forgot version numbers:

    $ flutter --version
    Flutter 0.2.8 • channel beta • https://github.com/flutter/flutter.git
    Framework • revision b397406561 (10 days ago) • 2018-04-02 13:53:20 -0700
    Engine • revision c903c217a1
    Tools • Dart 2.0.0-dev.43.0.flutter-52afcba357
    
    // pubspec.yaml
    
    camera:
      dependency: "direct main"
      description:
        name: camera
        url: "https://pub.dartlang.org"
      source: hosted
      version: "0.1.2"
    path_provider:
      dependency: "direct main"
      description:
        name: path_provider
        url: "https://pub.dartlang.org"
      source: hosted
      version: "0.4.0"
    
  • rmtmckenzie
    rmtmckenzie about 6 years
    Yes - it's failing because you're passing the root directory as the path to write the picture to (/data/user/0/com.example.mycameraapp/app_flutter). You should pass in a file name instead, e.g. /data/user/0/com.example.mycameraapp/app_flutter/savedpictur‌​e1.jpg
  • Aravindh Kumar
    Aravindh Kumar over 5 years
    @rmtmckenzie any idea of implementing in iOS to store the image in camera roll, I got stuck in it for more than a week.
  • rmtmckenzie
    rmtmckenzie over 5 years
    Hi @AravindhKumar - you'll have to use the meothodChannel to send the data in a format that iOS can understand, create a UIImage, and then use UIImageWriteToSavedPhotosAlbum to save it. Creating the UIImage depends on what you're trying to send from flutter. Hope that helps =). But if not, you would probably have better luck asking a new question.
  • Aravindh Kumar
    Aravindh Kumar over 5 years
    I do raised the issue here link seeking for help in achieving it, in my code, I am calling cameracontroller.takePicture(filePath); so i think i don't have an image here like UIImage, or maybe I don't know how to use it. here is my sample camera view source code.
  • Kirollos Morkos
    Kirollos Morkos over 5 years
    @AravindhKumar I just created the flutter_photokit plugin for that purpose! Check it out: pub.dartlang.org/packages/flutter_photokit
  • B--rian
    B--rian over 4 years
    Welcome to SO, and thanks for editing your initial answer. Please do not forget to disclose that you are associated with that project, since on that page it lists some Jelena as author of the package.
  • jesses.co.tt
    jesses.co.tt over 4 years
    your plugin is horribly out of date and not useable anymore! please check you pull requests!
  • Leonardo Rignanese
    Leonardo Rignanese over 4 years
    How can I get the output path then?