Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f9bca2b
Add support for Android devices with multiple external storage options.
ened Dec 7, 2018
6414953
Merge branch 'master' into path-provider-android
Aug 20, 2019
e5fe50b
Update path_provider AGB & iOS project file
Aug 20, 2019
8c5b266
extend unit tests for new methods
Aug 20, 2019
8803465
[path_provider] Activate getApplicationSupportDirectory integration test
Aug 20, 2019
ecc8aca
Merge branch 'path_provider/integration_test' into path-provider-android
Aug 20, 2019
280f229
add integration tests
Aug 20, 2019
2a3e4d4
[path_provider] Activate getApplicationSupportDirectory integration test
Aug 20, 2019
d6f66f2
Merge branch 'path_provider/integration_test' into path-provider-android
Aug 20, 2019
72a943b
Add missing unit tests
Aug 20, 2019
b4459b3
Merge branch 'path_provider/integration_test' into path-provider-android
Aug 20, 2019
7323e37
Formatting
Aug 20, 2019
1cfb256
Merge branch 'path_provider/integration_test' into path-provider-android
Aug 20, 2019
2736545
Merge branch 'master' of https://github.com/flutter/plugins into path…
Aug 22, 2019
e05e869
Merge branch 'master' into path-provider-android
Sep 5, 2019
e577e50
Merge branch 'master' of https://github.com/flutter/plugins into path…
Sep 6, 2019
f2941d9
Merge branch 'master' of https://github.com/flutter/plugins into path…
Sep 9, 2019
cf475e1
[path_provider] Updates tests and copy Android environment names.
Sep 9, 2019
354c9fa
Bump version
Sep 9, 2019
b60142f
Revert unrelated PR changes.
Sep 9, 2019
1b5f73a
Resolve test issues introduced in #1953.
Sep 9, 2019
d08d778
Formatting.
Sep 9, 2019
11620f0
Remove unused import
Sep 9, 2019
6ce45b1
Merge branch 'master' into path-provider-android
ened Oct 4, 2019
e8c948b
{} nits and ' strings
ened Oct 4, 2019
a175402
Introduce a enum to specify the storage directory
ened Oct 4, 2019
30465c3
Refactor test driver and remove now unused uuid dependency.
ened Oct 4, 2019
b3c78db
Missing doc nit
ened Oct 4, 2019
499fbdb
dartfmt
ened Oct 4, 2019
321c331
add missing storage directory types
ened Oct 4, 2019
d368c80
dartfmt once more
ened Oct 4, 2019
9806d05
Merge branch 'master' into path-provider-android
ened Oct 7, 2019
8937f5d
Merge branch 'master' of https://github.com/flutter/plugins into path…
ened Oct 11, 2019
abf34d6
Activate JUnit
ened Oct 11, 2019
3d96cc9
Pass a dart enum (by index) directly to Android, update tests.
ened Oct 11, 2019
b7d4b58
Add unit tests for the storage directory enum.
ened Oct 11, 2019
30f49ab
nit
ened Oct 11, 2019
7c1be07
Formatting.
ened Oct 11, 2019
c8d7572
Formatting.
ened Oct 11, 2019
408a3bb
Merge branch 'master' into path-provider-android
ened Oct 15, 2019
4e9a465
Merge branch 'master' into path-provider-android
ened Oct 17, 2019
76029b6
add clear exception when documents directory is unsupported
ened Oct 17, 2019
bc55bb2
Bump to V1.4.0 due to public API changes
ened Oct 17, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/path_provider/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 1.3.1

* Support retrieving storage paths on Android devices with multiple external
storage options. This adds a new class `AndroidEnvironment` that shadows the
directory names from Androids `android.os.Environment` class.
* Fixes `getLibraryDirectory` semantics & tests.

## 1.3.0

* Added iOS-only support for `getLibraryDirectory`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@

package io.flutter.plugins.pathprovider;

