使用navigatorState在使用getMaterialPage的GetMaterialPage的Genereraterouts时无效

发布于 2025-02-13 02:55:58 字数 17973 浏览 0 评论 0原文

我正在使用 flutter_callkit_incoming package package package 通过我的所有状态中的有效载荷在我的应用程序中获得呼叫notnotification notnotification应用程序IE背景/前景/终止状态。

在我的应用程序的前景状态下,单击“接听通知”通知后,“视频allingagorapage”现在可以进行导航。 - >使用Nikahmatch类Listererevent

,但是当此侦听器在后台/终止状态下用于导航时出现问题。 - >由于背景处理程序,使用ListerErvent作为顶级函数,如下所示,如下所示,

当编译器读取此行等待Navigationservice.instance.Instance.pushnamed(apploute.voicecall);在听众事件中单击“ flutter_callkit_incoming”在我应用程序的后台/终止状态中的“接受通知”时,我会在控制台中遇到此错误。

E/flutter (11545): Receiver: null
E/flutter (11545): Tried calling: pushNamed<Object>("/videoCall_agora", arguments: null)
E/flutter (11545): #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:38:5)
E/flutter (11545): #1      NavigationService.pushNamed (package:nikah_match/helpers/navigationService.dart:38:39)
E/flutter (11545): #2      listenerEvent.<anonymous closure> (package:nikah_match/main.dart:311:46)
E/flutter (11545): <asynchronous suspension>
E/flutter (11545): 

以及在日志中,我发现log(navigationKey.currentState.toString());在按下函数中定义的也是null。在前景导航的情况下,navigationKey.currentState从推送函数却不是无效的。

当我在终止状态下收到呼叫通知时,请在不创建窗口小部件并初始化导致导航器状态为null的getMaterialPage的情况下接受侦听器事件的情况(顶级功能)。

我认为 Case在启动/构建小部件树和Navigator键之前。

分配ListNerevent Accept

从未 BackgroundHandler函数:

Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  bool videoCallEnabled = false;
  bool audioCallEnabled = false;

  if (message != null) {
    debugPrint("Handling background is called");
    print(
        "Handling a background message and background handler: ${message.messageId}");
    try {
      videoCallEnabled = message.data.containsKey('videoCall');
      audioCallEnabled = message.data.containsKey('voiceCall');

      if (videoCallEnabled || audioCallEnabled) {
        log("Video call is configured and is started");
        showCallkitIncoming(Uuid().v4(), message: message);
        //w8 for streaming
        debugPrint("Should listen to events in background/terminated state");
        listenerEvent(message);
      } else {
        log("No Video or audio call was initialized");
      }
    } catch (e) {
      debugPrint("Error occured:" + e.toString());
    }
  }

}

这是我的侦听器事件:

 Future<void> listenerEvent(RemoteMessage message) async {


  log("Listner event background/terminated handler app has run");
  backgroundChatRoomId = message.data['chatRoomId'];
  backgroundCallsDocId = message.data['callsDocId'];
  backgroundRequesterName = message.data['callerName'];
  backgroundRequesterImageUrl = message.data['imageUrl'];
  // String imageUrl = message.data['imageUrl'];

  bool videoCallEnabled = false;


  if (message.data != null) {
    videoCallEnabled = message.data.containsKey('videoCall');
  } else {
    log("Data was null");
  }
  try {
    FlutterCallkitIncoming.onEvent.listen((event) async {
      print('HOME: $event');
      switch (event.name) {
        case CallEvent.ACTION_CALL_INCOMING:
        // TODO: received an incoming call
          log("Call is incoming");
          break;
        case CallEvent.ACTION_CALL_START:
        // TODO: started an outgoing call
        // TODO: show screen calling in Flutter
          log("Call is started");
          break;
        case CallEvent.ACTION_CALL_ACCEPT:
        // TODO: accepted an incoming call
        // TODO: show screen calling in Flutter
          log("......Call Accepted background/terminated state....");
          currentChannel = backgroundChatRoomId;
          log("currentChannel in accepted is: $currentChannel");
          debugPrint("Details of call"+backgroundChatRoomId+backgroundCallsDocId );
          await FirebaseFirestore.instance
              .collection("ChatRoom")
              .doc(backgroundChatRoomId)
              .collection("calls")
              .doc(backgroundCallsDocId)
              .update({
            'receiverCallResponse': 'Accepted',
            'callResponseDateTime': FieldValue.serverTimestamp()
          }).then((value) => log("Values updated at firebase firestore as Accepted"));

          if (videoCallEnabled) {
            log("in video call enabled in accept call of listener event");
            await NavigationService.instance.pushNamed(AppRoute.videoAgoraCall,);
           
          } 
          break;

      }
    });
  } on Exception {}
}

这是我的第一个状态getMaterial页面,它初始化了所有firebase消息传递函数(前景本地flutter local flutter本地通知不包括在代码中以易于可读性):

class NikkahMatch extends StatefulWidget {
  const NikkahMatch({Key key}) : super(key: key);

  @override
  State<NikkahMatch> createState() => _NikkahMatchState();
}

class _NikkahMatchState extends State<NikkahMatch> with WidgetsBindingObserver {


  


