diff --git a/packages/path_provider/CHANGELOG.md b/packages/path_provider/CHANGELOG.md index c4c8e763f90d..3314f3872d35 100644 --- a/packages/path_provider/CHANGELOG.md +++ b/packages/path_provider/CHANGELOG.md @@ -1,3 +1,13 @@ +## 1.1.0 + +* Added `getApplicationSupportDirectory`. +* Updated documentation for `getApplicationDocumentsDirectory` to suggest + using `getApplicationSupportDirectory` on iOS and + `getExternalStorageDirectory` on Android. +* Updated documentation for `getTemporaryDirectory` to suggest using it + for caches of files that do not need to be backed up. +* Updated integration tests and example to reflect the above changes. + ## 1.0.0 * Added integration tests. diff --git a/packages/path_provider/example/lib/main.dart b/packages/path_provider/example/lib/main.dart index 9311896128ba..db32f7b68e3a 100644 --- a/packages/path_provider/example/lib/main.dart +++ b/packages/path_provider/example/lib/main.dart @@ -35,6 +35,7 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { Future _tempDirectory; + Future _appSupportDirectory; Future _appDocumentsDirectory; Future _externalDocumentsDirectory; @@ -65,6 +66,12 @@ class _MyHomePageState extends State { }); } + void _requestAppSupportDirectory() { + setState(() { + _appSupportDirectory = getApplicationSupportDirectory(); + }); + } + void _requestExternalStorageDirectory() { setState(() { _externalDocumentsDirectory = getExternalStorageDirectory(); @@ -111,6 +118,21 @@ class _MyHomePageState extends State { child: FutureBuilder( future: _appDocumentsDirectory, builder: _buildDirectory), ), + Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: RaisedButton( + child: const Text('Get Application Support Directory'), + onPressed: _requestAppSupportDirectory, + ), + ), + ], + ), + Expanded( + child: FutureBuilder( + future: _appSupportDirectory, builder: _buildDirectory), + ), Column(children: [ Padding( padding: const EdgeInsets.all(16.0), diff --git a/packages/path_provider/example/test_driver/path_provider.dart b/packages/path_provider/example/test_driver/path_provider.dart index c0c623747d17..2f225a7034d8 100644 --- a/packages/path_provider/example/test_driver/path_provider.dart +++ b/packages/path_provider/example/test_driver/path_provider.dart @@ -35,6 +35,21 @@ void main() { file.deleteSync(); }); + test('getApplicationSupportDirectory', () async { + if (Platform.isIOS) { + final Directory result = await getApplicationSupportDirectory(); + final String uuid = Uuid().v1(); + final File file = File('${result.path}/$uuid.txt'); + file.writeAsStringSync('Hello world!'); + expect(file.readAsStringSync(), 'Hello world!'); + expect(result.listSync(), isNotEmpty); + file.deleteSync(); + } else if (Platform.isAndroid) { + final Future result = getApplicationSupportDirectory(); + expect(result, throwsA(isInstanceOf())); + } + }); + test('getExternalStorageDirectory', () async { if (Platform.isIOS) { final Future result = getExternalStorageDirectory(); diff --git a/packages/path_provider/ios/Classes/PathProviderPlugin.m b/packages/path_provider/ios/Classes/PathProviderPlugin.m index 3e82e081bf6d..d81d6c676deb 100644 --- a/packages/path_provider/ios/Classes/PathProviderPlugin.m +++ b/packages/path_provider/ios/Classes/PathProviderPlugin.m @@ -9,6 +9,13 @@ return paths.firstObject; } +static FlutterError* getFlutterError(NSError* error) { + if (error == nil) return nil; + return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %ld", error.code] + message:error.domain + details:error.localizedDescription]; +} + @implementation FLTPathProviderPlugin + (void)registerWithRegistrar:(NSObject*)registrar { @@ -20,6 +27,21 @@ + (void)registerWithRegistrar:(NSObject*)registrar { result([self getTemporaryDirectory]); } 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); + } } else { result(FlutterMethodNotImplemented); } @@ -34,4 +56,8 @@ + (NSString*)getApplicationDocumentsDirectory { return GetDirectoryOfType(NSDocumentDirectory); } ++ (NSString*)getApplicationSupportDirectory { + return GetDirectoryOfType(NSApplicationSupportDirectory); +} + @end diff --git a/packages/path_provider/lib/path_provider.dart b/packages/path_provider/lib/path_provider.dart index 1875f7436d8b..08e35d68dd63 100644 --- a/packages/path_provider/lib/path_provider.dart +++ b/packages/path_provider/lib/path_provider.dart @@ -10,7 +10,8 @@ import 'package:flutter/services.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/path_provider'); -/// Path to the temporary directory on the device. +/// Path to the temporary directory on the device that is not backed up and is +/// suitable for storing caches of downloaded files. /// /// Files in this directory may be cleared at any time. This does *not* return /// a new temporary directory. Instead, the caller is responsible for creating @@ -21,29 +22,47 @@ const MethodChannel _channel = /// /// On Android, this uses the `getCacheDir` API on the context. Future getTemporaryDirectory() async { - // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. - // https://github.com/flutter/flutter/issues/26431 - // ignore: strong_mode_implicit_dynamic_method - final String path = await _channel.invokeMethod('getTemporaryDirectory'); + final String path = + await _channel.invokeMethod('getTemporaryDirectory'); + if (path == null) { + return null; + } + return Directory(path); +} + +/// Path to a directory where the application may place application support +/// files. +/// +/// Use this for files you don’t want exposed to the user. Your app should not +/// use this directory for user data files. +/// +/// On iOS, this uses the `NSApplicationSupportDirectory` API. +/// If this directory does not exist, it is created automatically. +/// +/// On Android, this function throws an [UnsupportedError]. +Future getApplicationSupportDirectory() async { + if (!Platform.isIOS) + throw UnsupportedError("getApplicationSupportDirectory requires iOS"); + final String path = + await _channel.invokeMethod('getApplicationSupportDirectory'); if (path == null) { return null; } return Directory(path); } -/// Path to a directory where the application may place files that are private -/// to the application and will only be cleared when the application itself -/// is deleted. +/// Path to a directory where the application may place data that is +/// user-generated, or that cannot otherwise be recreated by your application. /// -/// On iOS, this uses the `NSDocumentsDirectory` API. +/// On iOS, this uses the `NSDocumentDirectory` API. Consider using +/// [getApplicationSupportDirectory] instead if the data is not user-generated. /// -/// On Android, this returns the AppData directory. +/// On Android, this uses the `getDataDirectory` API on the context. Consider +/// using getExternalStorageDirectory instead if data is intended to be visible +/// to the user. Future getApplicationDocumentsDirectory() async { final String path = - // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. - // https://github.com/flutter/flutter/issues/26431 - // ignore: strong_mode_implicit_dynamic_method - await _channel.invokeMethod('getApplicationDocumentsDirectory'); + await _channel.invokeMethod('getApplicationDocumentsDirectory'); if (path == null) { return null; } @@ -54,17 +73,15 @@ Future getApplicationDocumentsDirectory() async { /// The current operating system should be determined before issuing this /// function call, as this functionality is only available on Android. /// -/// On iOS, this function throws an UnsupportedError as it is not possible +/// On iOS, this function throws an [UnsupportedError] as it is not possible /// to access outside the app's sandbox. /// -/// On Android this returns getExternalStorageDirectory. +/// On Android this uses the `getExternalStorageDirectory` API. Future getExternalStorageDirectory() async { if (Platform.isIOS) throw UnsupportedError("Functionality not available on iOS"); - // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. - // https://github.com/flutter/flutter/issues/26431 - // ignore: strong_mode_implicit_dynamic_method - final String path = await _channel.invokeMethod('getStorageDirectory'); + final String path = + await _channel.invokeMethod('getStorageDirectory'); if (path == null) { return null; } diff --git a/packages/path_provider/pubspec.yaml b/packages/path_provider/pubspec.yaml index 9913857e150f..73b061924df3 100644 --- a/packages/path_provider/pubspec.yaml +++ b/packages/path_provider/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for getting commonly used locations on the Android & iOS file systems, such as the temp and app data directories. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider -version: 1.0.0 +version: 1.1.0 flutter: plugin: