title: Saving photo in Flutter (esp. to camera roll)
body: <p>I can't figure out how to save to the camera roll (the equivalent of React Native's <code>CameraRoll</code> <a href="https://facebook.github.io/react-native/docs/cameraroll.html#savetocameraroll" rel="noreferrer"><code>saveToCameraRoll()</code></a>.</p>
  
  <ol>
  <li><p><a href="https://pub.dartlang.org/packages/camera" rel="noreferrer">Flutter camera</a> recommends using <a href="https://pub.dartlang.org/packages/path_provider" rel="noreferrer"><code>path_provider</code></a> to get application directories, but it doesn't seem to have an option to get the camera roll directory path.</p></li>
  <li><p>I'm getting an exception on <code>CameraController.capture</code></p></li>
  </ol>
  
  <p>The relevant changes (and only the relevant changes, in the form of a diff) are here: <a href="https://gist.github.com/briankung/45f9d8438baab59ddcd3b6f3fe811d99" rel="noreferrer">https://gist.github.com/briankung/45f9d8438baab59ddcd3b6f3fe811d99</a></p>
  
  <p>My whole <code>main.dart</code> is below for easy repro (search <code>QUESTION:</code> to find the relevant portions):</p>
  
  <pre><code>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&lt;CameraDescription&gt; cameras;
  
  Future&lt;Null&gt; main() async {
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
      DeviceOrientation.portraitDown
    ]);
  
    cameras = await availableCameras();
    runApp(new CameraApp());
  }
  
  class CameraApp extends StatefulWidget {
    @override
    _CameraAppState createState() =&gt; new _CameraAppState();
  }
  
  class _CameraAppState extends State&lt;CameraApp&gt; {
    String _appDirectoryPath;
    CameraController controller;
  
    Future&lt;void&gt; _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,
        ),
      );
    }
  }
  </code></pre>
  
  <p>The log information is as follows:</p>
  
  <pre><code>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): &lt;asynchronous suspension&gt;
  E/flutter ( 5471): #1      _CameraAppState.build.&lt;anonymous closure&gt; (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.&lt;anonymous closure&gt; (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&amp;BindingBase&amp;GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:147:20)
  E/flutter ( 5471): #9      _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:121:22)
  E/flutter ( 5471): #10     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:101:7)
  E/flutter ( 5471): #11     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:64:7)
  E/flutter ( 5471): #12     _WidgetsFlutterBinding&amp;BindingBase&amp;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.
  </code></pre>
  
  <p>Thanks!</p>
  
  <p>Forgot version numbers:</p>
  
  <pre><code>$ 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"
  </code></pre>