    @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    WidgetsBinding.instance.addObserver(this);
//Function if from terminated state
    FirebaseMessaging.instance.getInitialMessage().then((message) async {

      log("get Initial Message function is used.. ");

      String screenName = 'No screen';
      bool screenEnabled = false;
      if (message != null) {
        if (message.data != null) {
          log("Remote message data is null for now");
          if (message.data.isNotEmpty) {
            screenEnabled = message.data.containsKey('screenName');
            if (screenEnabled) {
              if (screenName == 'chatScreen') {
                log("Screen is Chat");
                String type = 'Nothing';
                String chatRoomId = 'Nothing';
                if (message.data['type'] != null) {
                  type = message.data['type'];
                  if (type == 'profileMatched') {
                    String likerId = message.data['likerId'];
                    String likedId = message.data['likedId'];
                    chatRoomId = chatController.getChatRoomId(likerId, likedId);
                  }
                } else {
                  chatRoomId = message.data['chatRoomId'];
                }

                log("ChatRoom Id is: ${chatRoomId}");
                log("Navigating from onMessagePop to the ChatRoom 1");
                //We have chatRoomId here and we need to navigate to the ChatRoomScreen having same Id
                await FirebaseFirestore.instance
                    .collection("ChatRoom")
                    .doc(chatRoomId)
                    .get()
                    .then((value) async {
                  if (value.exists) {
                    log("ChatRoom Doc " + value.toString());
                    log("Navigating from onMessagePop to the ChatRoom 2");
                    log("Last Message was : ${value.data()['lastMessage']}");
                    backGroundLevelChatRoomDoc = value.data();
                   
                    await NavigationService.instance.pushNamed(AppRoute.chatScreen);
                  } else {
                    log("no doc exist for chat");
                  }
                });
              }

          else if (screenName == 'videoScreen') {
                log("Screen is Video");
                initCall(message);
              } else if (screenName == 'voiceScreen') {
                log("Screen is Audio");
                initCall(message);
              } else {
                log("Screen is in Else method of getInitialMessage");
              }
            
            } else {
              debugPrint("Notification Pay load data is Empty");
            }
          } else {
            log("Screen isn't enabled");
          }
        } else {
          log("message data is null");
        }
      } else {
        log("...........message data is null in bahir wala else");
      }
    });

   
    //This function will constantly listen to the notification recieved from firebase

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
      log("onMessageOpenedApp function is used.. ");
      String screenName = 'No screen';
      bool screenEnabled = false;
      if (message.data.isNotEmpty) {
        screenEnabled = message.data.containsKey('screenName');
        if (screenEnabled) {
          //Move to the screen which is needed to
          log("Screen is Enabled");
          screenName = message.data['screenName'];
          log("Screen name is: $screenName");

          if (screenName == 'chatScreen') {
            log("Screen is Chat");
            String type = 'Nothing';
            String chatRoomId = 'Nothing';
            if (message.data['type'] != null) {
              type = message.data['type'];
              if (type == 'profileMatched') {
                String likerId = message.data['likerId'];
                String likedId = message.data['likedId'];
                chatRoomId = chatController.getChatRoomId(likerId, likedId);
              }
            } else {
              chatRoomId = message.data['chatRoomId'];
            }

            log("ChatRoom Id is: ${chatRoomId}");
            log("Navigating from onMessagePop to the ChatRoom 1");
            //We have chatRoomId here and we need to navigate to the ChatRoomScreen having same Id
            await FirebaseFirestore.instance
                .collection("ChatRoom")
                .doc(chatRoomId)
                .get()
                .then((value) async {
              if (value.exists) {
                log("ChatRoom Doc " + value.toString());
                log("Navigating from onMessagePop to the ChatRoom 2");
                log("Last Message was : ${value.data()['lastMessage']}");
                backGroundLevelChatRoomDoc = value.data();
                /*     await NavigationService.instance
                    .pushNamed(AppRoute.chatScreen, args:ChatArgs(value.data(), false));*/
                await NavigationService.instance.pushNamed(AppRoute.chatScreen);
              } else {
                log("no doc exist for chat");
              }
            });
          }

       else if (screenName == 'videoScreen') {
            log("Screen is Video");
            initCall(message);
          } else if (screenName == 'voiceScreen') {
            log("Screen is Audio");
            initCall(message);
          } else {
            log("Screen is in Else");
          }
        }
      } else {
        debugPrint("Notification Pay load data is Empty");
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    print("Main page build");
    return GetMaterialApp(
      onGenerateRoute: AppRoute.generateRoute,
      debugShowCheckedModeBanner: false,
      navigatorKey: NavigationService.instance.navigationKey,
      debugShowMaterialGrid: false,
      title: 'Nikah Match',
      initialRoute: '/splash_screen',
      theme: ThemeData(
        fontFamily: 'Poppins',
        scaffoldBackgroundColor: kScaffoldBgColor,
        appBarTheme: const AppBarTheme(
          elevation: 0,
          backgroundColor: kPrimaryColor,
        ),
        accentColor: kPrimaryColor.withOpacity(0.2),
      ),
      themeMode: ThemeMode.light,
      getPages: [
        GetPage(name: '/splash_screen', page: () => SplashScreen()),
        GetPage(name: '/get_started', page: () => GetStarted()),
        GetPage(
          name: '/videoCall_agora',
          page: () => VideoCallAgoraUIKit(
            anotherUserName: backgroundRequesterName,
            anotherUserImage: backgroundRequesterImageUrl,
            channelName: backgroundChatRoomId,
            token: "",
            anotherUserId: "",
            docId: backgroundCallsDocId,
            callDoc: backgroundPassableAbleCdm,
          ),
        ),
        // GetPage(name: '/after_log_in_screen', page: () => AfterLogin()),
      ],
    );
  }
}

这是我的navigationservice类:

class NavigationService {
  // Global navigation key for whole application
  GlobalKey<NavigatorState> navigationKey = GlobalKey<NavigatorState>();

  /// Get app context
  BuildContext get appContext => navigationKey.currentContext;

  /// App route observer
  RouteObserver<Route<dynamic>> routeObserver = RouteObserver<Route<dynamic>>();

  static final NavigationService _instance = NavigationService._private();
  factory NavigationService() {
    return _instance;
  }
  NavigationService._private();

  static NavigationService get instance => _instance;

  /// Pushing new page into navigation stack
  ///
  /// `routeName` is page's route name defined in [AppRoute]
  /// `args` is optional data to be sent to new page
  Future<T> pushNamed<T extends Object>(String routeName,
      {Object args}) async {
    log(navigationKey.toString());
    log(navigationKey.currentState.toString());
    return navigationKey.currentState.pushNamed<T>(
      routeName,
      arguments: args,
    );
  }

