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

Commit 93a1c9b

Browse files
authored
[android_intent] Make action optional, as Intents can also resolve with just the component name (#2575)
* An intent takes an action, component or both, but at least one of them. * Extend the tests and enforce the new assertion on the private constructor as well * Extends unit test to cover no-action, but component name case. * Improves unit test even more by writing literal values in the code
1 parent c4d53b6 commit 93a1c9b

File tree

7 files changed

+89
-17
lines changed

7 files changed

+89
-17
lines changed

packages/android_intent/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.3.6
2+
3+
* Marks the `action` parameter as optional
4+
* Adds an assertion to ensure the intent receives an action, component or both.
5+
16
## 0.3.5+1
27

38
* Make the pedantic dev_dependency explicit.

packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/IntentSender.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public IntentSender(@Nullable Activity activity, @Nullable Context applicationCo
4242
* back to {@code applicationContext} and adds {@link Intent#FLAG_ACTIVITY_NEW_TASK} to the intent
4343
* before launching it.
4444
*
45-
* @param action the Intent action, such as {@code ACTION_VIEW}.
45+
* @param action the Intent action, such as {@code ACTION_VIEW} if non-null.
4646
* @param flags forwarded to {@link Intent#addFlags(int)} if non-null.
4747
* @param category forwarded to {@link Intent#addCategory(String)} if non-null.
4848
* @param data forwarded to {@link Intent#setData(Uri)} if non-null and 'type' parameter is null.
@@ -57,7 +57,7 @@ public IntentSender(@Nullable Activity activity, @Nullable Context applicationCo
5757
* Intent#setDataAndType(Uri, String)}
5858
*/
5959
void send(
60-
String action,
60+
@Nullable String action,
6161
@Nullable Integer flags,
6262
@Nullable String category,
6363
@Nullable Uri data,
@@ -70,8 +70,11 @@ void send(
7070
return;
7171
}
7272

73-
Intent intent = new Intent(action);
73+
Intent intent = new Intent();
7474

75+
if (action != null) {
76+
intent.setAction(action);
77+
}
7578
if (flags != null) {
7679
intent.addFlags(flags);
7780
}

packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/MethodCallHandlerImpl.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
9191
}
9292

9393
private static String convertAction(String action) {
94+
if (action == null) {
95+
return null;
96+
}
97+
9498
switch (action) {
9599
case "action_view":
96100
return Intent.ACTION_VIEW;

packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,34 @@ public void onMethodCall_setsComponentName() {
217217
Intent intent = shadowOf((Application) context).getNextStartedActivity();
218218
assertNotNull(intent);
219219
assertNotNull(intent.getComponent());
220-
assertEquals(expectedComponent.getPackageName(), intent.getPackage());
221-
assertEquals(expectedComponent.flattenToString(), intent.getComponent().flattenToString());
220+
assertEquals("foo", intent.getAction());
221+
assertEquals("io.flutter.plugins.androidintent", intent.getPackage());
222+
assertEquals(
223+
"io.flutter.plugins.androidintent/MainActivity", intent.getComponent().flattenToString());
224+
}
225+
226+
@Test
227+
public void onMethodCall_setsOnlyComponentName() {
228+
sender.setApplicationContext(context);
229+
Map<String, Object> args = new HashMap<>();
230+
ComponentName expectedComponent =
231+
new ComponentName("io.flutter.plugins.androidintent", "MainActivity");
232+
args.put("package", expectedComponent.getPackageName());
233+
args.put("componentName", expectedComponent.getClassName());
234+
Result result = mock(Result.class);
235+
ShadowPackageManager shadowPm =
236+
shadowOf(ApplicationProvider.getApplicationContext().getPackageManager());
237+
shadowPm.addActivityIfNotPresent(expectedComponent);
238+
239+
methodCallHandler.onMethodCall(new MethodCall("launch", args), result);
240+
241+
verify(result, times(1)).success(null);
242+
Intent intent = shadowOf((Application) context).getNextStartedActivity();
243+
assertNotNull(intent);
244+
assertNotNull(intent.getComponent());
245+
assertEquals("io.flutter.plugins.androidintent", intent.getPackage());
246+
assertEquals(
247+
"io.flutter.plugins.androidintent/MainActivity", intent.getComponent().flattenToString());
222248
}
223249

224250
@Test

packages/android_intent/lib/android_intent.dart

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class AndroidIntent {
2929
/// If not null, then [package] but also be provided.
3030
/// [type] refers to the type of the intent, can be null.
3131
const AndroidIntent({
32-
@required this.action,
32+
this.action,
3333
this.flags,
3434
this.category,
3535
this.data,
@@ -38,25 +38,28 @@ class AndroidIntent {
3838
this.componentName,
3939
Platform platform,
4040
this.type,
41-
}) : assert(action != null),
41+
}) : assert(action != null || componentName != null,
42+
'action or component (or both) must be specified'),
4243
_channel = const MethodChannel(_kChannelName),
4344
_platform = platform ?? const LocalPlatform();
4445

4546
/// This constructor is only exposed for unit testing. Do not rely on this in
4647
/// app code, it may break without warning.
4748
@visibleForTesting
4849
AndroidIntent.private({
49-
@required this.action,
5050
@required Platform platform,
5151
@required MethodChannel channel,
52+
this.action,
5253
this.flags,
5354
this.category,
5455
this.data,
5556
this.arguments,
5657
this.package,
5758
this.componentName,
5859
this.type,
59-
}) : _channel = channel,
60+
}) : assert(action != null || componentName != null,
61+
'action or component (or both) must be specified'),
62+
_channel = channel,
6063
_platform = platform;
6164

6265
/// This is the general verb that the intent should attempt to do. This
@@ -131,7 +134,10 @@ class AndroidIntent {
131134
if (!_platform.isAndroid) {
132135
return;
133136
}
134-
final Map<String, dynamic> args = <String, dynamic>{'action': action};
137+
final Map<String, dynamic> args = <String, dynamic>{};
138+
if (action != null) {
139+
args['action'] = action;
140+
}
135141
if (flags != null) {
136142
args['flags'] = convertFlags(flags);
137143
}

packages/android_intent/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: android_intent
22
description: Flutter plugin for launching Android Intents. Not supported on iOS.
33
homepage: https://github.com/flutter/plugins/tree/master/packages/android_intent
4-
version: 0.3.5+1
4+
version: 0.3.6
55

66
flutter:
77
plugin:

packages/android_intent/test/android_intent_test.dart

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ void main() {
1515
setUp(() {
1616
mockChannel = MockMethodChannel();
1717
});
18+
1819
group('AndroidIntent', () {
1920
test('pass right params', () async {
2021
androidIntent = AndroidIntent.private(
@@ -32,26 +33,53 @@ void main() {
3233
'type': 'video/*',
3334
}));
3435
});
35-
test('pass null value to action param', () async {
36+
37+
test('raises error if neither an action nor a component is provided', () {
38+
try {
39+
androidIntent = AndroidIntent(data: 'https://flutter.io');
40+
fail('should raise an AssertionError');
41+
} on AssertionError catch (e) {
42+
expect(e.message, 'action or component (or both) must be specified');
43+
} catch (e) {
44+
fail('should raise an AssertionError');
45+
}
46+
});
47+
test('can send Intent with an action and no component', () async {
3648
androidIntent = AndroidIntent.private(
37-
action: null,
38-
channel: mockChannel,
39-
platform: FakePlatform(operatingSystem: 'android'));
49+
action: 'action_view',
50+
channel: mockChannel,
51+
platform: FakePlatform(operatingSystem: 'android'),
52+
);
4053
await androidIntent.launch();
4154
verify(mockChannel.invokeMethod<void>('launch', <String, Object>{
42-
'action': null,
55+
'action': 'action_view',
56+
}));
57+
});
58+
59+
test('can send Intent with a component and no action', () async {
60+
androidIntent = AndroidIntent.private(
61+
package: 'packageName',
62+
componentName: 'componentName',
63+
channel: mockChannel,
64+
platform: FakePlatform(operatingSystem: 'android'),
65+
);
66+
await androidIntent.launch();
67+
verify(mockChannel.invokeMethod<void>('launch', <String, Object>{
68+
'package': 'packageName',
69+
'componentName': 'componentName',
4370
}));
4471
});
4572

4673
test('call in ios platform', () async {
4774
androidIntent = AndroidIntent.private(
48-
action: null,
75+
action: 'action_view',
4976
channel: mockChannel,
5077
platform: FakePlatform(operatingSystem: 'ios'));
5178
await androidIntent.launch();
5279
verifyZeroInteractions(mockChannel);
5380
});
5481
});
82+
5583
group('convertFlags ', () {
5684
androidIntent = const AndroidIntent(
5785
action: 'action_view',

0 commit comments

Comments
 (0)