[image_picker] Port plugin to use the new Platform Interface. (#2797)

This change Deprecates the old plugin API, but keeps it available.

Check the README.md for migration advice to the newest API.
diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md
index 657ffee..a72e3bc 100644
--- a/packages/image_picker/image_picker/CHANGELOG.md
+++ b/packages/image_picker/image_picker/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.6.7
+
+* Utilize the new platform_interface package.
+* **This change marks old methods as `deprecated`. Please check the README for migration instructions to the new API.**
+
 ## 0.6.6+5
 
 * Pin the version of the platform interface to 1.0.0 until the plugin refactor
diff --git a/packages/image_picker/image_picker/README.md b/packages/image_picker/image_picker/README.md
index aacf9cf..1c9503c 100755
--- a/packages/image_picker/image_picker/README.md
+++ b/packages/image_picker/image_picker/README.md
@@ -38,12 +38,13 @@
 
 class _MyHomePageState extends State<MyHomePage> {
   File _image;
+  final picker = ImagePicker();
 
   Future getImage() async {
-    var image = await ImagePicker.pickImage(source: ImageSource.camera);
+    final pickedFile = await picker.getImage(source: ImageSource.camera);
 
     setState(() {
-      _image = image;
+      _image = File(pickedFile.path);
     });
   }
 
@@ -74,8 +75,8 @@
 
 ```dart
 Future<void> retrieveLostData() async {
-  final LostDataResponse response =
-      await ImagePicker.retrieveLostData();
+  final LostData response =
+      await picker.getLostData();
   if (response == null) {
     return;
   }
@@ -94,3 +95,64 @@
 ```
 
 There's no way to detect when this happens, so calling this method at the right place is essential. We recommend to wire this into some kind of start up check. Please refer to the example app to see how we used it.
+
+## Deprecation warnings in `pickImage`, `pickVideo` and `LostDataResponse`
+
+Starting with version **0.6.7** of the image_picker plugin, the API of the plugin changed slightly to allow for web implementations to exist.
+
+The **old methods that returned `dart:io` File objects were marked as deprecated**, and a new set of methods that return [`PickedFile` objects](https://pub.dev/documentation/image_picker_platform_interface/latest/image_picker_platform_interface/PickedFile-class.html) were introduced.
+
+### How to migrate from to ^0.6.7
+
+#### Instantiate the `ImagePicker`
+
+The new ImagePicker API does not rely in static methods anymore, so the first thing you'll need to do is to create a new instance of the plugin where you need it:
+
+```dart
+final _picker = ImagePicker();
+```
+
+#### Call the new methods
+
+The new methods **receive the same parameters as before**, but they **return a `PickedFile`, instead of a `File`**. The `LostDataResponse` class has been replaced by the [`LostData` class](https://pub.dev/documentation/image_picker_platform_interface/latest/image_picker_platform_interface/LostData-class.html).
+
+| Old API | New API |
+|---------|---------|
+| `File image = await ImagePicker.pickImage(...)` | `PickedFile image = await _picker.getImage(...)` |
+| `File video = await ImagePicker.pickVideo(...)` | `PickedFile video = await _picker.getVideo(...)` |
+| `LostDataResponse response = await ImagePicker.retrieveLostData()` | `LostData response = await _picker.getLostData()` |
+
+#### `PickedFile` to `File`
+
+If your app needs dart:io `File` objects to operate, you may transform `PickedFile` to `File` like so:
+
+```dart
+final pickedFile = await _picker.getImage(...);
+final File file = File(pickedFile.path);
+```
+
+You may also retrieve the bytes from the pickedFile directly if needed:
+
+```dart
+final bytes = await pickedFile.readAsBytes();
+```
+
+#### Getting ready for the web platform
+
+Note that on the web platform (`kIsWeb == true`), `File` is not available, so the `path` of the `PickedFile` will point to a network resource instead:
+
+```dart
+if (kIsWeb) {
+  image = Image.network(pickedFile.path);
+} else {
+  image = Image.file(File(pickedFile.path));
+}
+```
+
+Alternatively, the code may be unified at the expense of memory utilization:
+
+```dart
+image = Image.memory(await pickedFile.readAsBytes())
+```
+
+Take a look at the changes to the `example` app introduced in version 0.6.7 to see the migration steps applied there.
diff --git a/packages/image_picker/image_picker/example/lib/main.dart b/packages/image_picker/image_picker/example/lib/main.dart
index 29781f7..231dcb2 100755
--- a/packages/image_picker/image_picker/example/lib/main.dart
+++ b/packages/image_picker/image_picker/example/lib/main.dart
@@ -7,6 +7,7 @@
 import 'dart:async';
 import 'dart:io';
 
+import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/src/widgets/basic.dart';
 import 'package:flutter/src/widgets/container.dart';
@@ -37,20 +38,21 @@
 }
 
 class _MyHomePageState extends State<MyHomePage> {
-  File _imageFile;
+  PickedFile _imageFile;
   dynamic _pickImageError;
   bool isVideo = false;
   VideoPlayerController _controller;
   String _retrieveDataError;
 
+  final ImagePicker _picker = ImagePicker();
   final TextEditingController maxWidthController = TextEditingController();
   final TextEditingController maxHeightController = TextEditingController();
   final TextEditingController qualityController = TextEditingController();
 
-  Future<void> _playVideo(File file) async {
+  Future<void> _playVideo(PickedFile file) async {
     if (file != null && mounted) {
       await _disposeVideoController();
-      _controller = VideoPlayerController.file(file);
+      _controller = VideoPlayerController.file(File(file.path));
       await _controller.setVolume(1.0);
       await _controller.initialize();
       await _controller.setLooping(true);
@@ -64,21 +66,26 @@
       await _controller.setVolume(0.0);
     }
     if (isVideo) {
-      final File file = await ImagePicker.pickVideo(
+      final PickedFile file = await _picker.getVideo(
           source: source, maxDuration: const Duration(seconds: 10));
       await _playVideo(file);
     } else {
       await _displayPickImageDialog(context,
           (double maxWidth, double maxHeight, int quality) async {
         try {
-          _imageFile = await ImagePicker.pickImage(
-              source: source,
-              maxWidth: maxWidth,
-              maxHeight: maxHeight,
-              imageQuality: quality);
-          setState(() {});
+          final pickedFile = await _picker.getImage(
+            source: source,
+            maxWidth: maxWidth,
+            maxHeight: maxHeight,
+            imageQuality: quality,
+          );
+          setState(() {
+            _imageFile = pickedFile;
+          });
         } catch (e) {
-          _pickImageError = e;
+          setState(() {
+            _pickImageError = e;
+          });
         }
       });
     }
@@ -132,7 +139,7 @@
       return retrieveError;
     }
     if (_imageFile != null) {
-      return Image.file(_imageFile);
+      return Image.file(File(_imageFile.path));
     } else if (_pickImageError != null) {
       return Text(
         'Pick image error: $_pickImageError',
@@ -147,7 +154,7 @@
   }
 
   Future<void> retrieveLostData() async {
-    final LostDataResponse response = await ImagePicker.retrieveLostData();
+    final LostData response = await _picker.getLostData();
     if (response.isEmpty) {
       return;
     }
@@ -173,7 +180,7 @@
         title: Text(widget.title),
       ),
       body: Center(
-        child: Platform.isAndroid
+        child: defaultTargetPlatform == TargetPlatform.android
             ? FutureBuilder<void>(
                 future: retrieveLostData(),
                 builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
diff --git a/packages/image_picker/image_picker/example/pubspec.yaml b/packages/image_picker/image_picker/example/pubspec.yaml
index d089161..f3171aa 100755
--- a/packages/image_picker/image_picker/example/pubspec.yaml
+++ b/packages/image_picker/image_picker/example/pubspec.yaml
@@ -6,9 +6,9 @@
   video_player: ^0.10.3
   flutter:
     sdk: flutter
+  flutter_plugin_android_lifecycle: ^1.0.2
   image_picker:
     path: ../
-  flutter_plugin_android_lifecycle: ^1.0.2
 
 dev_dependencies:
   flutter_driver:
diff --git a/packages/image_picker/image_picker/lib/image_picker.dart b/packages/image_picker/image_picker/lib/image_picker.dart
index 0dd9cac..7dd7e8e 100755
--- a/packages/image_picker/image_picker/lib/image_picker.dart
+++ b/packages/image_picker/image_picker/lib/image_picker.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package
+
 import 'dart:async';
 import 'dart:io';
 
@@ -15,7 +17,9 @@
         kTypeVideo,
         ImageSource,
         CameraDevice,
+        LostData,
         LostDataResponse,
+        PickedFile,
         RetrieveType;
 
 /// Provides an easy way to pick an image/video from the image library,
@@ -47,6 +51,7 @@
   ///
   /// 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.
+  @Deprecated('Use imagePicker.getImage() method instead.')
   static Future<File> pickImage(
       {@required ImageSource source,
       double maxWidth,
@@ -64,6 +69,44 @@
     return path == null ? null : File(path);
   }
 
+  /// Returns a [PickedFile] object wrapping the image that was picked.
+  ///
+  /// The returned [PickedFile] is intended to be used within a single APP session. Do not save the file path and use it across sessions.
+  ///
+  /// 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 [getLostData] when your app relaunches to retrieve the lost data.
+  Future<PickedFile> getImage({
+    @required ImageSource source,
+    double maxWidth,
+    double maxHeight,
+    int imageQuality,
+    CameraDevice preferredCameraDevice = CameraDevice.rear,
+  }) {
+    return platform.pickImage(
+      source: source,
+      maxWidth: maxWidth,
+      maxHeight: maxHeight,
+      imageQuality: imageQuality,
+      preferredCameraDevice: preferredCameraDevice,
+    );
+  }
+
   /// Returns a [File] object pointing to the video that was picked.
   ///
   /// The returned [File] is intended to be used within a single APP session. Do not save the file path and use it across sessions.
@@ -80,6 +123,7 @@
   ///
   /// 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.
+  @Deprecated('Use imagePicker.getVideo() method instead.')
   static Future<File> pickVideo(
       {@required ImageSource source,
       CameraDevice preferredCameraDevice = CameraDevice.rear,
@@ -93,6 +137,34 @@
     return path == null ? null : File(path);
   }
 
+  /// Returns a [PickedFile] object wrapping the video that was picked.
+  ///
+  /// The returned [PickedFile] is intended to be used within a single APP session. Do not save the file path and use it across sessions.
+  ///
+  /// 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 [getLostData] when your app relaunches to retrieve the lost data.
+  Future<PickedFile> getVideo({
+    @required ImageSource source,
+    CameraDevice preferredCameraDevice = CameraDevice.rear,
+    Duration maxDuration,
+  }) {
+    return platform.pickVideo(
+      source: source,
+      preferredCameraDevice: preferredCameraDevice,
+      maxDuration: maxDuration,
+    );
+  }
+
   /// Retrieve the lost image 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.
@@ -109,4 +181,21 @@
   static Future<LostDataResponse> retrieveLostData() {
     return platform.retrieveLostDataAsDartIoFile();
   }
+
+  /// Retrieve the lost [PickedFile] when [selectImage] or [selectVideo] 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> getLostData() {
+    return platform.retrieveLostData();
+  }
 }
diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml
index 173e8a8..51069b6 100755
--- a/packages/image_picker/image_picker/pubspec.yaml
+++ b/packages/image_picker/image_picker/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Flutter plugin for selecting images from the Android and iOS image
   library, and taking new pictures with the camera.
 homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker
-version: 0.6.6+5
+version: 0.6.7
 
 flutter:
   plugin:
@@ -17,7 +17,7 @@
   flutter:
     sdk: flutter
   flutter_plugin_android_lifecycle: ^1.0.2
-  image_picker_platform_interface: ">=1.0.0 <1.1.0"
+  image_picker_platform_interface: ^1.1.0
 
 dev_dependencies:
   video_player: ^0.10.3
diff --git a/packages/image_picker/image_picker/test/image_picker_test.dart b/packages/image_picker/image_picker/test/image_picker_test.dart
index 8db71ad..0ada1b2 100644
--- a/packages/image_picker/image_picker/test/image_picker_test.dart
+++ b/packages/image_picker/image_picker/test/image_picker_test.dart
@@ -15,6 +15,8 @@
 
     final List<MethodCall> log = <MethodCall>[];
 
+    final picker = ImagePicker();
+
     setUp(() {
       channel.setMockMethodCallHandler((MethodCall methodCall) async {
         log.add(methodCall);
@@ -26,8 +28,8 @@
 
     group('#pickImage', () {
       test('passes the image source argument correctly', () async {
-        await ImagePicker.pickImage(source: ImageSource.camera);
-        await ImagePicker.pickImage(source: ImageSource.gallery);
+        await picker.getImage(source: ImageSource.camera);
+        await picker.getImage(source: ImageSource.gallery);
 
         expect(
           log,
@@ -51,25 +53,25 @@
       });
 
       test('passes the width and height arguments correctly', () async {
-        await ImagePicker.pickImage(source: ImageSource.camera);
-        await ImagePicker.pickImage(
+        await picker.getImage(source: ImageSource.camera);
+        await picker.getImage(
           source: ImageSource.camera,
           maxWidth: 10.0,
         );
-        await ImagePicker.pickImage(
+        await picker.getImage(
           source: ImageSource.camera,
           maxHeight: 10.0,
         );
-        await ImagePicker.pickImage(
+        await picker.getImage(
           source: ImageSource.camera,
           maxWidth: 10.0,
           maxHeight: 20.0,
         );
-        await ImagePicker.pickImage(
+        await picker.getImage(
             source: ImageSource.camera, maxWidth: 10.0, imageQuality: 70);
-        await ImagePicker.pickImage(
+        await picker.getImage(
             source: ImageSource.camera, maxHeight: 10.0, imageQuality: 70);
-        await ImagePicker.pickImage(
+        await picker.getImage(
             source: ImageSource.camera,
             maxWidth: 10.0,
             maxHeight: 20.0,
@@ -133,12 +135,12 @@
 
       test('does not accept a negative width or height argument', () {
         expect(
-          ImagePicker.pickImage(source: ImageSource.camera, maxWidth: -1.0),
+          picker.getImage(source: ImageSource.camera, maxWidth: -1.0),
           throwsArgumentError,
         );
 
         expect(
-          ImagePicker.pickImage(source: ImageSource.camera, maxHeight: -1.0),
+          picker.getImage(source: ImageSource.camera, maxHeight: -1.0),
           throwsArgumentError,
         );
       });
@@ -146,13 +148,12 @@
       test('handles a null image path response gracefully', () async {
         channel.setMockMethodCallHandler((MethodCall methodCall) => null);
 
-        expect(
-            await ImagePicker.pickImage(source: ImageSource.gallery), isNull);
-        expect(await ImagePicker.pickImage(source: ImageSource.camera), isNull);
+        expect(await picker.getImage(source: ImageSource.gallery), isNull);
+        expect(await picker.getImage(source: ImageSource.camera), isNull);
       });
 
       test('camera position defaults to back', () async {
-        await ImagePicker.pickImage(source: ImageSource.camera);
+        await picker.getImage(source: ImageSource.camera);
 
         expect(
           log,
@@ -169,7 +170,7 @@
       });
 
       test('camera position can set to front', () async {
-        await ImagePicker.pickImage(
+        await picker.getImage(
             source: ImageSource.camera,
             preferredCameraDevice: CameraDevice.front);
 
@@ -190,8 +191,8 @@
 
     group('#pickVideo', () {
       test('passes the image source argument correctly', () async {
-        await ImagePicker.pickVideo(source: ImageSource.camera);
-        await ImagePicker.pickVideo(source: ImageSource.gallery);
+        await picker.getVideo(source: ImageSource.camera);
+        await picker.getVideo(source: ImageSource.gallery);
 
         expect(
           log,
@@ -211,14 +212,14 @@
       });
 
       test('passes the duration argument correctly', () async {
-        await ImagePicker.pickVideo(source: ImageSource.camera);
-        await ImagePicker.pickVideo(
+        await picker.getVideo(source: ImageSource.camera);
+        await picker.getVideo(
             source: ImageSource.camera,
             maxDuration: const Duration(seconds: 10));
-        await ImagePicker.pickVideo(
+        await picker.getVideo(
             source: ImageSource.camera,
             maxDuration: const Duration(minutes: 1));
-        await ImagePicker.pickVideo(
+        await picker.getVideo(
             source: ImageSource.camera, maxDuration: const Duration(hours: 1));
         expect(
           log,
@@ -250,13 +251,12 @@
       test('handles a null video path response gracefully', () async {
         channel.setMockMethodCallHandler((MethodCall methodCall) => null);
 
-        expect(
-            await ImagePicker.pickVideo(source: ImageSource.gallery), isNull);
-        expect(await ImagePicker.pickVideo(source: ImageSource.camera), isNull);
+        expect(await picker.getVideo(source: ImageSource.gallery), isNull);
+        expect(await picker.getVideo(source: ImageSource.camera), isNull);
       });
 
       test('camera position defaults to back', () async {
-        await ImagePicker.pickVideo(source: ImageSource.camera);
+        await picker.getVideo(source: ImageSource.camera);
 
         expect(
           log,
@@ -271,7 +271,7 @@
       });
 
       test('camera position can set to front', () async {
-        await ImagePicker.pickVideo(
+        await picker.getVideo(
             source: ImageSource.camera,
             preferredCameraDevice: CameraDevice.front);
 
@@ -296,7 +296,7 @@
             'path': '/example/path',
           };
         });
-        final LostDataResponse response = await ImagePicker.retrieveLostData();
+        final LostData response = await picker.getLostData();
         expect(response.type, RetrieveType.image);
         expect(response.file.path, '/example/path');
       });
@@ -309,7 +309,7 @@
             'errorMessage': 'test_error_message',
           };
         });
-        final LostDataResponse response = await ImagePicker.retrieveLostData();
+        final LostData response = await picker.getLostData();
         expect(response.type, RetrieveType.video);
         expect(response.exception.code, 'test_error_code');
         expect(response.exception.message, 'test_error_message');
@@ -319,7 +319,7 @@
         channel.setMockMethodCallHandler((MethodCall methodCall) async {
           return null;
         });
-        expect((await ImagePicker.retrieveLostData()).isEmpty, true);
+        expect((await picker.getLostData()).isEmpty, true);
       });
 
       test('retrieveLostData get both path and error should throw', () async {
@@ -331,7 +331,7 @@
             'path': '/example/path',
           };
         });
-        expect(ImagePicker.retrieveLostData(), throwsAssertionError);
+        expect(picker.getLostData(), throwsAssertionError);
       });
     });
   });
diff --git a/packages/image_picker/image_picker/test/image_picker_e2e.dart b/packages/image_picker/image_picker/test/old_image_picker_e2e.dart
similarity index 100%
rename from packages/image_picker/image_picker/test/image_picker_e2e.dart
rename to packages/image_picker/image_picker/test/old_image_picker_e2e.dart
diff --git a/packages/image_picker/image_picker/test/old_image_picker_test.dart b/packages/image_picker/image_picker/test/old_image_picker_test.dart
new file mode 100644
index 0000000..8d4e068
--- /dev/null
+++ b/packages/image_picker/image_picker/test/old_image_picker_test.dart
@@ -0,0 +1,340 @@
+// Copyright 2019 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:image_picker/image_picker.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  group('$ImagePicker', () {
+    const MethodChannel channel =
+        MethodChannel('plugins.flutter.io/image_picker');
+
+    final List<MethodCall> log = <MethodCall>[];
+
+    setUp(() {
+      channel.setMockMethodCallHandler((MethodCall methodCall) async {
+        log.add(methodCall);
+        return '';
+      });
+
+      log.clear();
+    });
+
+    group('#pickImage', () {
+      test('passes the image source argument correctly', () async {
+        await ImagePicker.pickImage(source: ImageSource.camera);
+        await ImagePicker.pickImage(source: ImageSource.gallery);
+
+        expect(
+          log,
+          <Matcher>[
+            isMethodCall('pickImage', arguments: <String, dynamic>{
+              'source': 0,
+              'maxWidth': null,
+              'maxHeight': null,
+              'imageQuality': null,
+              'cameraDevice': 0
+            }),
+            isMethodCall('pickImage', arguments: <String, dynamic>{
+              'source': 1,
+              'maxWidth': null,
+              'maxHeight': null,
+              'imageQuality': null,
+              'cameraDevice': 0
+            }),
+          ],
+        );
+      });
+
+      test('passes the width and height arguments correctly', () async {
+        await ImagePicker.pickImage(source: ImageSource.camera);
+        await ImagePicker.pickImage(
+          source: ImageSource.camera,
+          maxWidth: 10.0,
+        );
+        await ImagePicker.pickImage(
+          source: ImageSource.camera,
+          maxHeight: 10.0,
+        );
+        await ImagePicker.pickImage(
+          source: ImageSource.camera,
+          maxWidth: 10.0,
+          maxHeight: 20.0,
+        );
+        await ImagePicker.pickImage(
+            source: ImageSource.camera, maxWidth: 10.0, imageQuality: 70);
+        await ImagePicker.pickImage(
+            source: ImageSource.camera, maxHeight: 10.0, imageQuality: 70);
+        await ImagePicker.pickImage(
+            source: ImageSource.camera,
+            maxWidth: 10.0,
+            maxHeight: 20.0,
+            imageQuality: 70);
+
+        expect(
+          log,
+          <Matcher>[
+            isMethodCall('pickImage', arguments: <String, dynamic>{
+              'source': 0,
+              'maxWidth': null,
+              'maxHeight': null,
+              'imageQuality': null,
+              'cameraDevice': 0
+            }),
+            isMethodCall('pickImage', arguments: <String, dynamic>{
+              'source': 0,
+              'maxWidth': 10.0,
+              'maxHeight': null,
+              'imageQuality': null,
+              'cameraDevice': 0
+            }),
+            isMethodCall('pickImage', arguments: <String, dynamic>{
+              'source': 0,
+              'maxWidth': null,
+              'maxHeight': 10.0,
+              'imageQuality': null,
+              'cameraDevice': 0
+            }),
+            isMethodCall('pickImage', arguments: <String, dynamic>{
+              'source': 0,
+              'maxWidth': 10.0,
+              'maxHeight': 20.0,
+              'imageQuality': null,
+              'cameraDevice': 0
+            }),
+            isMethodCall('pickImage', arguments: <String, dynamic>{
+              'source': 0,
+              'maxWidth': 10.0,
+              'maxHeight': null,
+              'imageQuality': 70,
+              'cameraDevice': 0
+            }),
+            isMethodCall('pickImage', arguments: <String, dynamic>{
+              'source': 0,
+              'maxWidth': null,
+              'maxHeight': 10.0,
+              'imageQuality': 70,
+              'cameraDevice': 0
+            }),
+            isMethodCall('pickImage', arguments: <String, dynamic>{
+              'source': 0,
+              'maxWidth': 10.0,
+              'maxHeight': 20.0,
+              'imageQuality': 70,
+              'cameraDevice': 0
+            }),
+          ],
+        );
+      });
+
+      test('does not accept a negative width or height argument', () {
+        expect(
+          ImagePicker.pickImage(source: ImageSource.camera, maxWidth: -1.0),
+          throwsArgumentError,
+        );
+
+        expect(
+          ImagePicker.pickImage(source: ImageSource.camera, maxHeight: -1.0),
+          throwsArgumentError,
+        );
+      });
+
+      test('handles a null image path response gracefully', () async {
+        channel.setMockMethodCallHandler((MethodCall methodCall) => null);
+
+        expect(
+            await ImagePicker.pickImage(source: ImageSource.gallery), isNull);
+        expect(await ImagePicker.pickImage(source: ImageSource.camera), isNull);
+      });
+
+      test('camera position defaults to back', () async {
+        await ImagePicker.pickImage(source: ImageSource.camera);
+
+        expect(
+          log,
+          <Matcher>[
+            isMethodCall('pickImage', arguments: <String, dynamic>{
+              'source': 0,
+              'maxWidth': null,
+              'maxHeight': null,
+              'imageQuality': null,
+              'cameraDevice': 0,
+            }),
+          ],
+        );
+      });
+
+      test('camera position can set to front', () async {
+        await ImagePicker.pickImage(
+            source: ImageSource.camera,
+            preferredCameraDevice: CameraDevice.front);
+
+        expect(
+          log,
+          <Matcher>[
+            isMethodCall('pickImage', arguments: <String, dynamic>{
+              'source': 0,
+              'maxWidth': null,
+              'maxHeight': null,
+              'imageQuality': null,
+              'cameraDevice': 1,
+            }),
+          ],
+        );
+      });
+    });
+
+    group('#pickVideo', () {
+      test('passes the image source argument correctly', () async {
+        await ImagePicker.pickVideo(source: ImageSource.camera);
+        await ImagePicker.pickVideo(source: ImageSource.gallery);
+
+        expect(
+          log,
+          <Matcher>[
+            isMethodCall('pickVideo', arguments: <String, dynamic>{
+              'source': 0,
+              'cameraDevice': 0,
+              'maxDuration': null,
+            }),
+            isMethodCall('pickVideo', arguments: <String, dynamic>{
+              'source': 1,
+              'cameraDevice': 0,
+              'maxDuration': null,
+            }),
+          ],
+        );
+      });
+
+      test('passes the duration argument correctly', () async {
+        await ImagePicker.pickVideo(source: ImageSource.camera);
+        await ImagePicker.pickVideo(
+            source: ImageSource.camera,
+            maxDuration: const Duration(seconds: 10));
+        await ImagePicker.pickVideo(
+            source: ImageSource.camera,
+            maxDuration: const Duration(minutes: 1));
+        await ImagePicker.pickVideo(
+            source: ImageSource.camera, maxDuration: const Duration(hours: 1));
+        expect(
+          log,
+          <Matcher>[
+            isMethodCall('pickVideo', arguments: <String, dynamic>{
+              'source': 0,
+              'maxDuration': null,
+              'cameraDevice': 0,
+            }),
+            isMethodCall('pickVideo', arguments: <String, dynamic>{
+              'source': 0,
+              'maxDuration': 10,
+              'cameraDevice': 0,
+            }),
+            isMethodCall('pickVideo', arguments: <String, dynamic>{
+              'source': 0,
+              'maxDuration': 60,
+              'cameraDevice': 0,
+            }),
+            isMethodCall('pickVideo', arguments: <String, dynamic>{
+              'source': 0,
+              'maxDuration': 3600,
+              'cameraDevice': 0,
+            }),
+          ],
+        );
+      });
+
+      test('handles a null video path response gracefully', () async {
+        channel.setMockMethodCallHandler((MethodCall methodCall) => null);
+
+        expect(
+            await ImagePicker.pickVideo(source: ImageSource.gallery), isNull);
+        expect(await ImagePicker.pickVideo(source: ImageSource.camera), isNull);
+      });
+
+      test('camera position defaults to back', () async {
+        await ImagePicker.pickVideo(source: ImageSource.camera);
+
+        expect(
+          log,
+          <Matcher>[
+            isMethodCall('pickVideo', arguments: <String, dynamic>{
+              'source': 0,
+              'cameraDevice': 0,
+              'maxDuration': null,
+            }),
+          ],
+        );
+      });
+
+      test('camera position can set to front', () async {
+        await ImagePicker.pickVideo(
+            source: ImageSource.camera,
+            preferredCameraDevice: CameraDevice.front);
+
+        expect(
+          log,
+          <Matcher>[
+            isMethodCall('pickVideo', arguments: <String, dynamic>{
+              'source': 0,
+              'maxDuration': null,
+              'cameraDevice': 1,
+            }),
+          ],
+        );
+      });
+    });
+
+    group('#retrieveLostData', () {
+      test('retrieveLostData get success response', () async {
+        channel.setMockMethodCallHandler((MethodCall methodCall) async {
+          return <String, String>{
+            'type': 'image',
+            'path': '/example/path',
+          };
+        });
+        final LostDataResponse response = await ImagePicker.retrieveLostData();
+        expect(response.type, RetrieveType.image);
+        expect(response.file.path, '/example/path');
+      });
+
+      test('retrieveLostData get error response', () async {
+        channel.setMockMethodCallHandler((MethodCall methodCall) async {
+          return <String, String>{
+            'type': 'video',
+            'errorCode': 'test_error_code',
+            'errorMessage': 'test_error_message',
+          };
+        });
+        final LostDataResponse response = await ImagePicker.retrieveLostData();
+        expect(response.type, RetrieveType.video);
+        expect(response.exception.code, 'test_error_code');
+        expect(response.exception.message, 'test_error_message');
+      });
+
+      test('retrieveLostData get null response', () async {
+        channel.setMockMethodCallHandler((MethodCall methodCall) async {
+          return null;
+        });
+        expect((await ImagePicker.retrieveLostData()).isEmpty, true);
+      });
+
+      test('retrieveLostData get both path and error should throw', () async {
+        channel.setMockMethodCallHandler((MethodCall methodCall) async {
+          return <String, String>{
+            'type': 'video',
+            'errorCode': 'test_error_code',
+            'errorMessage': 'test_error_message',
+            'path': '/example/path',
+          };
+        });
+        expect(ImagePicker.retrieveLostData(), throwsAssertionError);
+      });
+    });
+  });
+}