import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.util.PathUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class PathProviderPlugin implements MethodCallHandler {

private final Registrar mRegistrar;

public static void registerWith(Registrar registrar) {
Expand All @@ -38,6 +43,13 @@ public void onMethodCall(MethodCall call, Result result) {
case "getStorageDirectory":
result.success(getPathProviderStorageDirectory());
break;
case "getExternalCacheDirectories":
result.success(getPathProviderExternalCacheDirectories());
break;
case "getExternalStorageDirectories":
final String type = call.argument("type");
result.success(getPathProviderExternalStorageDirectories(type));
break;
case "getApplicationSupportDirectory":
result.success(getApplicationSupportDirectory());
break;
Expand Down Expand Up @@ -65,4 +77,42 @@ private String getPathProviderStorageDirectory() {
}
return dir.getAbsolutePath();
}

private List<String> getPathProviderExternalCacheDirectories() {
final List<String> paths = new ArrayList<>();

if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
for (File dir : mRegistrar.context().getExternalCacheDirs()) {
if (dir != null) {
paths.add(dir.getAbsolutePath());
}
}
} else {
File dir = mRegistrar.context().getExternalCacheDir();
if (dir != null) {
paths.add(dir.getAbsolutePath());
}
}

return paths;
}

private List<String> getPathProviderExternalStorageDirectories(String type) {
final List<String> paths = new ArrayList<>();

if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
for (File dir : mRegistrar.context().getExternalFilesDirs(type)) {
if (dir != null) {
paths.add(dir.getAbsolutePath());
}
}
} else {
File dir = mRegistrar.context().getExternalFilesDir(type);
if (dir != null) {
paths.add(dir.getAbsolutePath());
}
}

return paths;
}
}
64 changes: 63 additions & 1 deletion packages/path_provider/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class _MyHomePageState extends State<MyHomePage> {
Future<Directory> _appLibraryDirectory;
Future<Directory> _appDocumentsDirectory;
Future<Directory> _externalDocumentsDirectory;
Future<List<Directory>> _externalStorageDirectories;
Future<List<Directory>> _externalCacheDirectories;

void _requestTempDirectory() {
setState(() {
Expand All @@ -61,6 +63,23 @@ class _MyHomePageState extends State<MyHomePage> {
return Padding(padding: const EdgeInsets.all(16.0), child: text);
}

Widget _buildDirectories(
BuildContext context, AsyncSnapshot<List<Directory>> snapshot) {
Text text = const Text('');
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
text = Text('Error: ${snapshot.error}');
} else if (snapshot.hasData) {
final String combined =
snapshot.data.map((Directory d) => d.path).join(', ');
text = Text('paths: $combined');
} else {
text = const Text('path unavailable');
}
}
return Padding(padding: const EdgeInsets.all(16.0), child: text);
}

void _requestAppDocumentsDirectory() {
setState(() {
_appDocumentsDirectory = getApplicationDocumentsDirectory();
Expand All @@ -85,6 +104,18 @@ class _MyHomePageState extends State<MyHomePage> {
});
}

void _requestExternalStorageDirectories(String type) {
setState(() {
_externalStorageDirectories = getExternalStorageDirectories(type);
});
}

void _requestExternalCacheDirectories() {
setState(() {
_externalCacheDirectories = getExternalCacheDirectories();
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
Expand Down Expand Up @@ -140,7 +171,38 @@ class _MyHomePageState extends State<MyHomePage> {
),
),
FutureBuilder<Directory>(
future: _externalDocumentsDirectory, builder: _buildDirectory)
future: _externalDocumentsDirectory, builder: _buildDirectory),
Column(children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: RaisedButton(
child: Text(
'${Platform.isIOS ? "External directories are unavailable " "on iOS" : "Get External Storage Directories"}'),
onPressed: Platform.isIOS
? null
: () {
_requestExternalStorageDirectories(
AndroidEnvironment.DIRECTORY_MUSIC);
},
),
),
]),
FutureBuilder<List<Directory>>(
future: _externalStorageDirectories,
builder: _buildDirectories),
Column(children: <Widget>[

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cyanglaz I have followed the style in the current example code, but think the indentation could be shown better with a few additional "," here & there. Perhaps better to be added in a future PR?

Padding(
padding: const EdgeInsets.all(16.0),
child: RaisedButton(
child: Text(
'${Platform.isIOS ? "External directories are unavailable " "on iOS" : "Get External Cache Directories"}'),
onPressed:
Platform.isIOS ? null : _requestExternalCacheDirectories,
),
),
]),
FutureBuilder<List<Directory>>(
future: _externalCacheDirectories, builder: _buildDirectories),
],
),
),
Expand Down
48 changes: 48 additions & 0 deletions packages/path_provider/example/test_driver/path_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,52 @@ void main() {
file.deleteSync();
}
});

test('getExternalCacheDirectories', () async {
if (Platform.isIOS) {
final Future<List<Directory>> result = getExternalCacheDirectories();
expect(result, throwsA(isInstanceOf<UnsupportedError>()));
} else if (Platform.isAndroid) {
final List<Directory> directories = await getExternalCacheDirectories();
for (Directory result in directories) {
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();
}
}
});

