[path_provider] Switch iOS to an internal method channel (#4921)

diff --git a/packages/path_provider/path_provider_ios/CHANGELOG.md b/packages/path_provider/path_provider_ios/CHANGELOG.md
index eb155d1..543af77 100644
--- a/packages/path_provider/path_provider_ios/CHANGELOG.md
+++ b/packages/path_provider/path_provider_ios/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.0.8
+
+* Switches to a package-internal implementation of the platform interface.
+
 ## 2.0.7
 
 * Fixes link in README.
diff --git a/packages/path_provider/path_provider_ios/ios/Classes/FLTPathProviderPlugin.m b/packages/path_provider/path_provider_ios/ios/Classes/FLTPathProviderPlugin.m
index 063e028..ac6a1be 100644
--- a/packages/path_provider/path_provider_ios/ios/Classes/FLTPathProviderPlugin.m
+++ b/packages/path_provider/path_provider_ios/ios/Classes/FLTPathProviderPlugin.m
@@ -9,18 +9,11 @@
   return paths.firstObject;
 }
 
-static FlutterError *getFlutterError(NSError *error) {
-  if (error == nil) return nil;
-  return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %ld", (long)error.code]
-                             message:error.domain
-                             details:error.localizedDescription];
-}
-
 @implementation FLTPathProviderPlugin
 
 + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
   FlutterMethodChannel *channel =
-      [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/path_provider"
+      [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/path_provider_ios"
                                   binaryMessenger:registrar.messenger];
   [channel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) {
     if ([@"getTemporaryDirectory" isEqualToString:call.method]) {
@@ -28,20 +21,7 @@
     } else if ([@"getApplicationDocumentsDirectory" isEqualToString:call.method]) {
       result([self getApplicationDocumentsDirectory]);
     } else if ([@"getApplicationSupportDirectory" isEqualToString:call.method]) {
-      NSString *path = [self getApplicationSupportDirectory];
-
-      // Create the path if it doesn't exist
-      NSError *error;
-      NSFileManager *fileManager = [NSFileManager defaultManager];
-      BOOL success = [fileManager createDirectoryAtPath:path
-                            withIntermediateDirectories:YES
-                                             attributes:nil
-                                                  error:&error];
-      if (!success) {
-        result(getFlutterError(error));
-      } else {
-        result(path);
-      }
+      result([self getApplicationSupportDirectory]);
     } else if ([@"getLibraryDirectory" isEqualToString:call.method]) {
       result([self getLibraryDirectory]);
     } else {
diff --git a/packages/path_provider/path_provider_ios/lib/path_provider_ios.dart b/packages/path_provider/path_provider_ios/lib/path_provider_ios.dart
new file mode 100644
index 0000000..88becf2
--- /dev/null
+++ b/packages/path_provider/path_provider_ios/lib/path_provider_ios.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/foundation.dart' show visibleForTesting;
+import 'package:flutter/services.dart';
+import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
+
+/// The iOS implementation of [PathProviderPlatform].
+class PathProviderIOS extends PathProviderPlatform {
+  /// The method channel used to interact with the native platform.
+  @visibleForTesting
+  MethodChannel methodChannel =
+      const MethodChannel('plugins.flutter.io/path_provider_ios');
+
+  /// Registers this class as the default instance of [PathProviderPlatform]
+  static void registerWith() {
+    PathProviderPlatform.instance = PathProviderIOS();
+  }
+
+  @override
+  Future<String?> getTemporaryPath() async {
+    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() async {
+    return methodChannel.invokeMethod<String>('getLibraryDirectory');
+  }
+
+  @override
+  Future<String?> getApplicationDocumentsPath() async {
+    return methodChannel
+        .invokeMethod<String>('getApplicationDocumentsDirectory');
+  }
+
+  @override
+  Future<String?> getExternalStoragePath() async {
+    throw UnsupportedError('getExternalStoragePath is not supported on iOS');
+  }
+
+  @override
+  Future<List<String>?> getExternalCachePaths() async {
+    throw UnsupportedError('getExternalCachePaths is not supported on iOS');
+  }
+
+  @override
+  Future<List<String>?> getExternalStoragePaths({
+    StorageDirectory? type,
+  }) async {
+    throw UnsupportedError('getExternalStoragePaths is not supported on iOS');
+  }
+
+  @override
+  Future<String?> getDownloadsPath() async {
+    throw UnsupportedError('getDownloadsPath is not supported on iOS');
+  }
+}
diff --git a/packages/path_provider/path_provider_ios/pubspec.yaml b/packages/path_provider/path_provider_ios/pubspec.yaml
index 0e05121..282f8e4 100644
--- a/packages/path_provider/path_provider_ios/pubspec.yaml
+++ b/packages/path_provider/path_provider_ios/pubspec.yaml
@@ -2,11 +2,11 @@
 description: iOS implementation of the path_provider plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_ios
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22
-version: 2.0.7
+version: 2.0.8
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
-  flutter: ">=2.5.0"
+  flutter: ">=2.8.0"
 
 flutter:
   plugin:
@@ -14,6 +14,7 @@
     platforms:
       ios:
         pluginClass: FLTPathProviderPlugin
+        dartPluginClass: PathProviderIOS
 
 dependencies:
   flutter:
@@ -27,5 +28,6 @@
     sdk: flutter
   integration_test:
     sdk: flutter
+  path: ^1.8.0
   plugin_platform_interface: ^2.0.0
   test: ^1.16.0
diff --git a/packages/path_provider/path_provider_ios/test/path_provider_ios_test.dart b/packages/path_provider/path_provider_ios/test/path_provider_ios_test.dart
new file mode 100644
index 0000000..40f81c5
--- /dev/null
+++ b/packages/path_provider/path_provider_ios/test/path_provider_ios_test.dart
@@ -0,0 +1,128 @@
+// 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_ios/path_provider_ios.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  group('PathProviderIOS', () {
+    late PathProviderIOS 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;
+
+    setUp(() async {
+      pathProvider = PathProviderIOS();
+
+      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');
+
+      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;
+          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 throws', () async {
+      expect(pathProvider.getDownloadsPath(), throwsA(isUnsupportedError));
+    });
+
+    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));
+    });
+  });
+}