Reputation: 4267
I can't figure out how to save to the camera roll (the equivalent of React Native's CameraRoll
saveToCameraRoll()
.
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.
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"
Upvotes: 5
Views: 7721
Reputation: 508
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.
Upvotes: 8
Reputation: 2711
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.
Upvotes: 3
Reputation: 40433
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.
Upvotes: 3