  Future<T> pushNamedIfNotCurrent<T extends Object>(String routeName,
      {Object args}) async {
    if (!isCurrent(routeName)) {
      return pushNamed(routeName, args: args);
    }
    return null;
  }

  bool isCurrent(String routeName) {
    bool isCurrent = false;
    navigationKey.currentState.popUntil((route) {
      if (route.settings.name == routeName) {
        isCurrent = true;
      }
      return true;
    });
    return isCurrent;
  }

  /// Pushing new page into navigation stack
  ///
  /// `route` is route generator
  Future<T> push<T extends Object>(Route<T> route) async {
    return navigationKey.currentState.push<T>(route);
  }

  /// Replace the current route of the navigator by pushing the given route and
  /// then disposing the previous route once the new route has finished
  /// animating in.
  Future<T> pushReplacementNamed<T extends Object, TO extends Object>(
      String routeName,
      {Object args}) async {
    return navigationKey.currentState.pushReplacementNamed<T, TO>(
      routeName,
      arguments: args,
    );
  }

  /// Push the route with the given name onto the navigator, and then remove all
  /// the previous routes until the `predicate` returns true.
  Future<T> pushNamedAndRemoveUntil<T extends Object>(
      String routeName, {
        Object args,
        bool Function(Route<dynamic>) predicate,
      }) async {
    return navigationKey.currentState.pushNamedAndRemoveUntil<T>(
      routeName,
      predicate==null?  (_) => false: (_) => true,
      arguments: args,
    );
  }

  /// Push the given route onto the navigator, and then remove all the previous
  /// routes until the `predicate` returns true.
  Future<T> pushAndRemoveUntil<T extends Object>(
      Route<T> route, {
        bool Function(Route<dynamic>) predicate,
      }) async {
    return navigationKey.currentState.pushAndRemoveUntil<T>(
      route,
      predicate==null?  (_) => false: (_) => true,
    );
  }

  /// Consults the current route's [Route.willPop] method, and acts accordingly,
  /// potentially popping the route as a result; returns whether the pop request
  /// should be considered handled.
  Future<bool> maybePop<T extends Object>([Object args]) async {
    return navigationKey.currentState.maybePop<T>(args as T);
  }

  /// Whether the navigator can be popped.
  bool canPop() => navigationKey.currentState.canPop();

  /// Pop the top-most route off the navigator.
  void goBack<T extends Object>({T result}) {
    navigationKey.currentState.pop<T>(result);
  }

  /// Calls [pop] repeatedly until the predicate returns true.
  void popUntil(String route) {
    navigationKey.currentState.popUntil(ModalRoute.withName(route));
  }
}
class AppRoute {
  static const homePage = '/home_page';

  static const chatScreen ='/chat_screen';

  static const splash = '/splash_screen';
  static const voiceCall = '/voice_call';
  static const videoAgoraCall = '/videoCall_agora';

  static Route<Object> generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case homePage:
        return MaterialPageRoute(
            builder: (_) => HomePage(), settings: settings);
      case chatScreen:

        return MaterialPageRoute(
            builder: (_) =>
                ChatScreen(docs: backGroundLevelChatRoomDoc, isArchived: false,), settings: settings);
        case splash:
        return MaterialPageRoute(
            builder: (_) =>  SplashScreen(), settings: settings);
      case voiceCall:
        return MaterialPageRoute(
            builder: (_) =>  VoiceCall(
              toCallName: backgroundRequesterName,
              toCallImageUrl: backgroundRequesterImageUrl,
              channelName: backgroundChatRoomId,
              token: voiceCallToken,
              docId: backgroundCallsDocId,
              callDoc: backgroundPassableAbleCdm,
            ), settings: settings);
      case videoAgoraCall:
        return MaterialPageRoute(
            builder: (_) =>  VideoCallAgoraUIKit(
              anotherUserName: backgroundRequesterName,
              anotherUserImage: backgroundRequesterImageUrl,
              channelName: backgroundChatRoomId,
              token: "",
              anotherUserId: "",
              docId: backgroundCallsDocId,
              callDoc: backgroundPassableAbleCdm,
            ), settings: settings);

      default:
        return null;
    }
  }
}

I'm using flutter_callkit_incoming package to get callsNotification in my application through the payload of FCM in all states of my App i.e background/forground/terminated state.

Navigation is fine now to the VideoCallingAgoraPage after clicking Accept button on call incoming notification on forground state of my app. -> Using listenerEvent from NikahMatch class

But problem comes when this listenerEvent is used for navigation in background/terminated state. -> Using listenerEvent as top level function because of background handler as shown below in my background handler function

When the compiler reads this line await NavigationService.instance.pushNamed(AppRoute.voiceCall); in listener event on clicking accept of notification from flutter_callKit_incoming in the background/terminated state of my app, I am getting this error in console.

E/flutter (11545): Receiver: null
E/flutter (11545): Tried calling: pushNamed<Object>("/videoCall_agora", arguments: null)
E/flutter (11545): #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:38:5)
E/flutter (11545): #1      NavigationService.pushNamed (package:nikah_match/helpers/navigationService.dart:38:39)
E/flutter (11545): #2      listenerEvent.<anonymous closure> (package:nikah_match/main.dart:311:46)
E/flutter (11545): <asynchronous suspension>
E/flutter (11545): 

As well as in the logs, I find that log(navigationKey.currentState.toString()); defined in pushNamed function is also null. While in the case of forground navigation, navigationKey.currentState from pushNamed function is never null.

When I received call notification in terminated state, accept case of listener event(top level function) was called without creating widget tree and initializing GetMaterialPage that caused navigator state to be null.

I think that the listnerEvent Accept case is run before starting/building widget tree and navigator key in GetMaterialPage is never assigned.

How can I get rid of that?

