Skip to content

Commit

Permalink
fix(android): resolve issue with activity result API registration for…
Browse files Browse the repository at this point in the history
… fragments (#4402)
  • Loading branch information
carlpoole authored Apr 12, 2021
1 parent 7bdcc15 commit ac6c6bc
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 46 deletions.
60 changes: 58 additions & 2 deletions android/capacitor/src/main/java/com/getcapacitor/Bridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@
import android.webkit.ValueCallback;
import android.webkit.WebSettings;
import android.webkit.WebView;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import com.getcapacitor.android.R;
import com.getcapacitor.annotation.CapacitorPlugin;
import com.getcapacitor.annotation.Permission;
Expand Down Expand Up @@ -80,6 +85,8 @@ public class Bridge {

// A reference to the main activity for the app
private final AppCompatActivity context;
// A reference to the containing Fragment if used
private final Fragment fragment;
private WebViewLocalServer localServer;
private String localUrl;
private String appUrl;
Expand Down Expand Up @@ -139,9 +146,23 @@ public Bridge(
PluginManager pluginManager,
CordovaPreferences preferences,
CapConfig config
) {
this(context, null, webView, initialPlugins, cordovaInterface, pluginManager, preferences, config);
}

private Bridge(
AppCompatActivity context,
Fragment fragment,
WebView webView,
List<Class<? extends Plugin>> initialPlugins,
MockCordovaInterfaceImpl cordovaInterface,
PluginManager pluginManager,
CordovaPreferences preferences,
CapConfig config
) {
this.app = new App();
this.context = context;
this.fragment = fragment;
this.webView = webView;
this.webViewClient = new BridgeWebViewClient(this);
this.initialPlugins = initialPlugins;
Expand Down Expand Up @@ -330,6 +351,16 @@ public AppCompatActivity getActivity() {
return this.context;
}

/**
* Get the fragment for the app, if applicable. This will likely be null unless Capacitor
* is being used embedded in a Native Android app.
*
* @return The fragment containing the Capacitor WebView.
*/
public Fragment getFragment() {
return this.fragment;
}

/**
* Get the core WebView under Capacitor's control
* @return
Expand Down Expand Up @@ -699,6 +730,25 @@ protected void savePermissionCall(PluginCall call) {
}
}

/**
* Register an Activity Result Launcher to the containing Fragment or Activity.
*
* @param contract A contract specifying that an activity can be called with an input of
* type I and produce an output of type O.
* @param callback The callback run on Activity Result.
* @return A registered Activity Result Launcher.
*/
public <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultCallback<O> callback
) {
if (fragment != null) {
return fragment.registerForActivityResult(contract, callback);
} else {
return context.registerForActivityResult(contract, callback);
}
}

/**
* Build the JSInjector that will be used to inject JS into files served to the app,
* to ensure that Capacitor's JS and the JS for all the plugins is loaded each time.
Expand Down Expand Up @@ -1140,12 +1190,18 @@ static class Builder {
private CapConfig config = null;
private List<Class<? extends Plugin>> plugins = new ArrayList<>();
private AppCompatActivity activity;
private Fragment fragment;
private final List<WebViewListener> webViewListeners = new ArrayList<>();

Builder(AppCompatActivity activity) {
this.activity = activity;
}

Builder(Fragment fragment) {
this.activity = (AppCompatActivity) fragment.getActivity();
this.fragment = fragment;
}

public Builder setInstanceState(Bundle instanceState) {
this.instanceState = instanceState;
return this;
Expand Down Expand Up @@ -1200,14 +1256,14 @@ public Bridge create() {
cordovaInterface.restoreInstanceState(instanceState);
}

WebView webView = activity.findViewById(R.id.webview);
WebView webView = this.fragment != null ? fragment.getView().findViewById(R.id.webview) : activity.findViewById(R.id.webview);
MockCordovaWebViewImpl mockWebView = new MockCordovaWebViewImpl(activity.getApplicationContext());
mockWebView.init(cordovaInterface, pluginEntries, preferences, webView);
PluginManager pluginManager = mockWebView.getPluginManager();
cordovaInterface.onCordovaInit(pluginManager);

// Bridge initialization
Bridge bridge = new Bridge(activity, webView, plugins, cordovaInterface, pluginManager, preferences, config);
Bridge bridge = new Bridge(activity, fragment, webView, plugins, cordovaInterface, pluginManager, preferences, config);
bridge.setCordovaWebView(mockWebView);
bridge.setWebViewListeners(webViewListeners);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import com.getcapacitor.android.R;
import java.util.ArrayList;
Expand Down Expand Up @@ -57,6 +56,10 @@ public void setConfig(CapConfig config) {
this.config = config;
}

public Bridge getBridge() {
return bridge;
}

public void addWebViewListener(WebViewListener webViewListener) {
webViewListeners.add(webViewListener);
}
Expand All @@ -65,7 +68,7 @@ public void addWebViewListener(WebViewListener webViewListener) {
* Load the WebView and create the Bridge
*/
protected void load(Bundle savedInstanceState) {
Logger.debug("Starting BridgeActivity");
Logger.debug("Loading Bridge with BridgeFragment");

Bundle args = getArguments();
String startDir = null;
Expand All @@ -75,7 +78,7 @@ protected void load(Bundle savedInstanceState) {
}

bridge =
new Bridge.Builder((AppCompatActivity) getActivity())
new Bridge.Builder(this)
.setInstanceState(savedInstanceState)
.setPlugins(initialPlugins)
.setConfig(config)
Expand Down Expand Up @@ -116,8 +119,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
this.load(savedInstanceState);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import android.webkit.WebView;
import android.widget.EditText;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.core.content.FileProvider;
Expand Down Expand Up @@ -53,30 +54,23 @@ private interface ActivityResultListener {

public BridgeWebChromeClient(Bridge bridge) {
this.bridge = bridge;
permissionLauncher =
bridge
.getActivity()
.registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
(Map<String, Boolean> isGranted) -> {
if (permissionListener != null) {
boolean granted = true;
for (Map.Entry<String, Boolean> permission : isGranted.entrySet()) {
if (!permission.getValue()) granted = false;
}
permissionListener.onPermissionSelect(granted);
}
}
);

ActivityResultCallback<Map<String, Boolean>> permissionCallback = (Map<String, Boolean> isGranted) -> {
if (permissionListener != null) {
boolean granted = true;
for (Map.Entry<String, Boolean> permission : isGranted.entrySet()) {
if (!permission.getValue()) granted = false;
}
permissionListener.onPermissionSelect(granted);
}
};

permissionLauncher = bridge.registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), permissionCallback);
activityLauncher =
bridge
.getActivity()
.registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
activityListener.onActivityResult(result);
}
);
bridge.registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> activityListener.onActivityResult(result)
);
}

/**
Expand Down
26 changes: 10 additions & 16 deletions android/capacitor/src/main/java/com/getcapacitor/Plugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,26 +111,20 @@ void initializeActivityLaunchers() {
for (final Method method : pluginClassMethods) {
if (method.isAnnotationPresent(ActivityCallback.class)) {
// register callbacks annotated with ActivityCallback for activity results
activityLaunchers.put(
method.getName(),
bridge
.getActivity()
.registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> triggerActivityCallback(method, result)
)
ActivityResultLauncher<Intent> launcher = bridge.registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> triggerActivityCallback(method, result)
);

activityLaunchers.put(method.getName(), launcher);
} else if (method.isAnnotationPresent(PermissionCallback.class)) {
// register callbacks annotated with PermissionCallback for permission results
permissionLaunchers.put(
method.getName(),
bridge
.getActivity()
.registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
permissions -> triggerPermissionCallback(method, permissions)
)
ActivityResultLauncher<String[]> launcher = bridge.registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
permissions -> triggerPermissionCallback(method, permissions)
);

permissionLaunchers.put(method.getName(), launcher);
}
}
}
Expand Down

0 comments on commit ac6c6bc

Please sign in to comment.