Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
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
Original file line number Diff line number Diff line change
Expand Up @@ -774,13 +774,18 @@ public String getDartEntrypointFunctionName() {
*
* If both preferences are set, the {@code Intent} preference takes priority.
*
* <p>If none is set, the {@link FlutterActivityAndFragmentDelegate} retrieves the initial route
* from the {@code Intent} through the Intent.getData() instead.
*
* <p>The reason that a {@code <meta-data>} preference is supported is because this {@code
* Activity} might be the very first {@code Activity} launched, which means the developer won't
* have control over the incoming {@code Intent}.
*
* <p>Subclasses may override this method to directly control the initial route.
*
* <p>If this method returns null, the {@link FlutterActivityAndFragmentDelegate} retrieves the
* initial route from the {@code Intent} through the Intent.getData() instead.
*/
@NonNull
public String getInitialRoute() {
if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) {
return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE);
Expand All @@ -792,9 +797,9 @@ public String getInitialRoute() {
Bundle metadata = activityInfo.metaData;
String desiredInitialRoute =
metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
return desiredInitialRoute != null ? desiredInitialRoute : DEFAULT_INITIAL_ROUTE;
return desiredInitialRoute;
} catch (PackageManager.NameNotFoundException e) {
return DEFAULT_INITIAL_ROUTE;
return null;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
Expand Down Expand Up @@ -362,18 +363,21 @@ private void doInitialFlutterViewRun() {
// So this is expected behavior in many cases.
return;
}

String initialRoute = host.getInitialRoute();
if (initialRoute == null) {
initialRoute = getInitialRouteFromIntent(host.getActivity().getIntent());
}
Log.v(
TAG,
"Executing Dart entrypoint: "
+ host.getDartEntrypointFunctionName()
+ ", and sending initial route: "
+ host.getInitialRoute());
+ initialRoute);

// The engine needs to receive the Flutter app's initial route before executing any
// Dart code to ensure that the initial route arrives in time to be applied.
if (host.getInitialRoute() != null) {
flutterEngine.getNavigationChannel().setInitialRoute(host.getInitialRoute());
if (initialRoute != null) {
flutterEngine.getNavigationChannel().setInitialRoute(initialRoute);
}

String appBundlePathOverride = host.getAppBundlePath();
Expand All @@ -388,6 +392,14 @@ private void doInitialFlutterViewRun() {
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
}

private String getInitialRouteFromIntent(Intent intent) {
Uri data = intent.getData();
if (data != null && !data.toString().isEmpty()) {
return data.toString();
}
return null;
}

/**
* Invoke this from {@code Activity#onResume()} or {@code Fragment#onResume()}.
*
Expand Down Expand Up @@ -622,8 +634,12 @@ void onRequestPermissionsResult(
void onNewIntent(@NonNull Intent intent) {
ensureAlive();
if (flutterEngine != null) {
Log.v(TAG, "Forwarding onNewIntent() to FlutterEngine.");
Log.v(TAG, "Forwarding onNewIntent() to FlutterEngine and sending pushRoute message.");
flutterEngine.getActivityControlSurface().onNewIntent(intent);
String initialRoute = getInitialRouteFromIntent(intent);
if (initialRoute != null && !initialRoute.isEmpty()) {
flutterEngine.getNavigationChannel().pushRoute(initialRoute);
}
} else {
Log.w(TAG, "onNewIntent() invoked before FlutterFragment was attached to an Activity.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,13 +646,18 @@ public String getDartEntrypointFunctionName() {
*
* If both preferences are set, the {@code Intent} preference takes priority.
*
* <p>If none is set, the {@link FlutterActivityAndFragmentDelegate} retrieves the initial route
* from the {@code Intent} through the Intent.getData() instead.
*
* <p>The reason that a {@code <meta-data>} preference is supported is because this {@code
* Activity} might be the very first {@code Activity} launched, which means the developer won't
* have control over the incoming {@code Intent}.
*
* <p>Subclasses may override this method to directly control the initial route.
*
* <p>If this method returns null, the {@link FlutterActivityAndFragmentDelegate} retrieves the
* initial route from the {@code Intent} through the Intent.getData() instead.
*/
@NonNull
protected String getInitialRoute() {
if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) {
return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE);
Expand All @@ -664,9 +669,9 @@ protected String getInitialRoute() {
Bundle metadata = activityInfo.metaData;
String desiredInitialRoute =
metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
return desiredInitialRoute != null ? desiredInitialRoute : DEFAULT_INITIAL_ROUTE;
return desiredInitialRoute;
} catch (PackageManager.NameNotFoundException e) {
return DEFAULT_INITIAL_ROUTE;
return null;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import io.flutter.FlutterInjector;
Expand Down Expand Up @@ -43,6 +44,7 @@
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;

@Config(manifest = Config.NONE)
Expand Down Expand Up @@ -426,6 +428,50 @@ public void itForwardsOnRequestPermissionsResultToFlutterEngine() {
.onRequestPermissionsResult(any(Integer.class), any(String[].class), any(int[].class));
}

@Test
public void itSendsInitialRouteFromIntentOnStartIfnoInitialRouteFromActivity() {
Intent intent = FlutterActivity.createDefaultIntent(RuntimeEnvironment.application);
intent.setData(Uri.parse("http://myApp/custom/route"));

ActivityController<FlutterActivity> activityController =
Robolectric.buildActivity(FlutterActivity.class, intent);
FlutterActivity flutterActivity = activityController.get();

when(mockHost.getActivity()).thenReturn(flutterActivity);
when(mockHost.getInitialRoute()).thenReturn(null);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);

// --- Execute the behavior under test ---
// The FlutterEngine is setup in onAttach().
delegate.onAttach(RuntimeEnvironment.application);
// Emulate app start.
delegate.onStart();

// Verify that the navigation channel was given the initial route message.
verify(mockFlutterEngine.getNavigationChannel(), times(1))
.setInitialRoute("http://myApp/custom/route");
}

@Test
public void itSendsPushRouteMessageWhenOnNewIntent() {
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);

// --- Execute the behavior under test ---
// The FlutterEngine is setup in onAttach().
delegate.onAttach(RuntimeEnvironment.application);

Intent mockIntent = mock(Intent.class);
when(mockIntent.getData()).thenReturn(Uri.parse("http://myApp/custom/route"));
// Emulate the host and call the method that we expect to be forwarded.
delegate.onNewIntent(mockIntent);

// Verify that the navigation channel was given the push route message.
verify(mockFlutterEngine.getNavigationChannel(), times(1))
.pushRoute("http://myApp/custom/route");
}

@Test
public void itForwardsOnNewIntentToFlutterEngine() {
// Create the real object that we're testing.
Expand Down