This is my backgroundHandler function:

Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  bool videoCallEnabled = false;
  bool audioCallEnabled = false;

  if (message != null) {
    debugPrint("Handling background is called");
    print(
        "Handling a background message and background handler: ${message.messageId}");
    try {
      videoCallEnabled = message.data.containsKey('videoCall');
      audioCallEnabled = message.data.containsKey('voiceCall');

      if (videoCallEnabled || audioCallEnabled) {
        log("Video call is configured and is started");
        showCallkitIncoming(Uuid().v4(), message: message);
        //w8 for streaming
        debugPrint("Should listen to events in background/terminated state");
        listenerEvent(message);
      } else {
        log("No Video or audio call was initialized");
      }
    } catch (e) {
      debugPrint("Error occured:" + e.toString());
    }
  }

}

This is my listener event:

 Future<void> listenerEvent(RemoteMessage message) async {


  log("Listner event background/terminated handler app has run");
  backgroundChatRoomId = message.data['chatRoomId'];
  backgroundCallsDocId = message.data['callsDocId'];
  backgroundRequesterName = message.data['callerName'];
  backgroundRequesterImageUrl = message.data['imageUrl'];
  // String imageUrl = message.data['imageUrl'];

  bool videoCallEnabled = false;


  if (message.data != null) {
    videoCallEnabled = message.data.containsKey('videoCall');
  } else {
    log("Data was null");
  }
  try {
    FlutterCallkitIncoming.onEvent.listen((event) async {
      print('HOME: $event');
      switch (event.name) {
        case CallEvent.ACTION_CALL_INCOMING:
        // TODO: received an incoming call
          log("Call is incoming");
          break;
        case CallEvent.ACTION_CALL_START:
        // TODO: started an outgoing call
        // TODO: show screen calling in Flutter
          log("Call is started");
          break;
        case CallEvent.ACTION_CALL_ACCEPT:
        // TODO: accepted an incoming call
        // TODO: show screen calling in Flutter
          log("......Call Accepted background/terminated state....");
          currentChannel = backgroundChatRoomId;
          log("currentChannel in accepted is: $currentChannel");
          debugPrint("Details of call"+backgroundChatRoomId+backgroundCallsDocId );
          await FirebaseFirestore.instance
              .collection("ChatRoom")
              .doc(backgroundChatRoomId)
              .collection("calls")
              .doc(backgroundCallsDocId)
              .update({
            'receiverCallResponse': 'Accepted',
            'callResponseDateTime': FieldValue.serverTimestamp()
          }).then((value) => log("Values updated at firebase firestore as Accepted"));

          if (videoCallEnabled) {
            log("in video call enabled in accept call of listener event");
            await NavigationService.instance.pushNamed(AppRoute.videoAgoraCall,);
           
          } 
          break;

      }
    });
  } on Exception {}
}

This is my first stateful GetMaterial page which initializes all Firebase Messaging functions (Forground Local FLutter local notifications excluded from code for readability):

class NikkahMatch extends StatefulWidget {
  const NikkahMatch({Key key}) : super(key: key);

  @override
  State<NikkahMatch> createState() => _NikkahMatchState();
}

class _NikkahMatchState extends State<NikkahMatch> with WidgetsBindingObserver {


  


    @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    WidgetsBinding.instance.addObserver(this);
//Function if from terminated state
    FirebaseMessaging.instance.getInitialMessage().then((message) async {

      log("get Initial Message function is used.. ");

      String screenName = 'No screen';
      bool screenEnabled = false;
      if (message != null) {
        if (message.data != null) {
          log("Remote message data is null for now");
          if (message.data.isNotEmpty) {
            screenEnabled = message.data.containsKey('screenName');
            if (screenEnabled) {
              if (screenName == 'chatScreen') {
                log("Screen is Chat");
                String type = 'Nothing';
                String chatRoomId = 'Nothing';
                if (message.data['type'] != null) {
                  type = message.data['type'];
                  if (type == 'profileMatched') {
                    String likerId = message.data['likerId'];
                    String likedId = message.data['likedId'];
                    chatRoomId = chatController.getChatRoomId(likerId, likedId);
                  }
                } else {
                  chatRoomId = message.data['chatRoomId'];
                }

                log("ChatRoom Id is: ${chatRoomId}");
                log("Navigating from onMessagePop to the ChatRoom 1");
                //We have chatRoomId here and we need to navigate to the ChatRoomScreen having same Id
                await FirebaseFirestore.instance
                    .collection("ChatRoom")
                    .doc(chatRoomId)
                    .get()
                    .then((value) async {
                  if (value.exists) {
                    log("ChatRoom Doc " + value.toString());
                    log("Navigating from onMessagePop to the ChatRoom 2");
                    log("Last Message was : ${value.data()['lastMessage']}");
                    backGroundLevelChatRoomDoc = value.data();
                   
                    await NavigationService.instance.pushNamed(AppRoute.chatScreen);
                  } else {
                    log("no doc exist for chat");
                  }
                });
              }

          else if (screenName == 'videoScreen') {
                log("Screen is Video");
                initCall(message);
              } else if (screenName == 'voiceScreen') {
                log("Screen is Audio");
                initCall(message);
              } else {
                log("Screen is in Else method of getInitialMessage");
              }
            
            } else {
              debugPrint("Notification Pay load data is Empty");
            }
          } else {
            log("Screen isn't enabled");
          }
        } else {
          log("message data is null");
        }
      } else {
        log("...........message data is null in bahir wala else");
      }
    });

   
    //This function will constantly listen to the notification recieved from firebase

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
      log("onMessageOpenedApp function is used.. ");
      String screenName = 'No screen';
      bool screenEnabled = false;
      if (message.data.isNotEmpty) {
        screenEnabled = message.data.containsKey('screenName');
        if (screenEnabled) {
          //Move to the screen which is needed to
          log("Screen is Enabled");
          screenName = message.data['screenName'];
          log("Screen name is: $screenName");

          if (screenName == 'chatScreen') {
            log("Screen is Chat");
            String type = 'Nothing';
            String chatRoomId = 'Nothing';
            if (message.data['type'] != null) {
              type = message.data['type'];
              if (type == 'profileMatched') {
                String likerId = message.data['likerId'];
                String likedId = message.data['likedId'];
                chatRoomId = chatController.getChatRoomId(likerId, likedId);
              }
            } else {
              chatRoomId = message.data['chatRoomId'];
            }

            log("ChatRoom Id is: ${chatRoomId}");
            log("Navigating from onMessagePop to the ChatRoom 1");
            //We have chatRoomId here and we need to navigate to the ChatRoomScreen having same Id
            await FirebaseFirestore.instance
                .collection("ChatRoom")
                .doc(chatRoomId)
                .get()
                .then((value) async {
              if (value.exists) {
                log("ChatRoom Doc " + value.toString());
                log("Navigating from onMessagePop to the ChatRoom 2");
                log("Last Message was : ${value.data()['lastMessage']}");
                backGroundLevelChatRoomDoc = value.data();
                /*     await NavigationService.instance
                    .pushNamed(AppRoute.chatScreen, args:ChatArgs(value.data(), false));*/
                await NavigationService.instance.pushNamed(AppRoute.chatScreen);
              } else {
                log("no doc exist for chat");
              }
            });
          }

       else if (screenName == 'videoScreen') {
            log("Screen is Video");
            initCall(message);
          } else if (screenName == 'voiceScreen') {
            log("Screen is Audio");
            initCall(message);
          } else {
            log("Screen is in Else");
          }
        }
      } else {
        debugPrint("Notification Pay load data is Empty");
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    print("Main page build");
    return GetMaterialApp(
      onGenerateRoute: AppRoute.generateRoute,
      debugShowCheckedModeBanner: false,
      navigatorKey: NavigationService.instance.navigationKey,
      debugShowMaterialGrid: false,
      title: 'Nikah Match',
      initialRoute: '/splash_screen',
      theme: ThemeData(
        fontFamily: 'Poppins',
        scaffoldBackgroundColor: kScaffoldBgColor,
        appBarTheme: const AppBarTheme(
          elevation: 0,
          backgroundColor: kPrimaryColor,
        ),
        accentColor: kPrimaryColor.withOpacity(0.2),
      ),
      themeMode: ThemeMode.light,
      getPages: [
        GetPage(name: '/splash_screen', page: () => SplashScreen()),
        GetPage(name: '/get_started', page: () => GetStarted()),
        GetPage(
          name: '/videoCall_agora',
          page: () => VideoCallAgoraUIKit(
            anotherUserName: backgroundRequesterName,
            anotherUserImage: backgroundRequesterImageUrl,
            channelName: backgroundChatRoomId,
            token: "",
            anotherUserId: "",
            docId: backgroundCallsDocId,
            callDoc: backgroundPassableAbleCdm,
          ),
        ),
        // GetPage(name: '/after_log_in_screen', page: () => AfterLogin()),
      ],
    );
  }
}

