Skip to content

Commit b0a68cd

Browse files
chunhtaichaselatta
authored andcommitted
support uri intent launcher in android (flutter#21275)
* support uri intent launcher in android * fix comment
1 parent 2aee13e commit b0a68cd

File tree

4 files changed

+83
-11
lines changed

4 files changed

+83
-11
lines changed

shell/platform/android/io/flutter/embedding/android/FlutterActivity.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -774,13 +774,18 @@ public String getDartEntrypointFunctionName() {
774774
*
775775
* If both preferences are set, the {@code Intent} preference takes priority.
776776
*
777+
* <p>If none is set, the {@link FlutterActivityAndFragmentDelegate} retrieves the initial route
778+
* from the {@code Intent} through the Intent.getData() instead.
779+
*
777780
* <p>The reason that a {@code <meta-data>} preference is supported is because this {@code
778781
* Activity} might be the very first {@code Activity} launched, which means the developer won't
779782
* have control over the incoming {@code Intent}.
780783
*
781784
* <p>Subclasses may override this method to directly control the initial route.
785+
*
786+
* <p>If this method returns null, the {@link FlutterActivityAndFragmentDelegate} retrieves the
787+
* initial route from the {@code Intent} through the Intent.getData() instead.
782788
*/
783-
@NonNull
784789
public String getInitialRoute() {
785790
if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) {
786791
return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE);
@@ -792,9 +797,9 @@ public String getInitialRoute() {
792797
Bundle metadata = activityInfo.metaData;
793798
String desiredInitialRoute =
794799
metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
795-
return desiredInitialRoute != null ? desiredInitialRoute : DEFAULT_INITIAL_ROUTE;
800+
return desiredInitialRoute;
796801
} catch (PackageManager.NameNotFoundException e) {
797-
return DEFAULT_INITIAL_ROUTE;
802+
return null;
798803
}
799804
}
800805

shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import android.app.Activity;
1010
import android.content.Context;
1111
import android.content.Intent;
12+
import android.net.Uri;
1213
import android.os.Build;
1314
import android.os.Bundle;
1415
import android.view.LayoutInflater;
@@ -362,18 +363,21 @@ private void doInitialFlutterViewRun() {
362363
// So this is expected behavior in many cases.
363364
return;
364365
}
365-
366+
String initialRoute = host.getInitialRoute();
367+
if (initialRoute == null) {
368+
initialRoute = getInitialRouteFromIntent(host.getActivity().getIntent());
369+
}
366370
Log.v(
367371
TAG,
368372
"Executing Dart entrypoint: "
369373
+ host.getDartEntrypointFunctionName()
370374
+ ", and sending initial route: "
371-
+ host.getInitialRoute());
375+
+ initialRoute);
372376

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

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

395+
private String getInitialRouteFromIntent(Intent intent) {
396+
Uri data = intent.getData();
397+
if (data != null && !data.toString().isEmpty()) {
398+
return data.toString();
399+
}
400+
return null;
401+
}
402+
391403
/**
392404
* Invoke this from {@code Activity#onResume()} or {@code Fragment#onResume()}.
393405
*
@@ -622,8 +634,12 @@ void onRequestPermissionsResult(
622634
void onNewIntent(@NonNull Intent intent) {
623635
ensureAlive();
624636
if (flutterEngine != null) {
625-
Log.v(TAG, "Forwarding onNewIntent() to FlutterEngine.");
637+
Log.v(TAG, "Forwarding onNewIntent() to FlutterEngine and sending pushRoute message.");
626638
flutterEngine.getActivityControlSurface().onNewIntent(intent);
639+
String initialRoute = getInitialRouteFromIntent(intent);
640+
if (initialRoute != null && !initialRoute.isEmpty()) {
641+
flutterEngine.getNavigationChannel().pushRoute(initialRoute);
642+
}
627643
} else {
628644
Log.w(TAG, "onNewIntent() invoked before FlutterFragment was attached to an Activity.");
629645
}

shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -646,13 +646,18 @@ public String getDartEntrypointFunctionName() {
646646
*
647647
* If both preferences are set, the {@code Intent} preference takes priority.
648648
*
649+
* <p>If none is set, the {@link FlutterActivityAndFragmentDelegate} retrieves the initial route
650+
* from the {@code Intent} through the Intent.getData() instead.
651+
*
649652
* <p>The reason that a {@code <meta-data>} preference is supported is because this {@code
650653
* Activity} might be the very first {@code Activity} launched, which means the developer won't
651654
* have control over the incoming {@code Intent}.
652655
*
653656
* <p>Subclasses may override this method to directly control the initial route.
657+
*
658+
* <p>If this method returns null, the {@link FlutterActivityAndFragmentDelegate} retrieves the
659+
* initial route from the {@code Intent} through the Intent.getData() instead.
654660
*/
655-
@NonNull
656661
protected String getInitialRoute() {
657662
if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) {
658663
return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE);
@@ -664,9 +669,9 @@ protected String getInitialRoute() {
664669
Bundle metadata = activityInfo.metaData;
665670
String desiredInitialRoute =
666671
metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
667-
return desiredInitialRoute != null ? desiredInitialRoute : DEFAULT_INITIAL_ROUTE;
672+
return desiredInitialRoute;
668673
} catch (PackageManager.NameNotFoundException e) {
669-
return DEFAULT_INITIAL_ROUTE;
674+
return null;
670675
}
671676
}
672677

shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import android.app.Activity;
1616
import android.content.Context;
1717
import android.content.Intent;
18+
import android.net.Uri;
1819
import androidx.annotation.NonNull;
1920
import androidx.lifecycle.Lifecycle;
2021
import io.flutter.FlutterInjector;
@@ -43,6 +44,7 @@
4344
import org.robolectric.Robolectric;
4445
import org.robolectric.RobolectricTestRunner;
4546
import org.robolectric.RuntimeEnvironment;
47+
import org.robolectric.android.controller.ActivityController;
4648
import org.robolectric.annotation.Config;
4749

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

431+
@Test
432+
public void itSendsInitialRouteFromIntentOnStartIfnoInitialRouteFromActivity() {
433+
Intent intent = FlutterActivity.createDefaultIntent(RuntimeEnvironment.application);
434+
intent.setData(Uri.parse("http://myApp/custom/route"));
435+
436+
ActivityController<FlutterActivity> activityController =
437+
Robolectric.buildActivity(FlutterActivity.class, intent);
438+
FlutterActivity flutterActivity = activityController.get();
439+
440+
when(mockHost.getActivity()).thenReturn(flutterActivity);
441+
when(mockHost.getInitialRoute()).thenReturn(null);
442+
// Create the real object that we're testing.
443+
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
444+
445+
// --- Execute the behavior under test ---
446+
// The FlutterEngine is setup in onAttach().
447+
delegate.onAttach(RuntimeEnvironment.application);
448+
// Emulate app start.
449+
delegate.onStart();
450+
451+
// Verify that the navigation channel was given the initial route message.
452+
verify(mockFlutterEngine.getNavigationChannel(), times(1))
453+
.setInitialRoute("http://myApp/custom/route");
454+
}
455+
456+
@Test
457+
public void itSendsPushRouteMessageWhenOnNewIntent() {
458+
// Create the real object that we're testing.
459+
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
460+
461+
// --- Execute the behavior under test ---
462+
// The FlutterEngine is setup in onAttach().
463+
delegate.onAttach(RuntimeEnvironment.application);
464+
465+
Intent mockIntent = mock(Intent.class);
466+
when(mockIntent.getData()).thenReturn(Uri.parse("http://myApp/custom/route"));
467+
// Emulate the host and call the method that we expect to be forwarded.
468+
delegate.onNewIntent(mockIntent);
469+
470+
// Verify that the navigation channel was given the push route message.
471+
verify(mockFlutterEngine.getNavigationChannel(), times(1))
472+
.pushRoute("http://myApp/custom/route");
473+
}
474+
429475
@Test
430476
public void itForwardsOnNewIntentToFlutterEngine() {
431477
// Create the real object that we're testing.

0 commit comments

Comments
 (0)