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

[path_provider] started supporting background platform channels #4443

Merged
merged 2 commits into from
Oct 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions packages/path_provider/path_provider/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.0.6

* Added support for Background Platform Channels on Android when it is
available.

## 2.0.5

* Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link

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.

Copy link
Member Author

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.

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 {
Copy link

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

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. 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Expand All @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion packages/path_provider/path_provider/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh I see, you're using reflection for this.

Copy link
Contributor

Choose a reason for hiding this comment

The 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"
Expand Down