[android_alarm_manager] Added Espresso test for background execution (#2482)
* Added test for android_alarm_manager background execution
Co-authored-by: Collin Jackson <jackson@google.com>
diff --git a/packages/android_alarm_manager/CHANGELOG.md b/packages/android_alarm_manager/CHANGELOG.md
index cf935b5..d04ca96 100644
--- a/packages/android_alarm_manager/CHANGELOG.md
+++ b/packages/android_alarm_manager/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.4.5+5
+
+* Added an Espresso test.
+
## 0.4.5+4
* Make the pedantic dev_dependency explicit.
diff --git a/packages/android_alarm_manager/example/android/app/build.gradle b/packages/android_alarm_manager/example/android/app/build.gradle
index d296caf..f066040 100644
--- a/packages/android_alarm_manager/example/android/app/build.gradle
+++ b/packages/android_alarm_manager/example/android/app/build.gradle
@@ -55,6 +55,8 @@
dependencies {
testImplementation 'junit:junit:4.12'
+ testImplementation "com.google.truth:truth:1.0"
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
+ api 'androidx.test:core:1.2.0'
}
diff --git a/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/BackgroundExecutionTest.java b/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/BackgroundExecutionTest.java
new file mode 100644
index 0000000..ce34b25
--- /dev/null
+++ b/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/BackgroundExecutionTest.java
@@ -0,0 +1,64 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.androidalarmmanagerexample;
+
+import static androidx.test.espresso.Espresso.pressBackUnconditionally;
+import static androidx.test.espresso.flutter.EspressoFlutter.onFlutterWidget;
+import static androidx.test.espresso.flutter.action.FlutterActions.click;
+import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withValueKey;
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.rule.ActivityTestRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BackgroundExecutionTest {
+ private SharedPreferences prefs;
+ static final String COUNT_KEY = "flutter.count";
+
+ @Rule
+ public ActivityTestRule<DriverExtensionActivity> myActivityTestRule =
+ new ActivityTestRule<>(DriverExtensionActivity.class, true, false);
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
+ prefs.edit().putLong(COUNT_KEY, 0).apply();
+
+ ActivityScenario.launch(DriverExtensionActivity.class);
+ }
+
+ @Test
+ public void startBackgroundIsolate() throws Exception {
+
+ // Register a one shot alarm which will go off in ~5 seconds.
+ onFlutterWidget(withValueKey("RegisterOneShotAlarm")).perform(click());
+
+ // The alarm count should be 0 after installation.
+ assertEquals(prefs.getLong(COUNT_KEY, -1), 0);
+
+ // Close the application to background it.
+ pressBackUnconditionally();
+
+ // The alarm should eventually fire, wake up the application, create a
+ // background isolate, and then increment the counter in the shared
+ // preferences. Timeout after 20s, just to be safe.
+ int tries = 0;
+ while ((prefs.getLong(COUNT_KEY, -1) == 0) && (tries < 200)) {
+ Thread.sleep(100);
+ ++tries;
+ }
+ assertEquals(prefs.getLong(COUNT_KEY, -1), 1);
+ }
+}
diff --git a/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/DriverExtensionActivity.java b/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/DriverExtensionActivity.java
new file mode 100644
index 0000000..c51a3c0
--- /dev/null
+++ b/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/DriverExtensionActivity.java
@@ -0,0 +1,15 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.androidalarmmanagerexample;
+
+import androidx.annotation.NonNull;
+
+public class DriverExtensionActivity extends MainActivity {
+ @Override
+ @NonNull
+ public String getDartEntrypointFunctionName() {
+ return "appMain";
+ }
+}
diff --git a/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/MainActivityTest.java b/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/MainActivityTest.java
index 6b69d39..86bb25c 100644
--- a/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/MainActivityTest.java
+++ b/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/MainActivityTest.java
@@ -5,11 +5,13 @@
package io.flutter.plugins.androidalarmmanagerexample;
import androidx.test.rule.ActivityTestRule;
-import dev.flutter.plugins.e2e.FlutterRunner;
+import dev.flutter.plugins.e2e.FlutterTestRunner;
import org.junit.Rule;
import org.junit.runner.RunWith;
-@RunWith(FlutterRunner.class)
+@RunWith(FlutterTestRunner.class)
public class MainActivityTest {
- @Rule public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);
+ @Rule
+ public ActivityTestRule<MainActivity> rule =
+ new ActivityTestRule<>(MainActivity.class, true, false);
}
diff --git a/packages/android_alarm_manager/example/android/app/src/debug/AndroidManifest.xml b/packages/android_alarm_manager/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..e826cdd
--- /dev/null
+++ b/packages/android_alarm_manager/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="io.flutter.plugins.androidalarmmanagerexample">
+ <!-- Flutter needs it to communicate with the running application
+ to allow setting breakpoints, to provide hot reload, etc.
+ -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <application android:usesCleartextTraffic="true">
+ <activity
+ android:name=".DriverExtensionActivity"
+ android:launchMode="singleTop"
+ android:theme="@style/LaunchTheme"
+ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+ android:hardwareAccelerated="true"
+ android:windowSoftInputMode="adjustResize">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java b/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java
index 2c80708..efe9064 100644
--- a/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java
+++ b/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java
@@ -10,6 +10,7 @@
import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry;
import io.flutter.plugins.androidalarmmanager.AndroidAlarmManagerPlugin;
import io.flutter.plugins.pathprovider.PathProviderPlugin;
+import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin;
public class MainActivity extends FlutterActivity {
// TODO(bkonyi): Remove this once v2 of GeneratedPluginRegistrant rolls to stable. https://github.com/flutter/flutter/issues/42694
@@ -18,6 +19,7 @@
ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine);
flutterEngine.getPlugins().add(new AndroidAlarmManagerPlugin());
flutterEngine.getPlugins().add(new E2EPlugin());
+ flutterEngine.getPlugins().add(new SharedPreferencesPlugin());
PathProviderPlugin.registerWith(
shimPluginRegistry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin"));
}
diff --git a/packages/android_alarm_manager/example/lib/main.dart b/packages/android_alarm_manager/example/lib/main.dart
index 12aad9b..4ba6977 100644
--- a/packages/android_alarm_manager/example/lib/main.dart
+++ b/packages/android_alarm_manager/example/lib/main.dart
@@ -4,32 +4,156 @@
// ignore_for_file: public_member_api_docs
-import 'dart:async';
+import 'dart:isolate';
+import 'dart:math';
+import 'dart:ui';
import 'package:android_alarm_manager/android_alarm_manager.dart';
-import 'package:flutter/widgets.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:flutter/material.dart';
-void printMessage(String msg) => print('[${DateTime.now()}] $msg');
+/// The [SharedPreferences] key to access the alarm fire count.
+const String countKey = 'count';
-void printPeriodic() => printMessage("Periodic!");
-void printOneShot() => printMessage("One shot!");
+/// The name associated with the UI isolate's [SendPort].
+const String isolateName = 'isolate';
+
+/// A port used to communicate from a background isolate to the UI isolate.
+final ReceivePort port = ReceivePort();
+
+/// Global [SharedPreferences] object.
+SharedPreferences prefs;
Future<void> main() async {
- final int periodicID = 0;
- final int oneShotID = 1;
-
+ // TODO(bkonyi): uncomment
WidgetsFlutterBinding.ensureInitialized();
- // Start the AlarmManager service.
- await AndroidAlarmManager.initialize();
+ // Register the UI isolate's SendPort to allow for communication from the
+ // background isolate.
+ IsolateNameServer.registerPortWithName(
+ port.sendPort,
+ isolateName,
+ );
+ prefs = await SharedPreferences.getInstance();
+ if (!prefs.containsKey(countKey)) {
+ await prefs.setInt(countKey, 0);
+ }
+ runApp(AlarmManagerExampleApp());
+}
- printMessage("main run");
- runApp(const Center(
- child:
- Text('See device log for output', textDirection: TextDirection.ltr)));
- await AndroidAlarmManager.periodic(
- const Duration(seconds: 5), periodicID, printPeriodic,
- wakeup: true, exact: true);
- await AndroidAlarmManager.oneShot(
- const Duration(seconds: 5), oneShotID, printOneShot);
+/// Example app for Espresso plugin.
+class AlarmManagerExampleApp extends StatelessWidget {
+ // This widget is the root of your application.
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Flutter Demo',
+ home: _AlarmHomePage(title: 'Flutter Demo Home Page'),
+ );
+ }
+}
+
+class _AlarmHomePage extends StatefulWidget {
+ _AlarmHomePage({Key key, this.title}) : super(key: key);
+ final String title;
+
+ @override
+ _AlarmHomePageState createState() => _AlarmHomePageState();
+}
+
+class _AlarmHomePageState extends State<_AlarmHomePage> {
+ int _counter = 0;
+
+ @override
+ void initState() {
+ super.initState();
+ AndroidAlarmManager.initialize();
+
+ // Register for events from the background isolate. These messages will
+ // always coincide with an alarm firing.
+ port.listen((_) async => await _incrementCounter());
+ }
+
+ Future<void> _incrementCounter() async {
+ print('Increment counter!');
+
+ // Ensure we've loaded the updated count from the background isolate.
+ await prefs.reload();
+
+ setState(() {
+ _counter++;
+ });
+ }
+
+ // The background
+ static SendPort uiSendPort;
+
+ // The callback for our alarm
+ static Future<void> callback() async {
+ print('Alarm fired!');
+
+ // Get the previous cached count and increment it.
+ final prefs = await SharedPreferences.getInstance();
+ int currentCount = prefs.getInt(countKey);
+ await prefs.setInt(countKey, currentCount + 1);
+
+ // This will be null if we're running in the background.
+ uiSendPort ??= IsolateNameServer.lookupPortByName(isolateName);
+ uiSendPort?.send(null);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ // TODO(jackson): This has been deprecated and should be replaced
+ // with `headline4` when it's available on all the versions of
+ // Flutter that we test.
+ // ignore: deprecated_member_use
+ final textStyle = Theme.of(context).textTheme.display1;
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(widget.title),
+ ),
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ Text(
+ 'Alarm fired $_counter times',
+ style: textStyle,
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ Text(
+ 'Total alarms fired: ',
+ style: textStyle,
+ ),
+ Text(
+ prefs.getInt(countKey).toString(),
+ key: ValueKey('BackgroundCountText'),
+ style: textStyle,
+ ),
+ ],
+ ),
+ RaisedButton(
+ child: Text(
+ 'Schedule OneShot Alarm',
+ ),
+ key: ValueKey('RegisterOneShotAlarm'),
+ onPressed: () async {
+ await AndroidAlarmManager.oneShot(
+ const Duration(seconds: 5),
+ // Ensure we have a unique alarm ID.
+ Random().nextInt(pow(2, 31)),
+ callback,
+ exact: true,
+ wakeup: true,
+ );
+ },
+ ),
+ ],
+ ),
+ ),
+ );
+ }
}
diff --git a/packages/android_alarm_manager/example/pubspec.yaml b/packages/android_alarm_manager/example/pubspec.yaml
index dbcf2c0..2fc1918 100644
--- a/packages/android_alarm_manager/example/pubspec.yaml
+++ b/packages/android_alarm_manager/example/pubspec.yaml
@@ -6,11 +6,12 @@
sdk: flutter
android_alarm_manager:
path: ../
- e2e: ^0.2.1
+ shared_preferences: ^0.5.6
+ e2e: 0.3.0
path_provider: ^1.3.1
-
dev_dependencies:
+ espresso: ^0.0.1+3
flutter_driver:
sdk: flutter
flutter_test:
diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart
index 8359bfd..a5bc1ac 100644
--- a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart
+++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart
@@ -4,9 +4,11 @@
import 'dart:async';
import 'dart:io';
+import 'package:android_alarm_manager_example/main.dart' as app;
import 'package:android_alarm_manager/android_alarm_manager.dart';
import 'package:e2e/e2e.dart';
import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_driver/driver_extension.dart';
import 'package:path_provider/path_provider.dart';
// From https://flutter.dev/docs/cookbook/persistence/reading-writing-files
@@ -44,14 +46,17 @@
Future<void> incrementCounter() async {
final int value = await readCounter();
- print('incrementCounter to: ${value + 1}');
await writeCounter(value + 1);
}
+void appMain() {
+ enableFlutterDriverExtension();
+ app.main();
+}
+
void main() {
E2EWidgetsFlutterBinding.ensureInitialized();
- print('main');
setUp(() async {
await AndroidAlarmManager.initialize();
});
diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml
index 3efcbb4..7bdf68f 100644
--- a/packages/android_alarm_manager/pubspec.yaml
+++ b/packages/android_alarm_manager/pubspec.yaml
@@ -1,7 +1,7 @@
name: android_alarm_manager
description: Flutter plugin for accessing the Android AlarmManager service, and
running Dart code in the background when alarms fire.
-version: 0.4.5+4
+version: 0.4.5+5
homepage: https://github.com/flutter/plugins/tree/master/packages/android_alarm_manager
dependencies: