Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 017a57e

Browse files
enedChris Yang
authored andcommitted
[path_provider] Android: Support multiple external storage options (#2049)
1 parent dadb913 commit 017a57e

File tree

12 files changed

+439
-41
lines changed

12 files changed

+439
-41
lines changed

packages/path_provider/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 1.4.0
2+
3+
* Support retrieving storage paths on Android devices with multiple external
4+
storage options. This adds a new class `AndroidEnvironment` that shadows the
5+
directory names from Androids `android.os.Environment` class.
6+
* Fixes `getLibraryDirectory` semantics & tests.
7+
18
## 1.3.1
29

310
* Define clang module for iOS.

packages/path_provider/android/build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,8 @@ android {
4545
disable 'InvalidPackage'
4646
}
4747
}
48+
49+
dependencies {
50+
implementation 'androidx.annotation:annotation:1.1.0'
51+
testImplementation 'junit:junit:4.12'
52+
}

packages/path_provider/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44

55
package io.flutter.plugins.pathprovider;
66

7+
import android.os.Build.VERSION;
8+
import android.os.Build.VERSION_CODES;
9+
import androidx.annotation.NonNull;
710
import io.flutter.plugin.common.MethodCall;
811
import io.flutter.plugin.common.MethodChannel;
912
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
1013
import io.flutter.plugin.common.MethodChannel.Result;
1114
import io.flutter.plugin.common.PluginRegistry.Registrar;
1215
import io.flutter.util.PathUtils;
1316
import java.io.File;
17+
import java.util.ArrayList;
18+
import java.util.List;
1419

