[path_provider] Switch macOS to an internal method channel (#4547)
Eliminates the path_provider_macos reliance on the default, shared method channel implementation, in favor of an in-package implementation.
Now that it's trivial to do so, also moves the creation of directories when necessary to the Dart side, and unit tests it there.
Part of https://github.com/flutter/flutter/issues/94224
diff --git a/packages/path_provider/path_provider_macos/CHANGELOG.md b/packages/path_provider/path_provider_macos/CHANGELOG.md
index d88ed06..49c2058 100644
--- a/packages/path_provider/path_provider_macos/CHANGELOG.md
+++ b/packages/path_provider/path_provider_macos/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.0.4
+
+* Switches to a package-internal implementation of the platform interface.
+
## 2.0.3
* Fixes link in README.
diff --git a/packages/path_provider/path_provider_macos/lib/path_provider_macos.dart b/packages/path_provider/path_provider_macos/lib/path_provider_macos.dart
new file mode 100644
index 0000000..6b3a6fa
--- /dev/null
+++ b/packages/path_provider/path_provider_macos/lib/path_provider_macos.dart
@@ -0,0 +1,72 @@
+// Copyright 2013 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.
+
+import 'dart:io';
+
+import 'package:flutter/services.dart';
+import 'package:meta/meta.dart';
+import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
+
+/// The macOS implementation of [PathProviderPlatform].
+class PathProviderMacOS extends PathProviderPlatform {
+ /// The method channel used to interact with the native platform.
+ @visibleForTesting
+ MethodChannel methodChannel =
+ const MethodChannel('plugins.flutter.io/path_provider_macos');
+
+ /// Registers this class as the default instance of [PathProviderPlatform]
+ static void registerWith() {
+ PathProviderPlatform.instance = PathProviderMacOS();
+ }
+
+ @override
+ Future<String?> getTemporaryPath() {
+ return methodChannel.invokeMethod<String>('getTemporaryDirectory');
+ }
+
+ @override
+ Future<String?> getApplicationSupportPath() async {
+ final String? path = await methodChannel
+ .invokeMethod<String>('getApplicationSupportDirectory');
+ if (path != null) {
+ // Ensure the directory exists before returning it, for consistency with
+ // other platforms.
+ await Directory(path).create(recursive: true);
+ }
+ return path;
+ }
+
+ @override
+ Future<String?> getLibraryPath() {
+ return methodChannel.invokeMethod<String>('getLibraryDirectory');
+ }
+
+ @override
+ Future<String?> getApplicationDocumentsPath() {
+ return methodChannel
+ .invokeMethod<String>('getApplicationDocumentsDirectory');
+ }
+
+ @override
+ Future<String?> getExternalStoragePath() async {
+ throw UnsupportedError('getExternalStoragePath is not supported on macOS');
+ }
+
+ @override
+ Future<List<String>?> getExternalCachePaths() async {
+ throw UnsupportedError('getExternalCachePaths is not supported on macOS');
+ }
+
+ @override
+ Future<List<String>?> getExternalStoragePaths({
+ StorageDirectory? type,
+ }) async {
+ throw UnsupportedError('getExternalStoragePaths is not supported on macOS');
+ }
+
+ @override
+ Future<String?> getDownloadsPath() {
+ return methodChannel.invokeMethod<String>('getDownloadsDirectory');
+ }
+}
diff --git a/packages/path_provider/path_provider_macos/macos/Classes/PathProviderPlugin.swift b/packages/path_provider/path_provider_macos/macos/Classes/PathProviderPlugin.swift
index b308793..e138eee 100644
--- a/packages/path_provider/path_provider_macos/macos/Classes/PathProviderPlugin.swift
+++ b/packages/path_provider/path_provider_macos/macos/Classes/PathProviderPlugin.swift
@@ -8,7 +8,7 @@
public class PathProviderPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(
- name: "plugins.flutter.io/path_provider",
+ name: "plugins.flutter.io/path_provider_macos",
binaryMessenger: registrar.messenger)
let instance = PathProviderPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
@@ -25,16 +25,6 @@
if let basePath = path {
let basePathURL = URL.init(fileURLWithPath: basePath)
path = basePathURL.appendingPathComponent(Bundle.main.bundleIdentifier!).path
- do {
- try FileManager.default.createDirectory(atPath: path!, withIntermediateDirectories: true)
- } catch {
- result(
- FlutterError(
- code: "directory_creation_failure",
- message: error.localizedDescription,
- details: "\(error)"))
- return
- }
}
result(path)
case "getLibraryDirectory":
diff --git a/packages/path_provider/path_provider_macos/pubspec.yaml b/packages/path_provider/path_provider_macos/pubspec.yaml
index ac6011d..9eb1bb4 100644
--- a/packages/path_provider/path_provider_macos/pubspec.yaml
+++ b/packages/path_provider/path_provider_macos/pubspec.yaml
@@ -2,7 +2,7 @@
description: macOS implementation of the path_provider plugin
repository: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider_macos
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22
-version: 2.0.3
+version: 2.0.4
environment:
sdk: ">=2.12.0 <3.0.0"
@@ -14,10 +14,16 @@
platforms:
macos:
pluginClass: PathProviderPlugin
+ dartPluginClass: PathProviderMacOS
dependencies:
flutter:
sdk: flutter
+ meta: ^1.3.0
+ path_provider_platform_interface: ^2.0.1
dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ path: ^1.8.0
pedantic: ^1.10.0
diff --git a/packages/path_provider/path_provider_macos/test/path_provider_macos_test.dart b/packages/path_provider/path_provider_macos/test/path_provider_macos_test.dart
new file mode 100644
index 0000000..7e783aa
--- /dev/null
+++ b/packages/path_provider/path_provider_macos/test/path_provider_macos_test.dart
@@ -0,0 +1,137 @@
+// Copyright 2013 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.
+
+import 'dart:io';
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:path/path.dart' as p;
+import 'package:path_provider_macos/path_provider_macos.dart';
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ group('PathProviderMacOS', () {
+ late PathProviderMacOS pathProvider;
+ late List<MethodCall> log;
+ // These unit tests use the actual filesystem, since an injectable
+ // filesystem would add a runtime dependency to the package, so everything
+ // is contained to a temporary directory.
+ late Directory testRoot;
+
+ late String temporaryPath;
+ late String applicationSupportPath;
+ late String libraryPath;
+ late String applicationDocumentsPath;
+ late String downloadsPath;
+
+ setUp(() async {
+ pathProvider = PathProviderMacOS();
+
+ testRoot = Directory.systemTemp.createTempSync();
+ final String basePath = testRoot.path;
+ temporaryPath = p.join(basePath, 'temporary', 'path');
+ applicationSupportPath =
+ p.join(basePath, 'application', 'support', 'path');
+ libraryPath = p.join(basePath, 'library', 'path');
+ applicationDocumentsPath =
+ p.join(basePath, 'application', 'documents', 'path');
+ downloadsPath = p.join(basePath, 'downloads', 'path');
+
+ log = <MethodCall>[];
+ TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger
+ .setMockMethodCallHandler(pathProvider.methodChannel,
+ (MethodCall methodCall) async {
+ log.add(methodCall);
+ switch (methodCall.method) {
+ case 'getTemporaryDirectory':
+ return temporaryPath;
+ case 'getApplicationSupportDirectory':
+ return applicationSupportPath;
+ case 'getLibraryDirectory':
+ return libraryPath;
+ case 'getApplicationDocumentsDirectory':
+ return applicationDocumentsPath;
+ case 'getDownloadsDirectory':
+ return downloadsPath;
+ default:
+ return null;
+ }
+ });
+ });
+
+ tearDown(() {
+ testRoot.deleteSync(recursive: true);
+ });
+
+ test('getTemporaryPath', () async {
+ final String? path = await pathProvider.getTemporaryPath();
+ expect(
+ log,
+ <Matcher>[isMethodCall('getTemporaryDirectory', arguments: null)],
+ );
+ expect(path, temporaryPath);
+ });
+
+ test('getApplicationSupportPath', () async {
+ final String? path = await pathProvider.getApplicationSupportPath();
+ expect(
+ log,
+ <Matcher>[
+ isMethodCall('getApplicationSupportDirectory', arguments: null)
+ ],
+ );
+ expect(path, applicationSupportPath);
+ });
+
+ test('getApplicationSupportPath creates the directory if necessary',
+ () async {
+ final String? path = await pathProvider.getApplicationSupportPath();
+ expect(Directory(path!).existsSync(), isTrue);
+ });
+
+ test('getLibraryPath', () async {
+ final String? path = await pathProvider.getLibraryPath();
+ expect(
+ log,
+ <Matcher>[isMethodCall('getLibraryDirectory', arguments: null)],
+ );
+ expect(path, libraryPath);
+ });
+
+ test('getApplicationDocumentsPath', () async {
+ final String? path = await pathProvider.getApplicationDocumentsPath();
+ expect(
+ log,
+ <Matcher>[
+ isMethodCall('getApplicationDocumentsDirectory', arguments: null)
+ ],
+ );
+ expect(path, applicationDocumentsPath);
+ });
+
+ test('getDownloadsPath', () async {
+ final String? result = await pathProvider.getDownloadsPath();
+ expect(
+ log,
+ <Matcher>[isMethodCall('getDownloadsDirectory', arguments: null)],
+ );
+ expect(result, downloadsPath);
+ });
+
+ test('getExternalCachePaths throws', () async {
+ expect(pathProvider.getExternalCachePaths(), throwsA(isUnsupportedError));
+ });
+
+ test('getExternalStoragePath throws', () async {
+ expect(
+ pathProvider.getExternalStoragePath(), throwsA(isUnsupportedError));
+ });
+
+ test('getExternalStoragePaths throws', () async {
+ expect(
+ pathProvider.getExternalStoragePaths(), throwsA(isUnsupportedError));
+ });
+ });
+}