test('getExternalStorageDirectories', () async {
if (Platform.isIOS) {
final Future<List<Directory>> result =
getExternalStorageDirectories(null);
expect(result, throwsA(isInstanceOf<UnsupportedError>()));
} else if (Platform.isAndroid) {
final List<String> allDirs = <String>[
null,
AndroidEnvironment.DIRECTORY_MUSIC,
AndroidEnvironment.DIRECTORY_PODCASTS,
AndroidEnvironment.DIRECTORY_RINGTONES,
AndroidEnvironment.DIRECTORY_ALARMS,
AndroidEnvironment.DIRECTORY_NOTIFICATIONS,
AndroidEnvironment.DIRECTORY_PICTURES,
AndroidEnvironment.DIRECTORY_MOVIES,
];
for (String type in allDirs) {
final List<Directory> directories =
await getExternalStorageDirectories(type);
for (Directory result in directories) {
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();
}
}
}
});
Comment thread
ened marked this conversation as resolved.
Outdated
}
66 changes: 66 additions & 0 deletions packages/path_provider/lib/path_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ Future<Directory> getApplicationSupportDirectory() async {

/// Path to the directory where application can store files that are persistent,
/// backed up, and not visible to the user, such as sqlite.db.
///
/// On Android, this function throws an [UnsupportedError] as no equivalent
/// folder exists.
Comment thread
ened marked this conversation as resolved.
Outdated
Future<Directory> getLibraryDirectory() async {
if (_platform.isAndroid)
Comment thread
ened marked this conversation as resolved.
Outdated
throw UnsupportedError("Functionality not available on Android");
final String path =
await _channel.invokeMethod<String>('getLibraryDirectory');
if (path == null) {
Expand Down Expand Up @@ -106,3 +111,64 @@ Future<Directory> getExternalStorageDirectory() async {
}
return Directory(path);
}

/// Paths to directories where application specific cache data can be stored.
Comment thread
ened marked this conversation as resolved.
Outdated
/// These paths typically reside on external storage like separate partitions
/// or SD cards. Phones may have multiple storage directories available.
///
/// 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
/// to access outside the app's sandbox.
///
/// On Android this returns Context.getExternalCacheDirs() or
/// Context.getExternalCacheDir() on API levels below 19.
Future<List<Directory>> getExternalCacheDirectories() async {
if (_platform.isIOS)
Comment thread
ened marked this conversation as resolved.
Outdated
throw UnsupportedError("Functionality not available on iOS");
final List<String> paths =
await _channel.invokeListMethod<String>('getExternalCacheDirectories');

return paths.map((String path) => Directory(path)).toList();
}

/// Shadows directory values from Androids `android.os.Environment` class.
///
/// https://developer.android.com/reference/android/os/Environment.html#fields_1
class AndroidEnvironment {
Comment thread
ened marked this conversation as resolved.
Outdated
static const String DIRECTORY_MUSIC = 'Music';
static const String DIRECTORY_PODCASTS = 'Podcasts';
static const String DIRECTORY_RINGTONES = 'Ringtones';
static const String DIRECTORY_ALARMS = 'Alarms';
static const String DIRECTORY_NOTIFICATIONS = 'Notifications';
static const String DIRECTORY_PICTURES = 'Pictures';
static const String DIRECTORY_MOVIES = 'Movies';
}

/// Paths to directories where application specific data can be stored.
/// These paths typically reside on external storage like separate partitions
/// or SD cards. Phones may have multiple storage directories available.
///
/// 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
/// to access outside the app's sandbox.
///
/// On Android this returns Context.getExternalFilesDirs(String type) or
/// Context.getExternalFilesDir(String type) on API levels below 19.
///
/// The parameter [type] is optional. If it is set, it *must* be one of the
/// constants defined in [AndroidEnvironment]. See [AndroidEnvironment] for
/// more information.
Future<List<Directory>> getExternalStorageDirectories(String type) async {
Comment thread
ened marked this conversation as resolved.
Outdated
if (_platform.isIOS)
throw UnsupportedError("Functionality not available on iOS");
final List<String> paths = await _channel.invokeListMethod<String>(
'getExternalStorageDirectories',
<String, String>{"type": type},
);

return paths.map((String path) => Directory(path)).toList();
}
2 changes: 1 addition & 1 deletion packages/path_provider/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <flutter-dev@googlegroups.com>
homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider
version: 1.3.0
version: 1.3.1

flutter:
plugin:
Expand Down
Loading