This is my NavigationService class:

class NavigationService {
  // Global navigation key for whole application
  GlobalKey<NavigatorState> navigationKey = GlobalKey<NavigatorState>();

  /// Get app context
  BuildContext get appContext => navigationKey.currentContext;

  /// App route observer
  RouteObserver<Route<dynamic>> routeObserver = RouteObserver<Route<dynamic>>();

  static final NavigationService _instance = NavigationService._private();
  factory NavigationService() {
    return _instance;
  }
  NavigationService._private();

  static NavigationService get instance => _instance;

  /// Pushing new page into navigation stack
  ///
  /// `routeName` is page's route name defined in [AppRoute]
  /// `args` is optional data to be sent to new page
  Future<T> pushNamed<T extends Object>(String routeName,
      {Object args}) async {
    log(navigationKey.toString());
    log(navigationKey.currentState.toString());
    return navigationKey.currentState.pushNamed<T>(
      routeName,
      arguments: args,
    );
  }

  Future<T> pushNamedIfNotCurrent<T extends Object>(String routeName,
      {Object args}) async {
    if (!isCurrent(routeName)) {
      return pushNamed(routeName, args: args);
    }
    return null;
  }

  bool isCurrent(String routeName) {
    bool isCurrent = false;
    navigationKey.currentState.popUntil((route) {
      if (route.settings.name == routeName) {
        isCurrent = true;
      }
      return true;
    });
    return isCurrent;
  }

  /// Pushing new page into navigation stack
  ///
  /// `route` is route generator
  Future<T> push<T extends Object>(Route<T> route) async {
    return navigationKey.currentState.push<T>(route);
  }

  /// Replace the current route of the navigator by pushing the given route and
  /// then disposing the previous route once the new route has finished
  /// animating in.
  Future<T> pushReplacementNamed<T extends Object, TO extends Object>(
      String routeName,
      {Object args}) async {
    return navigationKey.currentState.pushReplacementNamed<T, TO>(
      routeName,
      arguments: args,
    );
  }

  /// Push the route with the given name onto the navigator, and then remove all
  /// the previous routes until the `predicate` returns true.
  Future<T> pushNamedAndRemoveUntil<T extends Object>(
      String routeName, {
        Object args,
        bool Function(Route<dynamic>) predicate,
      }) async {
    return navigationKey.currentState.pushNamedAndRemoveUntil<T>(
      routeName,
      predicate==null?  (_) => false: (_) => true,
      arguments: args,
    );
  }

  /// Push the given route onto the navigator, and then remove all the previous
  /// routes until the `predicate` returns true.
  Future<T> pushAndRemoveUntil<T extends Object>(
      Route<T> route, {
        bool Function(Route<dynamic>) predicate,
      }) async {
    return navigationKey.currentState.pushAndRemoveUntil<T>(
      route,
      predicate==null?  (_) => false: (_) => true,
    );
  }

  /// Consults the current route's [Route.willPop] method, and acts accordingly,
  /// potentially popping the route as a result; returns whether the pop request
  /// should be considered handled.
  Future<bool> maybePop<T extends Object>([Object args]) async {
    return navigationKey.currentState.maybePop<T>(args as T);
  }

  /// Whether the navigator can be popped.
  bool canPop() => navigationKey.currentState.canPop();

