Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[image_picker_platform_interface] Introduce new PickedFile APIs. #2791

Merged
merged 7 commits into from
May 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.1.0

* Introduce PickedFile type for the new API.

## 1.0.1

* Update lower bound of dart dependency to 2.1.0.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ class MethodChannelImagePicker extends ImagePickerPlatform {
@visibleForTesting
MethodChannel get channel => _channel;

@override
Future<PickedFile> pickImage({
@required ImageSource source,
double maxWidth,
double maxHeight,
int imageQuality,
CameraDevice preferredCameraDevice = CameraDevice.rear,
}) async {
String path = await pickImagePath(
source: source,
maxWidth: maxWidth,
maxHeight: maxHeight,
imageQuality: imageQuality,
preferredCameraDevice: preferredCameraDevice,
);
return path != null ? PickedFile(path) : null;
}

@override
Future<String> pickImagePath({
@required ImageSource source,
Expand Down Expand Up @@ -53,6 +71,20 @@ class MethodChannelImagePicker extends ImagePickerPlatform {
);
}

@override
Future<PickedFile> pickVideo({
@required ImageSource source,
CameraDevice preferredCameraDevice = CameraDevice.rear,
Duration maxDuration,
}) async {
String path = await pickVideoPath(
source: source,
maxDuration: maxDuration,
preferredCameraDevice: preferredCameraDevice,
);
return path != null ? PickedFile(path) : null;
}

@override
Future<String> pickVideoPath({
@required ImageSource source,
Expand All @@ -71,10 +103,48 @@ class MethodChannelImagePicker extends ImagePickerPlatform {
}

@override
Future<LostData> retrieveLostData() async {
final Map<String, dynamic> result =
await _channel.invokeMapMethod<String, dynamic>('retrieve');

if (result == null) {
return LostData.empty();
}

assert(result.containsKey('path') ^ result.containsKey('errorCode'));

final String type = result['type'];
assert(type == kTypeImage || type == kTypeVideo);

RetrieveType retrieveType;
if (type == kTypeImage) {
retrieveType = RetrieveType.image;
} else if (type == kTypeVideo) {
retrieveType = RetrieveType.video;
}

PlatformException exception;
if (result.containsKey('errorCode')) {
exception = PlatformException(
code: result['errorCode'], message: result['errorMessage']);
}

final String path = result['path'];

return LostData(
file: path != null ? PickedFile(path) : null,
exception: exception,
type: retrieveType,
);
}

@override
// ignore: deprecated_member_use_from_same_package
Future<LostDataResponse> retrieveLostDataAsDartIoFile() async {
final Map<String, dynamic> result =
await _channel.invokeMapMethod<String, dynamic>('retrieve');
if (result == null) {
// ignore: deprecated_member_use_from_same_package
return LostDataResponse.empty();
}
assert(result.containsKey('path') ^ result.containsKey('errorCode'));
Expand All @@ -97,6 +167,7 @@ class MethodChannelImagePicker extends ImagePickerPlatform {

final String path = result['path'];

// ignore: deprecated_member_use_from_same_package
return LostDataResponse(
file: path == null ? null : File(path),
exception: exception,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ abstract class ImagePickerPlatform extends PlatformInterface {
///
/// In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost
/// in this call. You can then call [retrieveLostDataAsDartIoFile] when your app relaunches to retrieve the lost data.
@Deprecated('Use pickImage instead.')
Future<String> pickImagePath({
@required ImageSource source,
double maxWidth,
Expand All @@ -84,6 +85,7 @@ abstract class ImagePickerPlatform extends PlatformInterface {
///
/// In Android, the MainActivity can be destroyed for various fo reasons. If that happens, the result will be lost
/// in this call. You can then call [retrieveLostDataAsDartIoFile] when your app relaunches to retrieve the lost data.
@Deprecated('Use pickVideo instead.')
Future<String> pickVideoPath({
@required ImageSource source,
CameraDevice preferredCameraDevice = CameraDevice.rear,
Expand All @@ -92,7 +94,7 @@ abstract class ImagePickerPlatform extends PlatformInterface {
throw UnimplementedError('pickVideoPath() has not been implemented.');
}

/// Retrieve the lost image file when [pickImage] or [pickVideo] failed because the MainActivity is destroyed. (Android only)
/// Retrieve the lost image file when [pickImagePath] or [pickVideoPath] failed because the MainActivity is destroyed. (Android only)
///
/// Image or video can be lost if the MainActivity is destroyed. And there is no guarantee that the MainActivity is always alive.
/// Call this method to retrieve the lost data and process the data according to your APP's business logic.
Expand All @@ -105,8 +107,81 @@ abstract class ImagePickerPlatform extends PlatformInterface {
/// See also:
/// * [LostDataResponse], for what's included in the response.
/// * [Android Activity Lifecycle](https://developer.android.com/reference/android/app/Activity.html), for more information on MainActivity destruction.
@Deprecated('Use retrieveLostData instead.')
Future<LostDataResponse> retrieveLostDataAsDartIoFile() {
throw UnimplementedError(
'retrieveLostDataAsDartIoFile() has not been implemented.');
}

// Next version of the API.

/// Returns a [PickedFile] with the image that was picked.
///
/// The `source` argument controls where the image comes from. This can
/// be either [ImageSource.camera] or [ImageSource.gallery].
///
/// If specified, the image will be at most `maxWidth` wide and
/// `maxHeight` tall. Otherwise the image will be returned at it's
/// original width and height.
///
/// The `imageQuality` argument modifies the quality of the image, ranging from 0-100
/// where 100 is the original/max quality. If `imageQuality` is null, the image with
/// the original quality will be returned. Compression is only supportted for certain
/// image types such as JPEG. If compression is not supported for the image that is picked,
/// an warning message will be logged.
///
/// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
/// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
/// Defaults to [CameraDevice.rear].
///
/// In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost
/// in this call. You can then call [retrieveLostData] when your app relaunches to retrieve the lost data.
Future<PickedFile> pickImage({
@required ImageSource source,
double maxWidth,
double maxHeight,
int imageQuality,
CameraDevice preferredCameraDevice = CameraDevice.rear,
}) {
throw UnimplementedError('pickImage() has not been implemented.');
}

/// Returns a [PickedFile] containing the video that was picked.
///
/// The [source] argument controls where the video comes from. This can
/// be either [ImageSource.camera] or [ImageSource.gallery].
///
/// The [maxDuration] argument specifies the maximum duration of the captured video. If no [maxDuration] is specified,
/// the maximum duration will be infinite.
///
/// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
/// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
/// Defaults to [CameraDevice.rear].
///
/// In Android, the MainActivity can be destroyed for various fo reasons. If that happens, the result will be lost
/// in this call. You can then call [retrieveLostData] when your app relaunches to retrieve the lost data.
Future<PickedFile> pickVideo({
@required ImageSource source,
CameraDevice preferredCameraDevice = CameraDevice.rear,
Duration maxDuration,
}) {
throw UnimplementedError('pickVideo() has not been implemented.');
}

/// Retrieve the lost [PickedFile] file when [pickImage] or [pickVideo] failed because the MainActivity is destroyed. (Android only)
///
/// Image or video can be lost if the MainActivity is destroyed. And there is no guarantee that the MainActivity is always alive.
/// Call this method to retrieve the lost data and process the data according to your APP's business logic.
///
/// Returns a [LostData] object if successfully retrieved the lost data. The [LostData] object can represent either a
/// successful image/video selection, or a failure.
///
/// Calling this on a non-Android platform will throw [UnimplementedError] exception.
///
/// See also:
/// * [LostData], for what's included in the response.
/// * [Android Activity Lifecycle](https://developer.android.com/reference/android/app/Activity.html), for more information on MainActivity destruction.
Future<LostData> retrieveLostData() {
throw UnimplementedError('retrieveLostData() has not been implemented.');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:image_picker_platform_interface/src/types/types.dart';
/// Only applies to Android.
/// See also:
/// * [ImagePicker.retrieveLostData] for more details on retrieving lost data.
@Deprecated('Use methods that return a LostData object instead.')
class LostDataResponse {
/// Creates an instance with the given [file], [exception], and [type]. Any of
/// the params may be null, but this is never considered to be empty.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:meta/meta.dart';

/// The interface for a PickedFile.
///
/// A PickedFile is a container that wraps the path of a selected
/// file by the user and (in some platforms, like web) the bytes
/// with the contents of the file.
///
/// This class is a very limited subset of dart:io [File], so all
/// the methods should seem familiar.
@immutable
abstract class PickedFileBase {
/// Construct a PickedFile
PickedFileBase(String path);

/// Get the path of the picked file.
///
/// This should only be used as a backwards-compatibility clutch
/// for mobile apps, or cosmetic reasons only (to show the user
/// the path they've picked).
///
/// Accessing the data contained in the picked file by its path
/// is platform-dependant (and won't work on web), so use the
/// byte getters in the PickedFile instance instead.
String get path {
throw UnimplementedError('.path has not been implemented.');
}

/// Synchronously read the entire file contents as a string using the given [Encoding].
///
/// By default, `encoding` is [utf8].
///
/// Throws Exception if the operation fails.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also mention the default encoding

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You got it. I'll add the comment that it defaults to UTF8 and merge after this all goes green again.

Future<String> readAsString({Encoding encoding = utf8}) {
throw UnimplementedError('readAsString() has not been implemented.');
}

/// Synchronously read the entire file contents as a list of bytes.
///
/// Throws Exception if the operation fails.
Future<Uint8List> readAsBytes() {
throw UnimplementedError('readAsBytes() has not been implemented.');
}

/// Create a new independent [Stream] for the contents of this file.
///
/// If `start` is present, the file will be read from byte-offset `start`. Otherwise from the beginning (index 0).
///
/// If `end` is present, only up to byte-index `end` will be read. Otherwise, until end of file.
///
/// In order to make sure that system resources are freed, the stream must be read to completion or the subscription on the stream must be cancelled.
Stream<Uint8List> openRead([int start, int end]) {
throw UnimplementedError('openRead() has not been implemented.');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:http/http.dart' as http show readBytes;

import './base.dart';

/// A PickedFile that works on web.
///
/// It wraps the bytes of a selected file.
class PickedFile extends PickedFileBase {
final String path;
final Uint8List _initBytes;

/// Construct a PickedFile object from its ObjectUrl.
///
/// Optionally, this can be initialized with `bytes`
/// so no http requests are performed to retrieve files later.
PickedFile(this.path, {Uint8List bytes})
: _initBytes = bytes,
super(path);

Future<Uint8List> get _bytes async {
if (_initBytes != null) {
return Future.value(UnmodifiableUint8ListView(_initBytes));
}
return http.readBytes(path);
}

@override
Future<String> readAsString({Encoding encoding = utf8}) async {
return encoding.decode(await _bytes);
}

@override
Future<Uint8List> readAsBytes() async {
return Future.value(await _bytes);
}

@override
Stream<Uint8List> openRead([int start, int end]) async* {
final bytes = await _bytes;
yield bytes.sublist(start ?? 0, end ?? bytes.length);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import './base.dart';

/// A PickedFile backed by a dart:io File.
class PickedFile extends PickedFileBase {
final File _file;

/// Construct a PickedFile object backed by a dart:io File.
PickedFile(String path)
: _file = File(path),
super(path);

@override
String get path {
return _file.path;
}

@override
Future<String> readAsString({Encoding encoding = utf8}) {
return _file.readAsString(encoding: encoding);
}

@override
Future<Uint8List> readAsBytes() {
return _file.readAsBytes();
}

@override
Stream<Uint8List> openRead([int start, int end]) {
return _file
.openRead(start ?? 0, end)
.map((chunk) => Uint8List.fromList(chunk));
}
}
Loading