-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[path_provider] started supporting background platform channels #4443
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,51 +9,183 @@ | |
import android.os.Build.VERSION_CODES; | ||
import android.os.Handler; | ||
import android.os.Looper; | ||
import android.util.Log; | ||
import androidx.annotation.NonNull; | ||
import com.google.common.util.concurrent.FutureCallback; | ||
import com.google.common.util.concurrent.Futures; | ||
import com.google.common.util.concurrent.SettableFuture; | ||
import com.google.common.util.concurrent.ThreadFactoryBuilder; | ||
import io.flutter.embedding.engine.plugins.FlutterPlugin; | ||
import io.flutter.plugin.common.BinaryMessenger; | ||
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.MethodCodec; | ||
import io.flutter.plugin.common.StandardMethodCodec; | ||
import io.flutter.util.PathUtils; | ||
import java.io.File; | ||
import java.lang.reflect.Constructor; | ||
import java.lang.reflect.Method; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.concurrent.Callable; | ||
import java.util.concurrent.Executor; | ||
import java.util.concurrent.Executors; | ||
|
||
public class PathProviderPlugin implements FlutterPlugin, MethodCallHandler { | ||
|
||
static final String TAG = "PathProviderPlugin"; | ||
private Context context; | ||
private MethodChannel channel; | ||
private final Executor uiThreadExecutor = new UiThreadExecutor(); | ||
private final Executor executor = | ||
Executors.newSingleThreadExecutor( | ||
new ThreadFactoryBuilder() | ||
.setNameFormat("path-provider-background-%d") | ||
.setPriority(Thread.NORM_PRIORITY) | ||
.build()); | ||
private PathProviderImpl impl; | ||
|
||
/** | ||
* An abstraction over how to access the paths in a thread-safe manner. | ||
* | ||
* <p>We need this so on versions of Flutter that support Background Platform Channels this plugin | ||
* can take advantage of it. | ||
* | ||
* <p>This can be removed after https://github.com/flutter/engine/pull/29147 becomes available on | ||
* the stable branch. | ||
*/ | ||
private interface PathProviderImpl { | ||
void getTemporaryDirectory(@NonNull Result result); | ||
|
||
void getApplicationDocumentsDirectory(@NonNull Result result); | ||
|
||
void getStorageDirectory(@NonNull Result result); | ||
|
||
void getExternalCacheDirectories(@NonNull Result result); | ||
|
||
void getExternalStorageDirectories(@NonNull String directoryName, @NonNull Result result); | ||
|
||
void getApplicationSupportDirectory(@NonNull Result result); | ||
} | ||
|
||
/** The implementation for getting system paths that executes from the platform */ | ||
private class PathProviderPlatformThread implements PathProviderImpl { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is the shim, could it have a name that is easier to map to the reason why this class exists? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added docstring to clarify its purpose. I couldn't pack all that information into a name. This is something that exists temporarily so between the documentation and the good enough name, hopefully that's satisfactory. |
||
private final Executor uiThreadExecutor = new UiThreadExecutor(); | ||
private final Executor executor = | ||
Executors.newSingleThreadExecutor( | ||
new ThreadFactoryBuilder() | ||
.setNameFormat("path-provider-background-%d") | ||
.setPriority(Thread.NORM_PRIORITY) | ||
.build()); | ||
|
||
public void getTemporaryDirectory(@NonNull Result result) { | ||
executeInBackground(() -> getPathProviderTemporaryDirectory(), result); | ||
} | ||
|
||
public void getApplicationDocumentsDirectory(@NonNull Result result) { | ||
executeInBackground(() -> getPathProviderApplicationDocumentsDirectory(), result); | ||
} | ||
|
||
public void getStorageDirectory(@NonNull Result result) { | ||
executeInBackground(() -> getPathProviderStorageDirectory(), result); | ||
} | ||
|
||
public void getExternalCacheDirectories(@NonNull Result result) { | ||
executeInBackground(() -> getPathProviderExternalCacheDirectories(), result); | ||
} | ||
|
||
public void getExternalStorageDirectories( | ||
@NonNull String directoryName, @NonNull Result result) { | ||
executeInBackground(() -> getPathProviderExternalStorageDirectories(directoryName), result); | ||
} | ||
|
||
public void getApplicationSupportDirectory(@NonNull Result result) { | ||
executeInBackground(() -> PathProviderPlugin.this.getApplicationSupportDirectory(), result); | ||
} | ||
|
||
private <T> void executeInBackground(Callable<T> task, Result result) { | ||
final SettableFuture<T> future = SettableFuture.create(); | ||
Futures.addCallback( | ||
future, | ||
new FutureCallback<T>() { | ||
public void onSuccess(T answer) { | ||
result.success(answer); | ||
} | ||
|
||
public void onFailure(Throwable t) { | ||
result.error(t.getClass().getName(), t.getMessage(), null); | ||
} | ||
}, | ||
uiThreadExecutor); | ||
executor.execute( | ||
() -> { | ||
try { | ||
future.set(task.call()); | ||
} catch (Throwable t) { | ||
future.setException(t); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
/** The implementation for getting system paths that executes from a background thread. */ | ||
private class PathProviderBackgroundThread implements PathProviderImpl { | ||
public void getTemporaryDirectory(@NonNull Result result) { | ||
result.success(getPathProviderTemporaryDirectory()); | ||
} | ||
|
||
public void getApplicationDocumentsDirectory(@NonNull Result result) { | ||
result.success(getPathProviderApplicationDocumentsDirectory()); | ||
} | ||
|
||
public void getStorageDirectory(@NonNull Result result) { | ||
result.success(getPathProviderStorageDirectory()); | ||
} | ||
|
||
public void getExternalCacheDirectories(@NonNull Result result) { | ||
result.success(getPathProviderExternalCacheDirectories()); | ||
} | ||
|
||
public void getExternalStorageDirectories( | ||
@NonNull String directoryName, @NonNull Result result) { | ||
result.success(getPathProviderExternalStorageDirectories(directoryName)); | ||
} | ||
|
||
public void getApplicationSupportDirectory(@NonNull Result result) { | ||
result.success(PathProviderPlugin.this.getApplicationSupportDirectory()); | ||
} | ||
} | ||
|
||
public PathProviderPlugin() {} | ||
|
||
private void setup(BinaryMessenger messenger, Context context) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remember when naming methods that "setup" is a noun, and "set up" is a verb, so a method (which should be named with a verb) should be called "setUp". I'm aware that Pigeon has this issue, but we shouldn't spread it beyond there. |
||
String channelName = "plugins.flutter.io/path_provider"; | ||
// TODO(gaaclarke): Remove reflection guard when https://github.com/flutter/engine/pull/29147 | ||
// becomes available on the stable branch. | ||
try { | ||
Class methodChannelClass = Class.forName("io.flutter.plugin.common.MethodChannel"); | ||
Class taskQueueClass = Class.forName("io.flutter.plugin.common.BinaryMessenger$TaskQueue"); | ||
Method makeBackgroundTaskQueue = messenger.getClass().getMethod("makeBackgroundTaskQueue"); | ||
Object taskQueue = makeBackgroundTaskQueue.invoke(messenger); | ||
Constructor<MethodChannel> constructor = | ||
methodChannelClass.getConstructor( | ||
BinaryMessenger.class, String.class, MethodCodec.class, taskQueueClass); | ||
channel = | ||
constructor.newInstance(messenger, channelName, StandardMethodCodec.INSTANCE, taskQueue); | ||
impl = new PathProviderBackgroundThread(); | ||
Log.d(TAG, "Use TaskQueues."); | ||
} catch (Exception ex) { | ||
channel = new MethodChannel(messenger, channelName); | ||
impl = new PathProviderPlatformThread(); | ||
Log.d(TAG, "Don't use TaskQueues."); | ||
} | ||
this.context = context; | ||
channel.setMethodCallHandler(this); | ||
} | ||
|
||
@SuppressWarnings("deprecation") | ||
public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { | ||
PathProviderPlugin instance = new PathProviderPlugin(); | ||
instance.channel = new MethodChannel(registrar.messenger(), "plugins.flutter.io/path_provider"); | ||
instance.context = registrar.context(); | ||
instance.channel.setMethodCallHandler(instance); | ||
instance.setup(registrar.messenger(), registrar.context()); | ||
} | ||
|
||
@Override | ||
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { | ||
channel = new MethodChannel(binding.getBinaryMessenger(), "plugins.flutter.io/path_provider"); | ||
context = binding.getApplicationContext(); | ||
channel.setMethodCallHandler(this); | ||
setup(binding.getBinaryMessenger(), binding.getApplicationContext()); | ||
} | ||
|
||
@Override | ||
|
@@ -62,52 +194,28 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { | |
channel = null; | ||
} | ||
|
||
private <T> void executeInBackground(Callable<T> task, Result result) { | ||
final SettableFuture<T> future = SettableFuture.create(); | ||
Futures.addCallback( | ||
future, | ||
new FutureCallback<T>() { | ||
public void onSuccess(T answer) { | ||
result.success(answer); | ||
} | ||
|
||
public void onFailure(Throwable t) { | ||
result.error(t.getClass().getName(), t.getMessage(), null); | ||
} | ||
}, | ||
uiThreadExecutor); | ||
executor.execute( | ||
() -> { | ||
try { | ||
future.set(task.call()); | ||
} catch (Throwable t) { | ||
future.setException(t); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
public void onMethodCall(MethodCall call, @NonNull Result result) { | ||
switch (call.method) { | ||
case "getTemporaryDirectory": | ||
executeInBackground(() -> getPathProviderTemporaryDirectory(), result); | ||
impl.getTemporaryDirectory(result); | ||
break; | ||
case "getApplicationDocumentsDirectory": | ||
executeInBackground(() -> getPathProviderApplicationDocumentsDirectory(), result); | ||
impl.getApplicationDocumentsDirectory(result); | ||
break; | ||
case "getStorageDirectory": | ||
executeInBackground(() -> getPathProviderStorageDirectory(), result); | ||
impl.getStorageDirectory(result); | ||
break; | ||
case "getExternalCacheDirectories": | ||
executeInBackground(() -> getPathProviderExternalCacheDirectories(), result); | ||
impl.getExternalCacheDirectories(result); | ||
break; | ||
case "getExternalStorageDirectories": | ||
final Integer type = call.argument("type"); | ||
final String directoryName = StorageDirectoryMapper.androidType(type); | ||
executeInBackground(() -> getPathProviderExternalStorageDirectories(directoryName), result); | ||
impl.getExternalStorageDirectories(directoryName, result); | ||
break; | ||
case "getApplicationSupportDirectory": | ||
executeInBackground(() -> getApplicationSupportDirectory(), result); | ||
impl.getApplicationSupportDirectory(result); | ||
break; | ||
default: | ||
result.notImplemented(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ name: path_provider | |
description: Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories. | ||
repository: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider | ||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 | ||
version: 2.0.5 | ||
version: 2.0.6 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will need a higher version constraint on Flutter SDK. I'm not sure if that means it also needs a minor or major version bump. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh I see, you're using reflection for this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should really have been a minor version update anyway, since this is a change to "add functionality in a backwards compatible manner" (the semver spec description for when to make a minor change), rather than a bug fix. |
||
|
||
environment: | ||
sdk: ">=2.14.0 <3.0.0" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
*Impl
is usually used for the implementation of an interface.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added docstring to clarify its purpose.