  /// Pop the top-most route off the navigator.
  void goBack<T extends Object>({T result}) {
    navigationKey.currentState.pop<T>(result);
  }

  /// Calls [pop] repeatedly until the predicate returns true.
  void popUntil(String route) {
    navigationKey.currentState.popUntil(ModalRoute.withName(route));
  }
}
class AppRoute {
  static const homePage = '/home_page';

  static const chatScreen ='/chat_screen';

  static const splash = '/splash_screen';
  static const voiceCall = '/voice_call';
  static const videoAgoraCall = '/videoCall_agora';

  static Route<Object> generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case homePage:
        return MaterialPageRoute(
            builder: (_) => HomePage(), settings: settings);
      case chatScreen:

        return MaterialPageRoute(
            builder: (_) =>
                ChatScreen(docs: backGroundLevelChatRoomDoc, isArchived: false,), settings: settings);
        case splash:
        return MaterialPageRoute(
            builder: (_) =>  SplashScreen(), settings: settings);
      case voiceCall:
        return MaterialPageRoute(
            builder: (_) =>  VoiceCall(
              toCallName: backgroundRequesterName,
              toCallImageUrl: backgroundRequesterImageUrl,
              channelName: backgroundChatRoomId,
              token: voiceCallToken,
              docId: backgroundCallsDocId,
              callDoc: backgroundPassableAbleCdm,
            ), settings: settings);
      case videoAgoraCall:
        return MaterialPageRoute(
            builder: (_) =>  VideoCallAgoraUIKit(
              anotherUserName: backgroundRequesterName,
              anotherUserImage: backgroundRequesterImageUrl,
              channelName: backgroundChatRoomId,
              token: "",
              anotherUserId: "",
              docId: backgroundCallsDocId,
              callDoc: backgroundPassableAbleCdm,
            ), settings: settings);

      default:
        return null;
    }
  }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

静赏你的温柔 2025-02-20 02:55:58

实际上,当使用 flutter_incoming_callkit 用于终止/背景状态,持续数周,并且令人震惊解决方案非常简单。

导致您这个问题的原因是:

- &gt; BackgroundHandler功能以其孤立和来自Nikkahmatch类的ongereraterouts运行 BackgroundHandler功能, BackgroundHandler函数并不知道这个孤立的功能,而您实际上试图通过按下推动的路线进行导航, BackgroundHandler功能却不知道。 。

所以我的解决方案是:

- &gt;使用Firestore和Cloud功能的组合进行导航。这将使我们能够具有上下文,并且它不会是无效的,因为我们正在从应用程序窗口基内导航,并且不是来自隔离函数的 top级聆听功能,仅用于更改Firestore上呼叫文档中的值。

[注意:顶级函数是一个不属于任何类的函数]

在接收flutter_incoming_callkit在接收方侧面的通知:

在单击“接受”按钮上,使用top级聆听函数来更改呼叫从传入到呼叫文件中接受的状态。
这将从终止/背景状态打开应用程序。

我使用此功能didchangeaplifecyclestate在我的第一类小部件树中来处理/知道应用是否来自终止/背景状态:
查看此代码:

  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    print(state);
    if (state == AppLifecycleState.resumed) {
      //Check call when open app from background
      print("in app life cycle resumed");
      checkAndNavigationCallingPage();
    }
  }