1520
public class PathProviderPlugin implements MethodCallHandler {
21+
1622
private final Registrar mRegistrar;
1723

1824
public static void registerWith(Registrar registrar) {
@@ -27,7 +33,7 @@ private PathProviderPlugin(Registrar registrar) {
2733
}
2834

2935
@Override
30-
public void onMethodCall(MethodCall call, Result result) {
36+
public void onMethodCall(MethodCall call, @NonNull Result result) {
3137
switch (call.method) {
3238
case "getTemporaryDirectory":
3339
result.success(getPathProviderTemporaryDirectory());
@@ -38,6 +44,14 @@ public void onMethodCall(MethodCall call, Result result) {
3844
case "getStorageDirectory":
3945
result.success(getPathProviderStorageDirectory());
4046
break;
47+
case "getExternalCacheDirectories":
48+
result.success(getPathProviderExternalCacheDirectories());
49+
break;
50+
case "getExternalStorageDirectories":
51+
final Integer type = call.argument("type");
52+
final String directoryName = StorageDirectoryMapper.androidType(type);
53+
result.success(getPathProviderExternalStorageDirectories(directoryName));
54+
break;
4155
case "getApplicationSupportDirectory":
4256
result.success(getApplicationSupportDirectory());
4357
break;
@@ -65,4 +79,42 @@ private String getPathProviderStorageDirectory() {
6579
}
6680
return dir.getAbsolutePath();
6781
}
82+
83+
private List<String> getPathProviderExternalCacheDirectories() {
84+
final List<String> paths = new ArrayList<>();
85+
86+
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
87+
for (File dir : mRegistrar.context().getExternalCacheDirs()) {
88+
if (dir != null) {
89+
paths.add(dir.getAbsolutePath());
90+
}
91+
}
92+
} else {
93+
File dir = mRegistrar.context().getExternalCacheDir();
94+
if (dir != null) {
95+
paths.add(dir.getAbsolutePath());
96+
}
97+
}
98+
99+
return paths;
100+
}
101+
102+
private List<String> getPathProviderExternalStorageDirectories(String type) {
103+
final List<String> paths = new ArrayList<>();
104+
105+
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
106+
for (File dir : mRegistrar.context().getExternalFilesDirs(type)) {
107+
if (dir != null) {
108+
paths.add(dir.getAbsolutePath());
109+
}
110+
}
111+
} else {
112+
File dir = mRegistrar.context().getExternalFilesDir(type);
113+
if (dir != null) {
114+
paths.add(dir.getAbsolutePath());
115+
}
116+
}
117+
118+
return paths;
119+
}
68120
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.flutter.plugins.pathprovider;
2+
3+
import android.os.Build.VERSION;
4+
import android.os.Build.VERSION_CODES;
5+
import android.os.Environment;
6+
7+
/** Helps to map the Dart `StorageDirectory` enum to a Android system constant. */
8+
class StorageDirectoryMapper {
9+
10+
/**
11+
* Return a Android Environment constant for a Dart Index.
12+
*
13+
* @return The correct Android Environment constant or null, if the index is null.
14+
* @throws IllegalArgumentException If `dartIndex` is not null but also not matches any known
15+
* index.
16+
*/
17+
static String androidType(Integer dartIndex) throws IllegalArgumentException {
18+
if (dartIndex == null) {
19+
return null;
20+
}
21+
22+
switch (dartIndex) {
23+
case 0:
24+
return Environment.DIRECTORY_MUSIC;
25+
case 1:
26+
return Environment.DIRECTORY_PODCASTS;
27+
case 2:
28+
return Environment.DIRECTORY_RINGTONES;
29+
case 3:
30+
return Environment.DIRECTORY_ALARMS;
31+
case 4:
32+
return Environment.DIRECTORY_NOTIFICATIONS;
33+
case 5:
34+
return Environment.DIRECTORY_PICTURES;
35+
case 6:
36+
return Environment.DIRECTORY_MOVIES;
37+
case 7:
38+
return Environment.DIRECTORY_DOWNLOADS;
39+
case 8:
40+
return Environment.DIRECTORY_DCIM;
41+
case 9:
42+
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
43+
return Environment.DIRECTORY_DOCUMENTS;
44+
} else {
45+
throw new IllegalArgumentException("Documents directory is unsupported.");
46+
}
47+
default:
48+
throw new IllegalArgumentException("Unknown index: " + dartIndex);
49+
}
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package io.flutter.plugins.pathprovider;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNull;
5+
import static org.junit.Assert.fail;
6+
7+
import android.os.Environment;
8+
import org.junit.Test;
9+
10+
public class StorageDirectoryMapperTest {
11+
12+
@org.junit.Test
13+
public void testAndroidType_null() {
14+
assertNull(StorageDirectoryMapper.androidType(null));
15+
}
16+
17+
@org.junit.Test
18+
public void testAndroidType_valid() {
19+
assertEquals(Environment.DIRECTORY_MUSIC, StorageDirectoryMapper.androidType(0));
20+
assertEquals(Environment.DIRECTORY_PODCASTS, StorageDirectoryMapper.androidType(1));
21+
assertEquals(Environment.DIRECTORY_MUSIC, StorageDirectoryMapper.androidType(2));
22+
assertEquals(Environment.DIRECTORY_RINGTONES, StorageDirectoryMapper.androidType(3));
23+
assertEquals(Environment.DIRECTORY_ALARMS, StorageDirectoryMapper.androidType(4));
24+
assertEquals(Environment.DIRECTORY_NOTIFICATIONS, StorageDirectoryMapper.androidType(5));
25+
assertEquals(Environment.DIRECTORY_PICTURES, StorageDirectoryMapper.androidType(6));
26+
assertEquals(Environment.DIRECTORY_MOVIES, StorageDirectoryMapper.androidType(7));
27+
assertEquals(Environment.DIRECTORY_DOWNLOADS, StorageDirectoryMapper.androidType(8));
28+
assertEquals(Environment.DIRECTORY_DCIM, StorageDirectoryMapper.androidType(9));
29+
}
30+
31+
@Test
32+
public void testAndroidType_invalid() {
33+
try {
34+
assertEquals(Environment.DIRECTORY_DCIM, StorageDirectoryMapper.androidType(10));
35+
fail();
36+
} catch (IllegalArgumentException e) {
37+
assertEquals("Unknown index: " + 10, e.getMessage());
38+
}
39+
}
40+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
org.gradle.jvmargs=-Xmx1536M
2+
android.enableR8=true

packages/path_provider/example/lib/main.dart

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class _MyHomePageState extends State<MyHomePage> {
3939
Future<Directory> _appLibraryDirectory;
4040
Future<Directory> _appDocumentsDirectory;
4141
Future<Directory> _externalDocumentsDirectory;
42+
Future<List<Directory>> _externalStorageDirectories;
43+
Future<List<Directory>> _externalCacheDirectories;
4244

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

66+
Widget _buildDirectories(
67+
BuildContext context, AsyncSnapshot<List<Directory>> snapshot) {
68+
Text text = const Text('');
69+
if (snapshot.connectionState == ConnectionState.done) {
70+
if (snapshot.hasError) {
71+
text = Text('Error: ${snapshot.error}');
72+
} else if (snapshot.hasData) {
73+
final String combined =
74+
snapshot.data.map((Directory d) => d.path).join(', ');
75+
text = Text('paths: $combined');
76+
} else {
77+
text = const Text('path unavailable');
78+
}
79+
}
80+
return Padding(padding: const EdgeInsets.all(16.0), child: text);
81+
}
82+
6483
void _requestAppDocumentsDirectory() {
6584
setState(() {
6685
_appDocumentsDirectory = getApplicationDocumentsDirectory();
@@ -85,6 +104,18 @@ class _MyHomePageState extends State<MyHomePage> {
85104
});
86105
}
87106

107+
void _requestExternalStorageDirectories(StorageDirectory type) {
108+
setState(() {
109+
_externalStorageDirectories = getExternalStorageDirectories(type: type);
110+
});
111+
}
112+
113+
void _requestExternalCacheDirectories() {
114+
setState(() {
115+
_externalCacheDirectories = getExternalCacheDirectories();
116+
});
117+
}
118+
88119
@override
89120
Widget build(BuildContext context) {
90121
return Scaffold(
@@ -140,7 +171,39 @@ class _MyHomePageState extends State<MyHomePage> {
140171
),
141172
),
142173
FutureBuilder<Directory>(
143-
future: _externalDocumentsDirectory, builder: _buildDirectory)
174+
future: _externalDocumentsDirectory, builder: _buildDirectory),
175+
Column(children: <Widget>[
176+
Padding(
177+
padding: const EdgeInsets.all(16.0),
178+
child: RaisedButton(
179+
child: Text(
180+
'${Platform.isIOS ? "External directories are unavailable " "on iOS" : "Get External Storage Directories"}'),
181+
onPressed: Platform.isIOS
182+
? null
183+
: () {
184+
_requestExternalStorageDirectories(
185+
StorageDirectory.music,
186+
);
187+
},
188+
),
189+
),
190+
]),
191+
FutureBuilder<List<Directory>>(
192+
future: _externalStorageDirectories,
193+
builder: _buildDirectories),
194+
Column(children: <Widget>[
195+
Padding(
196+
padding: const EdgeInsets.all(16.0),
197+
child: RaisedButton(
198+
child: Text(
199+
'${Platform.isIOS ? "External directories are unavailable " "on iOS" : "Get External Cache Directories"}'),
200+
onPressed:
201+
Platform.isIOS ? null : _requestExternalCacheDirectories,
202+
),
203+
),
204+
]),
205+
FutureBuilder<List<Directory>>(
206+
future: _externalCacheDirectories, builder: _buildDirectories),
144207
],
145208
),
146209
),

packages/path_provider/example/pubspec.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ dependencies:
66
sdk: flutter
77
path_provider:
88
path: ../
9-
uuid: "^1.0.0"
109

1110
dev_dependencies:
1211
flutter_driver:

0 commit comments

Comments
 (0)