Фоновый обработчик
FCM не может вызывать специфичный для платформы код, зарегистрированный в MainActivity.java
(следуя инструкции flutter ).Есть ли способ сделать эту работу?Я новичок в разработке флаттера, Java и Android, мой поиск зашел в тупик.
Могу ли я принудительно открыть приложение с установленным флагом, проверить этот флаг и затем вызвать метод?
Существует ли какая-либо система событий / сообщений, на которую я мог бы подписаться и отправить из фонового режима?
Следующий код приводит к следующему выводу:
I/flutter ( 6448): BACKGROUND_HANDLER::
I/flutter ( 6448): TRIGGERING ALARM::BEFORE
I/flutter ( 6448): TRIGGERING ALARM::AFTER
E/flutter ( 6448): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: MissingPluginException(No implementation found for method setAlarm on channel com.hybridalert.flutter_hybrid/alarm)
E/flutter ( 6448): #0 MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:314:7)
E/flutter ( 6448): <asynchronous suspension>
E/flutter ( 6448): #1 triggerAlarm (package:flutter_hybrid_alert/src/ui/home.dart:30:37)
E/flutter ( 6448): <asynchronous suspension>
E/flutter ( 6448): #2 backgroundHandle (package:flutter_hybrid_alert/src/ui/home.dart:67:5)
E/flutter ( 6448): #3 _fcmSetupBackgroundChannel.<anonymous closure> (package:firebase_messaging/firebase_messaging.dart:38:30)
E/flutter ( 6448): #4 _AsyncAwaitCompleter.start (dart:async-patch/async_patch.dart:43:6)
E/flutter ( 6448): #5 _fcmSetupBackgroundChannel.<anonymous closure> (package:firebase_messaging/firebase_messaging.dart:31:42)
E/flutter ( 6448): #6 MethodChannel._handleAsMethodCall (package:flutter/src/services/platform_channel.dart:397:55)
E/flutter ( 6448): #7 _AsyncAwaitCompleter.start (dart:async-patch/async_patch.dart:43:6)
E/flutter ( 6448): #8 MethodChannel._handleAsMethodCall (package:flutter/src/services/platform_channel.dart:394:39)
E/flutter ( 6448): #9 MethodChannel.setMethodCallHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:365:54)
E/flutter ( 6448): #10 _DefaultBinaryMessenger.handlePlatformMessage (package:flutter/src/services/binary_messenger.dart:110:33)
E/flutter ( 6448): #11 _AsyncAwaitCompleter.start (dart:async-patch/async_patch.dart:43:6)
E/flutter ( 6448): #12 _DefaultBinaryMessenger.handlePlatformMessage (package:flutter/src/services/binary_messenger.dart:101:37)
E/flutter ( 6448): #13 _invoke3.<anonymous closure> (dart:ui/hooks.dart:293:15)
E/flutter ( 6448): #14 _rootRun (dart:async/zone.dart:1124:13)
E/flutter ( 6448): #15 _CustomZone.run (dart:async/zone.dart:1021:19)
E/flutter ( 6448): #16 _CustomZone.runGuarded (dart:async/zone.dart:923:7)
E/flutter ( 6448): #17 _invoke3 (dart:ui/hooks.dart:292:10)
E/flutter ( 6448): #18 _dispatchPlatformMessage (dart:ui/hooks.dart:154:5)
E/flutter ( 6448):
home.dart
import "package:cloud_firestore/cloud_firestore.dart";
import "package:firebase_messaging/firebase_messaging.dart";
import "package:flutter/foundation.dart";
import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_hybrid_alert/src/api/firestore.dart";
import "package:flutter_hybrid_alert/src/api/handlers.dart";
import "package:flutter_hybrid_alert/src/api/storage.dart";
import "package:flutter_hybrid_alert/src/models/models.dart";
import "package:flutter_hybrid_alert/src/routes.dart";
import "package:flutter_hybrid_alert/src/store/actions/auth_actions.dart";
import "package:flutter_hybrid_alert/src/store/actions/blacklist_actions.dart";
import "package:flutter_hybrid_alert/src/store/actions/user_actions.dart";
import "package:flutter_hybrid_alert/src/store/app_state.dart";
import "package:flutter_hybrid_alert/src/store/reducers/auth_reducer.dart";
import "package:flutter_hybrid_alert/src/ui/login.dart";
import "package:flutter_hybrid_alert/src/widgets/indicators/app_loading.dart";
import "package:flutter_local_notifications/flutter_local_notifications.dart";
import "package:flutter_redux/flutter_redux.dart";
import "package:nested_navigators/nested_nav_item.dart";
import "package:nested_navigators/nested_navigators.dart";
import "package:redux/redux.dart";
enum NestedNavItemKey { blacklist, recent, history, settings }
const platform = const MethodChannel("com.hybridalert.flutter_hybrid/alarm");
Future<void> triggerAlarm() async {
try {
final bool res = await platform.invokeMethod("setAlarm");
print(res ? "Alarm triggered successfully." : "Alarm trigger failed.");
} on PlatformException catch (e) {
print("Error triggering alarm: \"${e.message}\".");
}
}
/// showNotification - Init local notifications and show a new one.
void showNotification(String body) {
final androidInitSettings = AndroidInitializationSettings("@mipmap/ic_launcher");
final initSettings = InitializationSettings(androidInitSettings, null);
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
flutterLocalNotificationsPlugin.initialize(initSettings);
const String notificationChannel = "HybridAlert";
final androidPlatformChannelSpecifics = AndroidNotificationDetails(
notificationChannel,
notificationChannel,
"Alerts for Hybrid platform.",
importance: Importance.Max,
priority: Priority.High,
ticker: "ticker",
);
final platformChannelSpecifics = NotificationDetails(androidPlatformChannelSpecifics, null);
flutterLocalNotificationsPlugin.show(0, notificationChannel, body, platformChannelSpecifics);
}
Future<dynamic> backgroundHandle(Map<String, dynamic> message) {
print("BACKGROUND_HANDLER::");
if (message.containsKey("data")) {
// Handle data message
final dynamic data = message["data"];
showNotification("BG:: " + data["name"]);
// THIS THROWS THE ERROR
print("TRIGGERING ALARM::BEFORE");
triggerAlarm();
print("TRIGGERING ALARM::AFTER");
}
return null;
}
/// handleNotifications - Force local notifications.
Future<dynamic> handleNotifications(Map<String, dynamic> message) async {
print("NOTIFICATION_HANDLER::");
if (message.containsKey("data")) {
final dynamic data = message["data"];
return showNotification("FG:: " + data["name"]);
}
final dynamic body = message["notification"]["body"];
return showNotification(body);
}
Future<void> initFCM() async {
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
_firebaseMessaging.requestNotificationPermissions();
return _firebaseMessaging.getToken().then((token) {
print("TOKEN:: $token");
return handleSetUserToken(token);
}).then((_) {
return _firebaseMessaging.configure(
onMessage: handleNotifications,
onBackgroundMessage: backgroundHandle,
);
});
}
class Home extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _HomeState();
}
}
triggerMainThread() async {
try {
final bool res = await platform.invokeMethod("setAlarm");
print(res ? "Alarm triggered successfully." : "Alarm trigger failed.");
} on PlatformException catch (e) {
print("Error triggering alarm: \"${e.message}\".");
}
}
class _HomeState extends State<Home> {
AppUserSettings settings;
String userId;
Store store;
@override
void initState() {
super.initState();
// THIS WORKS FINE
print("TRY_MAIN::BEFORE");
triggerMainThread();
print("TRY_MAIN::AFTER");
initFCM();
if (userId == null) {
getStorageUser().then((String id) {
store.dispatch(SetUserFBEmailAction(id));
setState(() {
userId = id;
});
});
}
}
@override
Widget build(BuildContext context) {
store = StoreProvider.of<AppState>(context);
final AppState state = store.state;
final Stream<DocumentSnapshot> userStream =
userId != null ? usersRef.document(userId).get().asStream() : null;
return StreamBuilder(
stream: userStream,
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData || (state.loading.settings && state.auth.hybridAuthenticated)) {
return buildAppLoadingIndicator("Loading settings...");
}
/* Initialize User Store Data
======================================== */
// Set hybrid email in store from firebase
final AppUser appUser = AppUser.fromJson(snapshot.data.data);
final List<String> stateBlacklist = state.blacklist;
final List<String> blacklist = appUser.settings.blacklist;
if (blacklist != null) {
if (stateBlacklist != null) {
// Compare the two
final bool listsEqual = listEquals(blacklist, stateBlacklist);
if (!listsEqual) {
store.dispatch(SetBlacklistAction(blacklist));
}
} else {
store.dispatch(SetBlacklistAction(blacklist));
}
}
final String hybridEmail = appUser.email;
final String stateHybridEmail = state.user.hybridEmail;
final bool hybridEmailChanged = hybridEmail != stateHybridEmail;
if (hybridEmailChanged) {
store.dispatch(SetUserHybridEmailAction(hybridEmail));
}
// Set firebase userId as fbEmail.
if (state.user.fbEmail == null) {
store.dispatch(SetUserFBEmailAction(userId));
}
// Build the Settings UI.
return NestedNavigators(
items: {
NestedNavItemKey.recent: NestedNavigatorItem(
initialRoute: Routes.recent,
icon: Icons.home,
text: "Recent",
),
NestedNavItemKey.history: NestedNavigatorItem(
initialRoute: Routes.history,
icon: Icons.history,
text: "History",
),
NestedNavItemKey.blacklist: NestedNavigatorItem(
initialRoute: Routes.blacklist,
icon: Icons.list,
text: "Blacklist",
),
NestedNavItemKey.settings: NestedNavigatorItem(
initialRoute: Routes.settings,
icon: Icons.settings,
text: "Settings",
),
},
generateRoute: (routeSettings) {
return Routes.generateRoute(routeSettings, store.state);
},
buildBottomNavigationItem: (key, item, selected) => BottomNavigationBarItem(
icon: Icon(
item.icon,
// color: Colors.blue,
),
title: Text(
item.text,
style: TextStyle(
fontSize: 18,
),
),
),
bottomNavigationBarTheme: Theme.of(context).copyWith(
splashColor: Colors.grey,
),
);
},
);
}
}
class AppHome extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return AppHomeState();
}
}
class AppHomeState extends State {
String hybridToken;
@override
void initState() {
super.initState();
getStorageToken().then((token) {
setState(() {
hybridToken = token;
});
});
}
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, AppState>(
converter: (store) => store.state,
builder: (BuildContext context, AppState state) {
final AuthState authState = state.auth;
final bool firebaseAuth = authState.firebaseAuthenticated;
final bool hybridAuth = authState.hybridAuthenticated;
if (!hybridAuth) {
if (hybridToken != null && hybridToken != "") {
final AuthState newState = AuthState(true, true);
store.dispatch(SetAuthAction(newState));
}
}
final bool isLoggedIn = hybridAuth && firebaseAuth;
if (isLoggedIn) {
return Home();
}
return LoginScreen();
},
);
}
}
MainActivity.java
package com.hybridalert.flutter_hybrid_alert;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.hybridalert.flutter_hybrid/alarm";
// I'm aware this is the default code from the flutter tutorial. Just testing at this point.
private int setAlarm() {
int batteryLevel = -1;
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
} else {
Intent intent = new ContextWrapper(getApplicationContext()).
registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
}
return batteryLevel;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
System.out.println("CALL_METHOD::" + call.method);
// Note: this method is invoked on the main thread.
if (call.method.equals("setAlarm")) {
int batteryLevel = setAlarm();
if (batteryLevel != -1) {
result.success(true);
} else {
result.error("UNAVAILABLE", "Battery level not available.", null);
}
} else {
result.notImplemented();
}
}
});
}
}