checkAndNavigationCallingPage() async {
    print("checkAndNavigationCallingPage CheckCalling page 1");
    if (auth.currentUser != null) {
      print("auth.currentUser.uid: ${auth.currentUser.uid}");
    }

    // NavigationService().navigationKey.currentState.pushNamed(AppRoute.videoAgoraCall);

    var currentCall = await getCurrentCall();
    print("inside the checkAndNavigationCallingPage and $currentCall");

    if (currentCall != null) {
      print("inside the currentCall != null");
      //Here we have to move to calling page
      final g = Get;
      if (!g.isDialogOpen) {
        g.defaultDialog(content: CircularProgressIndicator());
      }
      Future.delayed(Duration(milliseconds: 50));
      await FirebaseFirestore.instance
          .collection('Users')
          .doc(auth.currentUser.uid)
          .collection('calls')
          .doc(auth.currentUser.uid)
          .get()
          .then((userCallDoc) async {
        if (userCallDoc.exists) {
          if (userCallDoc['type'] == 'chapVoiceCall' ||
              userCallDoc['type'] == 'chapVideoCall') {
            bool isDeclined = false;
            String chapCallDocId = "";
            chapCallDocId = userCallDoc['chapCallDocId'];
            print(
                "............. Call was Accepted by receiver or sender.............");
            print("ChapCallDocId $chapCallDocId");
            ChapCallDocModel cdm = ChapCallDocModel();
            await FirebaseFirestore.instance
                .collection("ChatRoom")
                .doc(userCallDoc['chatRoomId'])
                .collection("chapCalls")
                .doc(chapCallDocId)
                .get()
                .then((value) {
              if ((value['requestedResponse'] == 'Declined' &&
                      value['requesterResponse'] == 'Declined') ||
                  (value['senderResponse'] == 'Declined') ||
                  (value['requestedResponse'] == 'TimeOut' &&
                      value['requesterResponse'] == 'TimeOut')) {
                isDeclined = true;
                print(
                    "in checking declined ${value['receiverCallResponse'] == 'Declined' || value['senderCallResponse'] == 'Declined'}");
              } else {
                isDeclined = false;
                cdm = ChapCallDocModel.fromJson(value.data());
                print("CDM print is: ${cdm.toJson()}");
              }
            });
            currentChannel = userCallDoc['chatRoomId'];

            if (!isDeclined) {
              if (userCallDoc['type'] == 'chapVoiceCall') {
                print("in voice call enabled in accept call of listener event");
                var voiceCallToken = await GetToken().getTokenMethod(
                    userCallDoc['chatRoomId'], auth.currentUser.uid);
                print("token before if in splashscreen is: ${voiceCallToken}");
                if (voiceCallToken != null) {
                  if (g.isDialogOpen) {
                    g.back();
                  }
                  Get.to(
                    () => ChapVoiceCall(
                      toCallName: userCallDoc['requesterName'],
                      toCallImageUrl: userCallDoc['requesterImage'],
                      channelName: userCallDoc['chatRoomId'],
                      token: voiceCallToken,
                      docId: userCallDoc['chapCallDocId'],
                      callDoc: cdm,
                    ),
                  );
                } else {
                  print(
                      "......Call Accepted background/terminated state....in token being null in voice call enabled in accept call of listener event");
                }
              } else {
                if (g.isDialogOpen) {
                  g.back();
                }
                g.to(() => ChapVideoCallAgoraUIKit(
                      anotherUserName: userCallDoc['requesterName'],
                      anotherUserImage: userCallDoc['requesterImage'],
                      channelName: userCallDoc['chatRoomId'],
                      token: "",
                      anotherUserId: userCallDoc['requesterId'],
                      docId: userCallDoc['chapCallDocId'],
                      callDoc: cdm,
                    ));
              }
            } else {
              await FlutterCallkitIncoming.endAllCalls();
              print(
                  "the call was either declined by sender or receiver or was timed out.");
            }
          } else {
            bool isDeclined = false;
            print(
                "............. Call was Accepted by receiver or sender.............");
            CallDocModel cdm = CallDocModel();
            await FirebaseFirestore.instance
                .collection("ChatRoom")
                .doc(userCallDoc['chatRoomId'])
                .collection("calls")
                .doc(userCallDoc['callsDocId'])
                .get()
                .then((value) {
              if (value['receiverCallResponse'] == 'Declined' ||
                  value['senderCallResponse'] == 'Declined' ||
                  value['receiverCallResponse'] == 'TimeOut' ||
                  value['senderCallResponse'] == 'TimeOut') {
                isDeclined = true;
                print(
                    "in checking declined ${value['receiverCallResponse'] == 'Declined' || value['senderCallResponse'] == 'Declined'}");
              } else {
                isDeclined = false;
                cdm = CallDocModel.fromJson(value.data());
                print("CDM print is: ${cdm.toJson()}");
              }
            });
            currentChannel = userCallDoc['chatRoomId'];

            if (!isDeclined) {
              if (userCallDoc['type'] == 'voiceCall') {
                print("in voice call enabled in accept call of listener event");
                var voiceCallToken = await GetToken().getTokenMethod(
                    userCallDoc['chatRoomId'], auth.currentUser.uid);
                print("token before if in splashscreen is: ${voiceCallToken}");
                if (voiceCallToken != null) {
                  if (g.isDialogOpen) {
                    g.back();
                  }
                  Get.to(
                    () => VoiceCall(
                      toCallName: userCallDoc['requesterName'],
                      toCallImageUrl: userCallDoc['requesterImage'],
                      channelName: userCallDoc['chatRoomId'],
                      token: voiceCallToken,
                      docId: userCallDoc['callsDocId'],
                      callDoc: cdm,
                    ),
                  );
                } else {
                  print(
                      "......Call Accepted background/terminated state....in token being null in voice call enabled in accept call of listener event");
                }
              } else {
                if (g.isDialogOpen) {
                  g.back();
                }
                g.to(() => VideoCallAgoraUIKit(
                      anotherUserName: userCallDoc['requesterName'],
                      anotherUserImage: userCallDoc['requesterImage'],
                      channelName: userCallDoc['chatRoomId'],
                      token: "",
                      anotherUserId: userCallDoc['requesterId'],
                      docId: userCallDoc['callsDocId'],
                      callDoc: cdm,
                    ));
              }
            } else {
              await FlutterCallkitIncoming.endAllCalls();
              print(
                  "the call was either declined by sender or receiver or was timed out.");
            }
          }
        } else {
          debugPrint("Document not found");
        }
      });
    }
  }

在上面的代码中,我给了我的数据库方案,因此需要知道如何处理不同呼叫状态的任何人都可以深入研究它。或您可以在这里发表评论,我很荣幸答复。

Actually, I was also stuck when using flutter_incoming_callkit for navigation in terminated/background state for weeks, and strikingly, the solution was so simple.

The reason causing you this problem is:

-> For receiving notification in terminated or background state, backgroundHandler function is working in its own isolate and onGenerateRoutes from your NikkahMatch class is not and never known to this isolated function where you are actually trying to navigate through the pushNamed route.

So my solution was:

-> Use the combination of firestore and cloud functions for navigation. This would allow us to have the context and it won't be null as we are navigating from inside the app widget tree and not from an isolated function i.e backgroundHandler. Top-level listenerEvent Function is only used to change the values in the call document on Firestore.

[Note: Top-Level function is a function that is not part of any class]

Upon receiving flutter_incoming_callkit notification on receiver side:

On the click of Accept button, use top-level listenerEvent function to change the call status from incoming to accepted in call's document.
This will open the app from terminated/background state.

I used this function didChangeAppLifecycleState in my first class of widget tree to handle/know if app has come from terminated/background state:
Check out this code:

  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    print(state);
    if (state == AppLifecycleState.resumed) {
      //Check call when open app from background
      print("in app life cycle resumed");
      checkAndNavigationCallingPage();
    }
  }

