A macOS platform implementation of image_picker
using the native system picker instead of the system open file dialog.
This package is an alternative to image_picker_macos
which uses file_selector
.
Note
This native picker depends on the photos in the Photos for MacOS App, which uses the Apple PhotosUI Picker, also known as PHPicker.
Default picker | Native picker |
---|---|
- 🚀 Seamless Integration
Effortlessly integrates with theimage_picker
package. Switch seamlessly betweenimage_picker_macos
and this native platform implementation without modifying existing code. - 🔒 No Permissions or Setup Required
Requires no runtime permission prompts or entitlement configuration. Everything works out of the box. - 📱 macOS Photos App
Enables picking images from the macOS Photos app, integrating with the Apple ecosystem and supporting photo imports from connected iOS devices. - 🛠️ Supports Image Options
Adds support for image arguments likemaxWidth
,maxHeight
, andimageQuality
—features not currently supported inimage_picker_macos
.
Run the following command to add the dependencies:
$ flutter pub add image_picker image_picker_macos native_image_picker_macos
image_picker
: The app-facing package for the Image Picker plugin which specifies the API used by Flutter apps.image_picker_macos
: The default macOS implementation ofimage_picker
, built onfile_selector
and usingNSOpenPanel
with appropriate file type filters set. Lacks image resizing/compression options but supports older macOS versions.native_image_picker_macos
: A macOS implementation ofimage_picker
, built onPHPickerViewController
which depends on the Photos for macOS App. Supports image resizing/compression options. Requires macOS 13.0+.
Using both image_picker_macos
and native_image_picker_macos
can enable user-level opt-in to switch between implementations if the user prefers to pick images from the photos app or the file system.
The platform implementation image_picker_macos
is required to ensure compatibility with macOS versions before 13.0, which is used as a fallback, in that case, it's necessary to setup image_picker_macos
.
Tip
After registering this implementation as outlined in the Usage section, you can use the image_picker
plugin as usual.
By default, this package doesn't replace the default implementation of image_picker
for macOS to avoid conflict with image_picker_macos
.
This implementation supports macOS 13 Ventura and later.
To apply this package only in case it's supported on the current macOS version:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await NativeImagePickerMacOS.registerWithIfSupported(); // ADD THIS LINE
runApp(const MainApp());
}
To checks if the current implementation of image_picker
is native_image_picker_macos
:
final bool isRegistered = NativeImagePickerMacOS.isRegistered();
// OR
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
final bool isRegistered = ImagePickerPlatform.instance is NativeImagePickerMacOS;
To checks if the current macOS version supports this implementation:
final bool isSupported = await NativeImagePickerMacOS.isSupported(); // Returns false on non-macOS platforms or if PHPicker is not supported on the current macOS version.
To switch between image_picker_macos
and native_image_picker_macos
implementations:
// NOTE: This code assumes the current target platform is macOS and native_image_picker_macos implementation is supported.
import 'package:image_picker_macos/image_picker_macos.dart';
import 'package:native_image_picker_macos/native_image_picker_macos.dart';
// To switch to image_picker_macos (supported on all macOS versions):
ImagePickerMacOS.registerWith();
// To switch to native_image_picker_macos (supported on macOS 13 and above):
NativeImagePickerMacOS.registerWith();
To open the macOS photos app:
await NativeImagePickerMacOS.instanceOrNull?.openPhotosApp();
// OR
final ImagePickerPlatform imagePickerImplementation = ImagePickerPlatform.instance;
if (imagePickerImplementation is NativeImagePickerMacOS) {
await imagePickerImplementation.openPhotosApp();
}
Tip
You can use NativeImagePickerMacOS.registerWith()
to register this implementation. However, this bypasses platform checks, which may result in runtime errors if the current platform is not macOS or if the macOS version is unsupported. Instead, use registerWithIfSupported()
if uncertain.
Refer to the example main.dart for a full usage example.
This package uses pigeon for platform communication with the platform host and mockito for mocking in unit tests and swift-format for formatting the Swift code.
$ dart run pigeon --input pigeons/messages.dart # To generate the required Dart and host-language code.
$ dart run build_runner build --delete-conflicting-outputs # To generate the mock classes.
$ swift-format format --in-place --recursive macos/native_image_picker_macos/Sources/native_image_picker_macos example/macos/Runner example/macos/RunnerTests example/macos/RunnerUITests # To format the Swift code.
$ dart format . # To format the Dart code.
$ (cd example/macos && xcodebuild test -workspace Runner.xcworkspace -scheme Runner -configuration Debug -quiet) # To run the native macOS unit tests.
$ flutter test # To run the Flutter unit tests.
- Flutter repo style guide.
- Flutter repo plugin tests.
- Flutter repo writing effective tests.
- Flutter developing plugin packages.
- Flutter testing plugins, Flutter plugins in Flutter tests and Flutter mock dependencies using Mockito.
- Pigeon example README.
- Effective Dart.
- PHPickerViewController.
Contributions are welcome. File issues to the GitHub repo.
- Similarly to
image_picker_macos
,ImageSource.camera
is not supported unless acameraDelegate
is set. - Similarly to
image_picker_macos
, themaxDuration
argument inpickVideo
is unsupported and will be silently ignored.
This functionality was originally proposed as a pull request to image_picker_macos
, but it was later decided to split it into a community package which is unendorsed.
Ask a question about using the package.
Tip
With this approach, you can effectively test this platform implementation with the existing packages that use image_picker
APIs. All platform-specific calls to NativeImagePickerMacOS
should use the instance from ImagePickerPlatform.instance
instead of creating a new NativeImagePickerMacOS
to work.
To override the methods implementation for unit testing, add the dev dependencies:
mockito
(ormocktail
): for mocking the instance methods ofNativeImagePickerMacOS
.image_picker_platform_interface
: for overriding the instance ofImagePickerPlatform
with the mock instance.build_runner
: for creating the generated Dart files.plugin_platform_interface
: SinceImagePickerPlatform
extendsPlatformInterface
, it's required to apply the mixinMockPlatformInterfaceMixin
to the mock ofNativeImagePickerMacOS
to ignore an assertation failure that enforces the usage ofextends
instead ofimplements
, since mock classes need to extendMock
and implement the real class.
$ flutter pub add dev:mockito dev:image_picker_platform_interface dev:build_runner dev:plugin_platform_interface # Add them as dev-dependencies
In your test file, add this annotation somewhere:
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:native_image_picker_macos/native_image_picker_macos.dart';
@GenerateNiceMocks([MockSpec<NativeImagePickerMacOS>()])
Generate the MockNativeImagePickerMacOS
by running:
$ dart run build_runner build --delete-conflicting-outputs
Create a new instance of MockNativeImagePickerMacOS
and override the instance of ImagePickerPlatform
to every test:
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late MockNativeImagePickerMacOS mockNativeImagePickerMacOS;
setUp(() {
mockNativeImagePickerMacOS = MockNativeImagePickerMacOS();
ImagePickerPlatform.instance = mockNativeImagePickerMacOS;
});
// Your tests, example:
testWidgets(
'pressing the open photos button calls openPhotosApp from $NativeImagePickerMacOS',
(WidgetTester tester) async {
await tester
.pumpWidget(const ExampleWidget()); // REPLACE WITH THE TARGET WIDGET
final openPhotosFinder =
find.text('Open Photos App'); // REPLACE WITH THE BUTTON TEXT
expect(openPhotosFinder, findsOneWidget);
// Assuming the openPhotosApp call will success.
when(mockNativeImagePickerMacOS.openPhotosApp())
.thenAnswer((_) async => true);
await tester.tap(openPhotosFinder);
await tester.pump();
verify(mockNativeImagePickerMacOS.openPhotosApp()).called(1);
verifyNoMoreInteractions(mockNativeImagePickerMacOS);
},
);
// ...
}
However, if you run the tests, you will get the following error:
Assertion failed: "Platform interfaces must not be implemented with `implements`"
And that is because by default, all plugin platform interfaces that inherit from PlatformInterface
must extends
and not implements
it to avoid breaking changes (adding new methods to platform interfaces are not considered breaking changes).
And mock classes must implements
the real class rather than extends
them, a solution is to provide the mixin MockPlatformInterfaceMixin
from plugin_platform_interface
that will override this check:
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
// This doesn't work yet since MockNativeImagePickerMacOS is generated, unlike the mocktail package.
class MockNativeImagePickerMacOS extends Mock
with MockPlatformInterfaceMixin
implements NativeImagePickerMacOS {}
And since MockNativeImagePickerMacOS
is generated, we need a new class that extends the base mock and provides the MockPlatformInterfaceMixin
for plugin_platform_interface
to not throw the assertion failure:
@GenerateNiceMocks([MockSpec<NativeImagePickerMacOS>(as: Symbol('BaseMockNativeImagePickerMacOS'))]) // This name should be different than MockNativeImagePickerMacOS for the mockito generation to success
import '<current-test-file-name>.mocks.dart'; // REPLACE <current-test-file-name> with the current test file name without extension
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
class MockNativeImagePickerMacOS extends BaseMockNativeImagePickerMacOS
with MockPlatformInterfaceMixin {}
// Use MockNativeImagePickerMacOS instead of BaseMockNativeImagePickerMacOS for creating the mock of NativeImagePickerMacOS
Refer to the example main_test.dart for the full example test.
Note
Refer to the Flutter documentation on mocking dependencies using Mockito for more details.