Skip to content

Commit

Permalink
[path_provider] Switch macOS to an internal method channel (flutter#4547
Browse files Browse the repository at this point in the history
)

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 flutter/flutter#94224
  • Loading branch information
stuartmorgan authored and KyleFin committed Dec 21, 2021
1 parent e6030f9 commit dcc820f
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 12 deletions.
4 changes: 4 additions & 0 deletions packages/path_provider/path_provider_macos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.0.4

* Switches to a package-internal implementation of the platform interface.

## 2.0.3

* Fixes link in README.
Expand Down
Original file line number Diff line number Diff line change
@@ -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');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Foundation
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)
Expand All @@ -25,16 +25,6 @@ public class PathProviderPlugin: NSObject, FlutterPlugin {
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":
Expand Down
8 changes: 7 additions & 1 deletion packages/path_provider/path_provider_macos/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: path_provider_macos
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"
Expand All @@ -14,10 +14,16 @@ flutter:
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
Original file line number Diff line number Diff line change
@@ -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));
});
});
}

0 comments on commit dcc820f

Please sign in to comment.