checkAndNavigationCallingPage() async {
    print("checkAndNavigationCallingPage CheckCalling page 1");
    if (auth.currentUser != null) {
      print("auth.currentUser.uid: ${auth.currentUser.uid}");
    }

    // NavigationService().navigationKey.currentState.pushNamed(AppRoute.videoAgoraCall);

    var currentCall = await getCurrentCall();
    print("inside the checkAndNavigationCallingPage and $currentCall");

    if (currentCall != null) {
      print("inside the currentCall != null");
      //Here we have to move to calling page
      final g = Get;
      if (!g.isDialogOpen) {
        g.defaultDialog(content: CircularProgressIndicator());
      }
      Future.delayed(Duration(milliseconds: 50));
      await FirebaseFirestore.instance
          .collection('Users')
          .doc(auth.currentUser.uid)
          .collection('calls')
          .doc(auth.currentUser.uid)
          .get()
          .then((userCallDoc) async {
        if (userCallDoc.exists) {
          if (userCallDoc['type'] == 'chapVoiceCall' ||
              userCallDoc['type'] == 'chapVideoCall') {
            bool isDeclined = false;
            String chapCallDocId = "";
            chapCallDocId = userCallDoc['chapCallDocId'];
            print(
                "............. Call was Accepted by receiver or sender.............");
            print("ChapCallDocId $chapCallDocId");
            ChapCallDocModel cdm = ChapCallDocModel();
            await FirebaseFirestore.instance
                .collection("ChatRoom")
                .doc(userCallDoc['chatRoomId'])
                .collection("chapCalls")
                .doc(chapCallDocId)
                .get()
                .then((value) {
              if ((value['requestedResponse'] == 'Declined' &&
                      value['requesterResponse'] == 'Declined') ||
                  (value['senderResponse'] == 'Declined') ||
                  (value['requestedResponse'] == 'TimeOut' &&
                      value['requesterResponse'] == 'TimeOut')) {
                isDeclined = true;
                print(
                    "in checking declined ${value['receiverCallResponse'] == 'Declined' || value['senderCallResponse'] == 'Declined'}");
              } else {
                isDeclined = false;
                cdm = ChapCallDocModel.fromJson(value.data());
                print("CDM print is: ${cdm.toJson()}");
              }
            });
            currentChannel = userCallDoc['chatRoomId'];

            if (!isDeclined) {
              if (userCallDoc['type'] == 'chapVoiceCall') {
                print("in voice call enabled in accept call of listener event");
                var voiceCallToken = await GetToken().getTokenMethod(
                    userCallDoc['chatRoomId'], auth.currentUser.uid);
                print("token before if in splashscreen is: ${voiceCallToken}");
                if (voiceCallToken != null) {
                  if (g.isDialogOpen) {
                    g.back();
                  }
                  Get.to(
                    () => ChapVoiceCall(
                      toCallName: userCallDoc['requesterName'],
                      toCallImageUrl: userCallDoc['requesterImage'],
                      channelName: userCallDoc['chatRoomId'],
                      token: voiceCallToken,
                      docId: userCallDoc['chapCallDocId'],
                      callDoc: cdm,
                    ),
                  );
                } else {
                  print(
                      "......Call Accepted background/terminated state....in token being null in voice call enabled in accept call of listener event");
                }
              } else {
                if (g.isDialogOpen) {
                  g.back();
                }
                g.to(() => ChapVideoCallAgoraUIKit(
                      anotherUserName: userCallDoc['requesterName'],
                      anotherUserImage: userCallDoc['requesterImage'],
                      channelName: userCallDoc['chatRoomId'],
                      token: "",
                      anotherUserId: userCallDoc['requesterId'],
                      docId: userCallDoc['chapCallDocId'],
                      callDoc: cdm,
                    ));
              }
            } else {
              await FlutterCallkitIncoming.endAllCalls();
              print(
                  "the call was either declined by sender or receiver or was timed out.");
            }
          } else {
            bool isDeclined = false;
            print(
                "............. Call was Accepted by receiver or sender.............");
            CallDocModel cdm = CallDocModel();
            await FirebaseFirestore.instance
                .collection("ChatRoom")
                .doc(userCallDoc['chatRoomId'])
                .collection("calls")
                .doc(userCallDoc['callsDocId'])
                .get()
                .then((value) {
              if (value['receiverCallResponse'] == 'Declined' ||
                  value['senderCallResponse'] == 'Declined' ||
                  value['receiverCallResponse'] == 'TimeOut' ||
                  value['senderCallResponse'] == 'TimeOut') {
                isDeclined = true;
                print(
                    "in checking declined ${value['receiverCallResponse'] == 'Declined' || value['senderCallResponse'] == 'Declined'}");
              } else {
                isDeclined = false;
                cdm = CallDocModel.fromJson(value.data());
                print("CDM print is: ${cdm.toJson()}");
              }
            });
            currentChannel = userCallDoc['chatRoomId'];

            if (!isDeclined) {
              if (userCallDoc['type'] == 'voiceCall') {
                print("in voice call enabled in accept call of listener event");
                var voiceCallToken = await GetToken().getTokenMethod(
                    userCallDoc['chatRoomId'], auth.currentUser.uid);
                print("token before if in splashscreen is: ${voiceCallToken}");
                if (voiceCallToken != null) {
                  if (g.isDialogOpen) {
                    g.back();
                  }
                  Get.to(
                    () => VoiceCall(
                      toCallName: userCallDoc['requesterName'],
                      toCallImageUrl: userCallDoc['requesterImage'],
                      channelName: userCallDoc['chatRoomId'],
                      token: voiceCallToken,
                      docId: userCallDoc['callsDocId'],
                      callDoc: cdm,
                    ),
                  );
                } else {
                  print(
                      "......Call Accepted background/terminated state....in token being null in voice call enabled in accept call of listener event");
                }
              } else {
                if (g.isDialogOpen) {
                  g.back();
                }
                g.to(() => VideoCallAgoraUIKit(
                      anotherUserName: userCallDoc['requesterName'],
                      anotherUserImage: userCallDoc['requesterImage'],
                      channelName: userCallDoc['chatRoomId'],
                      token: "",
                      anotherUserId: userCallDoc['requesterId'],
                      docId: userCallDoc['callsDocId'],
                      callDoc: cdm,
                    ));
              }
            } else {
              await FlutterCallkitIncoming.endAllCalls();
              print(
                  "the call was either declined by sender or receiver or was timed out.");
            }
          }
        } else {
          debugPrint("Document not found");
        }
      });
    }
  }

In the above code, I have given my db scenario, so anyone, who needs to know how to handle the different call status can deeply look into it. Or you can comment here and I would be honored to reply.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文