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

Commit 6ccebda

Browse files
[android_alarm_manager] Added Espresso test for background execution (#2482)
* Added test for android_alarm_manager background execution Co-authored-by: Collin Jackson <[email protected]>
1 parent 1d7e12e commit 6ccebda

File tree

11 files changed

+268
-28
lines changed

11 files changed

+268
-28
lines changed

packages/android_alarm_manager/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.4.5+5
2+
3+
* Added an Espresso test.
4+
15
## 0.4.5+4
26

37
* Make the pedantic dev_dependency explicit.

packages/android_alarm_manager/example/android/app/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ flutter {
5555

5656
dependencies {
5757
testImplementation 'junit:junit:4.12'
58+
testImplementation "com.google.truth:truth:1.0"
5859
androidTestImplementation 'androidx.test:runner:1.1.1'
5960
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
61+
api 'androidx.test:core:1.2.0'
6062
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2020 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.androidalarmmanagerexample;
6+
7+
import static androidx.test.espresso.Espresso.pressBackUnconditionally;
8+
import static androidx.test.espresso.flutter.EspressoFlutter.onFlutterWidget;
9+
import static androidx.test.espresso.flutter.action.FlutterActions.click;
10+
import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withValueKey;
11+
import static org.junit.Assert.assertEquals;
12+
13+
import android.content.Context;
14+
import android.content.SharedPreferences;
15+
import androidx.test.InstrumentationRegistry;
16+
import androidx.test.core.app.ActivityScenario;
17+
import androidx.test.ext.junit.runners.AndroidJUnit4;
18+
import androidx.test.rule.ActivityTestRule;
19+
import org.junit.Before;
20+
import org.junit.Rule;
21+
import org.junit.Test;
22+
import org.junit.runner.RunWith;
23+
24+
@RunWith(AndroidJUnit4.class)
25+
public class BackgroundExecutionTest {
26+
private SharedPreferences prefs;
27+
static final String COUNT_KEY = "flutter.count";
28+
29+
@Rule
30+
public ActivityTestRule<DriverExtensionActivity> myActivityTestRule =
31+
new ActivityTestRule<>(DriverExtensionActivity.class, true, false);
32+
33+
@Before
34+
public void setUp() throws Exception {
35+
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
36+
prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
37+
prefs.edit().putLong(COUNT_KEY, 0).apply();
38+
39+
ActivityScenario.launch(DriverExtensionActivity.class);
40+
}
41+
42+
@Test
43+
public void startBackgroundIsolate() throws Exception {
44+
45+
// Register a one shot alarm which will go off in ~5 seconds.
46+
onFlutterWidget(withValueKey("RegisterOneShotAlarm")).perform(click());
47+
48+
// The alarm count should be 0 after installation.
49+
assertEquals(prefs.getLong(COUNT_KEY, -1), 0);
50+
51+
// Close the application to background it.
52+
pressBackUnconditionally();
53+
54+
// The alarm should eventually fire, wake up the application, create a
55+
// background isolate, and then increment the counter in the shared
56+
// preferences. Timeout after 20s, just to be safe.
57+
int tries = 0;
58+
while ((prefs.getLong(COUNT_KEY, -1) == 0) && (tries < 200)) {
59+
Thread.sleep(100);
60+
++tries;
61+
}
62+
assertEquals(prefs.getLong(COUNT_KEY, -1), 1);
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.androidalarmmanagerexample;
6+
7+
import androidx.annotation.NonNull;
8+
9+
public class DriverExtensionActivity extends MainActivity {
10+
@Override
11+
@NonNull
12+
public String getDartEntrypointFunctionName() {
13+
return "appMain";
14+
}
15+
}

packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/MainActivityTest.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
package io.flutter.plugins.androidalarmmanagerexample;
66

77
import androidx.test.rule.ActivityTestRule;
8-
import dev.flutter.plugins.e2e.FlutterRunner;
8+
import dev.flutter.plugins.e2e.FlutterTestRunner;
99
import org.junit.Rule;
1010
import org.junit.runner.RunWith;
1111

12-
@RunWith(FlutterRunner.class)
12+
@RunWith(FlutterTestRunner.class)
1313
public class MainActivityTest {
14-
@Rule public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);
14+
@Rule
15+
public ActivityTestRule<MainActivity> rule =
16+
new ActivityTestRule<>(MainActivity.class, true, false);
1517
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2+
package="io.flutter.plugins.androidalarmmanagerexample">
3+
<!-- Flutter needs it to communicate with the running application
4+
to allow setting breakpoints, to provide hot reload, etc.
5+
-->
6+
<uses-permission android:name="android.permission.INTERNET"/>
7+
<application android:usesCleartextTraffic="true">
8+
<activity
9+
android:name=".DriverExtensionActivity"
10+
android:launchMode="singleTop"
11+
android:theme="@style/LaunchTheme"
12+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
13+
android:hardwareAccelerated="true"
14+
android:windowSoftInputMode="adjustResize">
15+
<intent-filter>
16+
<action android:name="android.intent.action.MAIN"/>
17+
<category android:name="android.intent.category.LAUNCHER"/>
18+
</intent-filter>
19+
</activity>
20+
</application>
21+
</manifest>

packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry;
1111
import io.flutter.plugins.androidalarmmanager.AndroidAlarmManagerPlugin;
1212
import io.flutter.plugins.pathprovider.PathProviderPlugin;
13+
import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin;
1314

1415
public class MainActivity extends FlutterActivity {
1516
// TODO(bkonyi): Remove this once v2 of GeneratedPluginRegistrant rolls to stable. https://github.com/flutter/flutter/issues/42694
@@ -18,6 +19,7 @@ public void configureFlutterEngine(FlutterEngine flutterEngine) {
1819
ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine);
1920
flutterEngine.getPlugins().add(new AndroidAlarmManagerPlugin());
2021
flutterEngine.getPlugins().add(new E2EPlugin());
22+
flutterEngine.getPlugins().add(new SharedPreferencesPlugin());
2123
PathProviderPlugin.registerWith(
2224
shimPluginRegistry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin"));
2325
}

packages/android_alarm_manager/example/lib/main.dart

Lines changed: 144 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,156 @@
44

55
// ignore_for_file: public_member_api_docs
66

7-
import 'dart:async';
7+
import 'dart:isolate';
8+
import 'dart:math';
9+
import 'dart:ui';
810

911
import 'package:android_alarm_manager/android_alarm_manager.dart';
10-
import 'package:flutter/widgets.dart';
12+
import 'package:shared_preferences/shared_preferences.dart';
13+
import 'package:flutter/material.dart';
1114

12-
void printMessage(String msg) => print('[${DateTime.now()}] $msg');
15+
/// The [SharedPreferences] key to access the alarm fire count.
16+
const String countKey = 'count';
1317

14-
void printPeriodic() => printMessage("Periodic!");
15-
void printOneShot() => printMessage("One shot!");
18+
/// The name associated with the UI isolate's [SendPort].
19+
const String isolateName = 'isolate';
1620

17-
Future<void> main() async {
18-
final int periodicID = 0;
19-
final int oneShotID = 1;
21+
/// A port used to communicate from a background isolate to the UI isolate.
22+
final ReceivePort port = ReceivePort();
23+
24+
/// Global [SharedPreferences] object.
25+
SharedPreferences prefs;
2026

27+
Future<void> main() async {
28+
// TODO(bkonyi): uncomment
2129
WidgetsFlutterBinding.ensureInitialized();
2230

23-
// Start the AlarmManager service.
24-
await AndroidAlarmManager.initialize();
25-
26-
printMessage("main run");
27-
runApp(const Center(
28-
child:
29-
Text('See device log for output', textDirection: TextDirection.ltr)));
30-
await AndroidAlarmManager.periodic(
31-
const Duration(seconds: 5), periodicID, printPeriodic,
32-
wakeup: true, exact: true);
33-
await AndroidAlarmManager.oneShot(
34-
const Duration(seconds: 5), oneShotID, printOneShot);
31+
// Register the UI isolate's SendPort to allow for communication from the
32+
// background isolate.
33+
IsolateNameServer.registerPortWithName(
34+
port.sendPort,
35+
isolateName,
36+
);
37+
prefs = await SharedPreferences.getInstance();
38+
if (!prefs.containsKey(countKey)) {
39+
await prefs.setInt(countKey, 0);
40+
}
41+
runApp(AlarmManagerExampleApp());
42+
}
43+
44+
/// Example app for Espresso plugin.
45+
class AlarmManagerExampleApp extends StatelessWidget {
46+
// This widget is the root of your application.
47+
@override
48+
Widget build(BuildContext context) {
49+
return MaterialApp(
50+
title: 'Flutter Demo',
51+
home: _AlarmHomePage(title: 'Flutter Demo Home Page'),
52+
);
53+
}
54+
}
55+
56+
class _AlarmHomePage extends StatefulWidget {
57+
_AlarmHomePage({Key key, this.title}) : super(key: key);
58+
final String title;
59+
60+
@override
61+
_AlarmHomePageState createState() => _AlarmHomePageState();
62+
}
63+
64+
class _AlarmHomePageState extends State<_AlarmHomePage> {
65+
int _counter = 0;
66+
67+
@override
68+
void initState() {
69+
super.initState();
70+
AndroidAlarmManager.initialize();
71+
72+
// Register for events from the background isolate. These messages will
73+
// always coincide with an alarm firing.
74+
port.listen((_) async => await _incrementCounter());
75+
}
76+
77+
Future<void> _incrementCounter() async {
78+
print('Increment counter!');
79+
80+
// Ensure we've loaded the updated count from the background isolate.
81+
await prefs.reload();
82+
83+
setState(() {
84+
_counter++;
85+
});
86+
}
87+
88+
// The background
89+
static SendPort uiSendPort;
90+
91+
// The callback for our alarm
92+
static Future<void> callback() async {
93+
print('Alarm fired!');
94+
95+
// Get the previous cached count and increment it.
96+
final prefs = await SharedPreferences.getInstance();
97+
int currentCount = prefs.getInt(countKey);
98+
await prefs.setInt(countKey, currentCount + 1);
99+
100+
// This will be null if we're running in the background.
101+
uiSendPort ??= IsolateNameServer.lookupPortByName(isolateName);
102+
uiSendPort?.send(null);
103+
}
104+
105+
@override
106+
Widget build(BuildContext context) {
107+
// TODO(jackson): This has been deprecated and should be replaced
108+
// with `headline4` when it's available on all the versions of
109+
// Flutter that we test.
110+
// ignore: deprecated_member_use
111+
final textStyle = Theme.of(context).textTheme.display1;
112+
return Scaffold(
113+
appBar: AppBar(
114+
title: Text(widget.title),
115+
),
116+
body: Center(
117+
child: Column(
118+
mainAxisAlignment: MainAxisAlignment.center,
119+
children: <Widget>[
120+
Text(
121+
'Alarm fired $_counter times',
122+
style: textStyle,
123+
),
124+
Row(
125+
mainAxisAlignment: MainAxisAlignment.center,
126+
children: <Widget>[
127+
Text(
128+
'Total alarms fired: ',
129+
style: textStyle,
130+
),
131+
Text(
132+
prefs.getInt(countKey).toString(),
133+
key: ValueKey('BackgroundCountText'),
134+
style: textStyle,
135+
),
136+
],
137+
),
138+
RaisedButton(
139+
child: Text(
140+
'Schedule OneShot Alarm',
141+
),
142+
key: ValueKey('RegisterOneShotAlarm'),
143+
onPressed: () async {
144+
await AndroidAlarmManager.oneShot(
145+
const Duration(seconds: 5),
146+
// Ensure we have a unique alarm ID.
147+
Random().nextInt(pow(2, 31)),
148+
callback,
149+
exact: true,
150+
wakeup: true,
151+
);
152+
},
153+
),
154+
],
155+
),
156+
),
157+
);
158+
}
35159
}

packages/android_alarm_manager/example/pubspec.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ dependencies:
66
sdk: flutter
77
android_alarm_manager:
88
path: ../
9-
e2e: ^0.2.1
9+
shared_preferences: ^0.5.6
10+
e2e: 0.3.0
1011
path_provider: ^1.3.1
1112

12-
1313
dev_dependencies:
14+
espresso: ^0.0.1+3
1415
flutter_driver:
1516
sdk: flutter
1617
flutter_test:

packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
import 'dart:async';
66
import 'dart:io';
7+
import 'package:android_alarm_manager_example/main.dart' as app;
78
import 'package:android_alarm_manager/android_alarm_manager.dart';
89
import 'package:e2e/e2e.dart';
910
import 'package:flutter_test/flutter_test.dart';
11+
import 'package:flutter_driver/driver_extension.dart';
1012
import 'package:path_provider/path_provider.dart';
1113

1214
// From https://flutter.dev/docs/cookbook/persistence/reading-writing-files
@@ -44,14 +46,17 @@ Future<int> readCounter() async {
4446

4547
Future<void> incrementCounter() async {
4648
final int value = await readCounter();
47-
print('incrementCounter to: ${value + 1}');
4849
await writeCounter(value + 1);
4950
}
5051

52+
void appMain() {
53+
enableFlutterDriverExtension();
54+
app.main();
55+
}
56+
5157
void main() {
5258
E2EWidgetsFlutterBinding.ensureInitialized();
5359

54-
print('main');
5560
setUp(() async {
5661
await AndroidAlarmManager.initialize();
5762
});

0 commit comments

Comments
 (0)