如何使用 GetX 和顶级回调处理程序在 Flutter 中路由到不同的屏幕?
How do I route to a different screen, in Flutter, by using GetX and a top level Callback Handler?
简介
为了在Flutter中路由到不同的屏幕,使用了GetX包来简化contextless路由。需要无上下文路由,因为切换到不同屏幕需要能够在未在 Widget 中使用的顶级回调处理程序中发生,因此没有 BuildContext。我使用的回调处理程序源自 caller: ^0.0.4
包,如果 phone 调用结束,主页需要路由到不同的屏幕。我在 Android Studio Arctic Fox 中使用 Android 模拟器 | 2020.3.1 补丁 3,在 Windows 10 桌面上。
程序流程和错误
启动时,出现主页。然后用户在应用程序中发出调用,并由 flutter_phone_direct_caller: ^2.1.0
和 contacts_service: ^0.6.3
包在单独的文件中处理到 main.dart。当 phone 通话结束时,我希望向控制台打印一条语句,其中包括联系人号码和通话持续时间,以及完成到不同屏幕的路线。打印语句正常运行(因此回调处理程序初始化工作),但是,路由没有发生并且出现以下错误消息:
E/flutter (20421): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: You are trying to use contextless navigation without
E/flutter (20421): a GetMaterialApp or Get.key.
E/flutter (20421): If you are testing your app, you can use:
E/flutter (20421): [Get.testMode = true], or if you are running your app on
E/flutter (20421): a physical device or emulator, you must exchange your [MaterialApp]
E/flutter (20421): for a [GetMaterialApp].
E/flutter (20421):
E/flutter (20421): #0 GetNavigation.global (package:get/get_navigation/src/extension_navigation.dart:1094:7)
E/flutter (20421): #1 GetNavigation.toNamed (package:get/get_navigation/src/extension_navigation.dart:592:12)
E/flutter (20421): #2 callerCallbackHandler (package:phone_app/main.dart:22:11)
E/flutter (20421): #3 _callbackDispatcher.<anonymous closure> (package:caller/caller.dart:129:19)
E/flutter (20421): #4 _callbackDispatcher.<anonymous closure> (package:caller/caller.dart:99:43)
E/flutter (20421): #5 MethodChannel._handleAsMethodCall (package:flutter/src/services/platform_channel.dart:386:55)
E/flutter (20421): #6 MethodChannel.setMethodCallHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:379:34)
E/flutter (20421): #7 _DefaultBinaryMessenger.setMessageHandler.<anonymous closure> (package:flutter/src/services/binding.dart:379:35)
E/flutter (20421): #8 _DefaultBinaryMessenger.setMessageHandler.<anonymous closure> (package:flutter/src/services/binding.dart:376:46)
E/flutter (20421): #9 _invoke2.<anonymous closure> (dart:ui/hooks.dart:205:15)
E/flutter (20421): #10 _rootRun (dart:async/zone.dart:1428:13)
E/flutter (20421): #11 _CustomZone.run (dart:async/zone.dart:1328:19)
E/flutter (20421): #12 _CustomZone.runGuarded (dart:async/zone.dart:1236:7)
E/flutter (20421): #13 _invoke2 (dart:ui/hooks.dart:204:10)
E/flutter (20421): #14 _ChannelCallbackRecord.invoke (dart:ui/channel_buffers.dart:42:5)
E/flutter (20421): #15 _Channel.push (dart:ui/channel_buffers.dart:132:31)
E/flutter (20421): #16 ChannelBuffers.push (dart:ui/channel_buffers.dart:329:17)
E/flutter (20421): #17 PlatformDispatcher._dispatchPlatformMessage (dart:ui/platform_dispatcher.dart:544:22)
E/flutter (20421): #18 _dispatchPlatformMessage (dart:ui/hooks.dart:92:31)
尝试的解决方案
我 am 使用 GetMaterialApp 进行无上下文导航,所以我怀疑问题可能是回调处理程序在返回 GetMaterialApp 之前被调用,这导致 Navigator 不可用尚未初始化。出于这个原因,我尝试仅在 GetMaterialApp Widget 完成构建后调用路由:WidgetsBinding.instance?.addPostFrameCallback((timeStamp) => Get.toNamed('/testScreen'));
。我已确保通过以下语句创建了 WidgetsBinding:WidgetsFlutterBinding.ensureInitialized();
。在前一种情况下:路由永远不会发生,我认为这是由于 WidgetsBinding 的实例为空,并且没有出现错误消息。我不知道如何让回调处理程序仅在构建完成后才执行代码,假设这是导致上述错误的问题。我还没有看到在顶级回调处理程序中实现 GetX 路由的其他代码案例。
示例代码
我的 main.dart 文件的内容如下所示,其中包括主要功能、MyApp class 及其构建的 Widget 和命名路由、回调处理程序的初始化和回调处理程序本身:
import 'package:caller/caller.dart';
import 'package:flutter/material.dart';
import 'package:phone_app/screens/home.dart';
import 'package:phone_app/screens/test_screen.dart';
import 'package:get/get.dart';
/// Defines a callback that will handle all background incoming events
///
/// The duration will only have a value if the current event is `CallerEvent.callEnded`
Future<void> callerCallbackHandler(
CallerEvent event,
String number,
int? duration,
) async {
print("New event received from native $event");
switch (event) {
case CallerEvent.callEnded:
print('[ Caller ] Ended a call with number $number and duration $duration');
Get.toNamed('/testScreen');
break;
case CallerEvent.onMissedCall:
print('[ Caller ] Missed a call from number $number');
break;
case CallerEvent.onIncomingCallAnswered:
print('[ Caller ] Accepted call from number $number');
break;
case CallerEvent.onIncomingCallReceived:
print('[ Caller ] Phone is ringing with number $number');
break;
}
}
Future<void> initialize() async {
/// Check if the user has granted permissions
final permission = await Caller.checkPermission();
print('Caller permission $permission');
/// If not, then request user permission to access the Call State
if (!permission) {
Caller.requestPermissions();
} else {
Caller.initialize(callerCallbackHandler);
}
}
void main() {
WidgetsFlutterBinding.ensureInitialized(); //used when Flutter needs to call native code before calling runApp (sets up internal state of MethodChannels)
initialize(); //checks for and requests call prompt permissions
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return GetMaterialApp( //MaterialApp is a child of GetMaterial app (which is used to display dialogs, routes, snackbars, etc. without passing a context)
debugShowCheckedModeBanner: false,
title: 'Phone calls',
theme: ThemeData(
primarySwatch: Colors.purple,
),
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => const MyHomePage(title: 'Phone Calls')
),
GetPage(
name: '/testScreen',
page: () => const TestScreen()
),
],
);
}
}
经过仔细检查,caller: ^0.0.4
包使用了一个与主隔离区分开的 Dart 隔离区,因此 callerCallbackHandler
方法在应用程序的上下文之外运行。这使得无法更新应用程序状态或执行 UI 影响逻辑。作为一种解决方法,我决定实现一个原生的 Android BroadcastReceiver
来获取不断变化的 phone 状态并路由到指定的屏幕。如果 Flutter 应用程序当前关闭,BroadcastReceiver
也会以编程方式启动它。
简介
为了在Flutter中路由到不同的屏幕,使用了GetX包来简化contextless路由。需要无上下文路由,因为切换到不同屏幕需要能够在未在 Widget 中使用的顶级回调处理程序中发生,因此没有 BuildContext。我使用的回调处理程序源自 caller: ^0.0.4
包,如果 phone 调用结束,主页需要路由到不同的屏幕。我在 Android Studio Arctic Fox 中使用 Android 模拟器 | 2020.3.1 补丁 3,在 Windows 10 桌面上。
程序流程和错误
启动时,出现主页。然后用户在应用程序中发出调用,并由 flutter_phone_direct_caller: ^2.1.0
和 contacts_service: ^0.6.3
包在单独的文件中处理到 main.dart。当 phone 通话结束时,我希望向控制台打印一条语句,其中包括联系人号码和通话持续时间,以及完成到不同屏幕的路线。打印语句正常运行(因此回调处理程序初始化工作),但是,路由没有发生并且出现以下错误消息:
E/flutter (20421): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: You are trying to use contextless navigation without
E/flutter (20421): a GetMaterialApp or Get.key.
E/flutter (20421): If you are testing your app, you can use:
E/flutter (20421): [Get.testMode = true], or if you are running your app on
E/flutter (20421): a physical device or emulator, you must exchange your [MaterialApp]
E/flutter (20421): for a [GetMaterialApp].
E/flutter (20421):
E/flutter (20421): #0 GetNavigation.global (package:get/get_navigation/src/extension_navigation.dart:1094:7)
E/flutter (20421): #1 GetNavigation.toNamed (package:get/get_navigation/src/extension_navigation.dart:592:12)
E/flutter (20421): #2 callerCallbackHandler (package:phone_app/main.dart:22:11)
E/flutter (20421): #3 _callbackDispatcher.<anonymous closure> (package:caller/caller.dart:129:19)
E/flutter (20421): #4 _callbackDispatcher.<anonymous closure> (package:caller/caller.dart:99:43)
E/flutter (20421): #5 MethodChannel._handleAsMethodCall (package:flutter/src/services/platform_channel.dart:386:55)
E/flutter (20421): #6 MethodChannel.setMethodCallHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:379:34)
E/flutter (20421): #7 _DefaultBinaryMessenger.setMessageHandler.<anonymous closure> (package:flutter/src/services/binding.dart:379:35)
E/flutter (20421): #8 _DefaultBinaryMessenger.setMessageHandler.<anonymous closure> (package:flutter/src/services/binding.dart:376:46)
E/flutter (20421): #9 _invoke2.<anonymous closure> (dart:ui/hooks.dart:205:15)
E/flutter (20421): #10 _rootRun (dart:async/zone.dart:1428:13)
E/flutter (20421): #11 _CustomZone.run (dart:async/zone.dart:1328:19)
E/flutter (20421): #12 _CustomZone.runGuarded (dart:async/zone.dart:1236:7)
E/flutter (20421): #13 _invoke2 (dart:ui/hooks.dart:204:10)
E/flutter (20421): #14 _ChannelCallbackRecord.invoke (dart:ui/channel_buffers.dart:42:5)
E/flutter (20421): #15 _Channel.push (dart:ui/channel_buffers.dart:132:31)
E/flutter (20421): #16 ChannelBuffers.push (dart:ui/channel_buffers.dart:329:17)
E/flutter (20421): #17 PlatformDispatcher._dispatchPlatformMessage (dart:ui/platform_dispatcher.dart:544:22)
E/flutter (20421): #18 _dispatchPlatformMessage (dart:ui/hooks.dart:92:31)
尝试的解决方案
我 am 使用 GetMaterialApp 进行无上下文导航,所以我怀疑问题可能是回调处理程序在返回 GetMaterialApp 之前被调用,这导致 Navigator 不可用尚未初始化。出于这个原因,我尝试仅在 GetMaterialApp Widget 完成构建后调用路由:WidgetsBinding.instance?.addPostFrameCallback((timeStamp) => Get.toNamed('/testScreen'));
。我已确保通过以下语句创建了 WidgetsBinding:WidgetsFlutterBinding.ensureInitialized();
。在前一种情况下:路由永远不会发生,我认为这是由于 WidgetsBinding 的实例为空,并且没有出现错误消息。我不知道如何让回调处理程序仅在构建完成后才执行代码,假设这是导致上述错误的问题。我还没有看到在顶级回调处理程序中实现 GetX 路由的其他代码案例。
示例代码
我的 main.dart 文件的内容如下所示,其中包括主要功能、MyApp class 及其构建的 Widget 和命名路由、回调处理程序的初始化和回调处理程序本身:
import 'package:caller/caller.dart';
import 'package:flutter/material.dart';
import 'package:phone_app/screens/home.dart';
import 'package:phone_app/screens/test_screen.dart';
import 'package:get/get.dart';
/// Defines a callback that will handle all background incoming events
///
/// The duration will only have a value if the current event is `CallerEvent.callEnded`
Future<void> callerCallbackHandler(
CallerEvent event,
String number,
int? duration,
) async {
print("New event received from native $event");
switch (event) {
case CallerEvent.callEnded:
print('[ Caller ] Ended a call with number $number and duration $duration');
Get.toNamed('/testScreen');
break;
case CallerEvent.onMissedCall:
print('[ Caller ] Missed a call from number $number');
break;
case CallerEvent.onIncomingCallAnswered:
print('[ Caller ] Accepted call from number $number');
break;
case CallerEvent.onIncomingCallReceived:
print('[ Caller ] Phone is ringing with number $number');
break;
}
}
Future<void> initialize() async {
/// Check if the user has granted permissions
final permission = await Caller.checkPermission();
print('Caller permission $permission');
/// If not, then request user permission to access the Call State
if (!permission) {
Caller.requestPermissions();
} else {
Caller.initialize(callerCallbackHandler);
}
}
void main() {
WidgetsFlutterBinding.ensureInitialized(); //used when Flutter needs to call native code before calling runApp (sets up internal state of MethodChannels)
initialize(); //checks for and requests call prompt permissions
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return GetMaterialApp( //MaterialApp is a child of GetMaterial app (which is used to display dialogs, routes, snackbars, etc. without passing a context)
debugShowCheckedModeBanner: false,
title: 'Phone calls',
theme: ThemeData(
primarySwatch: Colors.purple,
),
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => const MyHomePage(title: 'Phone Calls')
),
GetPage(
name: '/testScreen',
page: () => const TestScreen()
),
],
);
}
}
经过仔细检查,caller: ^0.0.4
包使用了一个与主隔离区分开的 Dart 隔离区,因此 callerCallbackHandler
方法在应用程序的上下文之外运行。这使得无法更新应用程序状态或执行 UI 影响逻辑。作为一种解决方法,我决定实现一个原生的 Android BroadcastReceiver
来获取不断变化的 phone 状态并路由到指定的屏幕。如果 Flutter 应用程序当前关闭,BroadcastReceiver
也会以编程方式启动它。