0

Greeting, I have a really specific question to ask. I have to explain it with steps and pictures so there they are. I have an app with three screens:

Main Feed Screen, enter image description here

Main Chat and Requests Screen, enter image description here enter image description here

Main Profile Screen, enter image description here

And they are all a part of a PageView. This PageView class is controlled inside of a class called main_tab_controller.dart. In that class, in initState(), I have a Firebase Messaging method that is called every time I get a notification (onMessage). So, every time I get this notification, I show an overlay that looks like this.

enter image description here

And it works perfectly on these three main screen. If it's a chat notification, I will direct the PageView to the second screen i.e MainChatAndRequest Screen, and open the chat screen. If it's a request notification, I will direct the PageView to the second screen i.e MainChatAndRequest Screen, and open the requests screen.

But the issue that I am having is the following. In my MainFeedScreen and MainProfileScreen, I have some other screens that I open. For example in MainFeedScreen, I open UserDetailsScreen or FilterScreen. Or in the MainProfileScreen, I open SettingsScreen or EditUserProfileScreen.

So my question is: For example, if I navigate to MainProfileScreen and in that screen open SettingsScreen, and I get the overlay top message, how do I close the SettingsScreen that is currently open and navigate back to the second screen i.e MainChatsAndRequestsScreen from the Firebase Messaging Function that is in initState() of main_tab_controller.dart that is the parent to all of the other screens. You have the Image Below:

enter image description here

I have tried everything, Navigator.popUntil(context), Navigator.pushReplacement(context), used Navigator.pushNamed(context) but nothing worked. If someone can help me, it would be much appreciated.

Just to give you the better undertanding of the screens: The Parent Screen is the PageView with three screens:

  1. Main Feed Screen
  2. Main Chat and Requests Screen
  3. Main Profile Screen

and then in Main Feed Screen you have:

  1. Filters Screen
  2. Profile Details Screen

in Main Chat and Requests Screen you have two TabBar Screens:

  1. Chats Screen
  2. Requests Screen

and in Main Profile Screen you have:

  1. Settings Screen
  2. Edit Profiles Screen

PageView Code Snippet:

    @override
  void initState() {
    pageController = PageController(initialPage: _currentIndex);
    chatAndRequestController = TabController(length: 2, vsync: this);
    var chatAndRequestProvider =
        Provider.of<ChatAndRequestProvider>(context, listen: false);
    super.initState();
    fbm.requestNotificationPermissions();
    fbm.configure(
      onMessage: (Map<String, dynamic> message) async {
        print("onMessage: $message");
        bool isRequest;
        var mode = (Platform.isIOS) ? message['mode'] : message['data']['mode'];
        var imageUrl = '';
        switch (mode) {
          case 'chat':
            isRequest = false;
            imageUrl =
                chatAndRequestProvider.chatsList.first[kProfilePictureUrl];
            break;
          case 'sentRequest':
            isRequest = true;
            imageUrl = (Platform.isIOS)
                ? message['profilePictureUrl']
                : message['data']['profilePictureUrl'];
            break;
          case 'acceptRequest':
            isRequest = false;
            imageUrl = (Platform.isIOS)
                ? message['profilePictureUrl']
                : message['data']['profilePictureUrl'];
            break;
          default:
            isRequest = false;
            break;
        }
        AudioCache player = new AudioCache();
        const alarmAudioPath = "sounds/notification_sound.mp3";
        player.play(alarmAudioPath);
        print('Show this ting');
        if (_currentIndex != 1) {
          if (!isDialogOpen) {
            isDialogOpen = true;
            _showAnimatedBox(
              context,
              (Platform.isIOS)
                  ? message['aps']['alert']['title']
                  : message['notification']['title'],
              (Platform.isIOS)
                  ? message['aps']['alert']['body']
                  : message['notification']['body'],
              imageUrl,
              isRequest,
            );
          }
        }
      },
      onLaunch: (Map<String, dynamic> message) async {
        print("onLaunch: $message");
      },
      onResume: (Map<String, dynamic> message) async {
        print("onResume: $message");
      },
    );
    notificationPlugin
        .setListenerForLowerVersions(onNotificationInLowerVersions);
    notificationPlugin.setOnNotificationClick(onNotificationClick);
    _children.addAll([
      MainFeedScreen(
        analytics: widget.analytics,
        observer: widget.observer,
        latitude: widget.latitude,
        longitude: widget.longitude,
      ),
      MainChatAndRequestScreen(
        analytics: widget.analytics,
        observer: widget.observer,
        pageContoller: chatAndRequestController,
      ),
      MainProfileScreen(analytics: widget.analytics, observer: widget.observer),
    ]);
  }

Future _showAnimatedBox(context, topText, bottomText, imageUrl, isRequest) {
    showDialog(
        context: context,
        builder: (BuildContext builderContext) {
          _timer = Timer(Duration(seconds: 4), () {
            Navigator.of(context).pop();
            isDialogOpen = false;
          });
          return Dismissible(
            key: Key('dismissible'),
            direction: DismissDirection.up,
            onDismissed: (_) {
              Navigator.of(context).pop();
              isDialogOpen = false;
            },
            child: FunkyNotification(
              () {
                var chatAndRequestProvider =
                    Provider.of<ChatAndRequestProvider>(context, listen: false);
                // var contextProv =
                //     Provider.of<ContextProvider>(context, listen: false);
                chatAndRequestProvider.setAreThereNewChatsAndRequestFalse();
                if (isRequest) {
                  pageController.jumpToPage(1);
                  chatAndRequestController.animateTo(1);
                  Navigator.of(context).pop();
                  // Navigator.of(contextProv.context).pop();
                  // SystemChannels.platform.invokeMethod('SystemNavigator.pop');
                  // Navigator.popUntil(
                  //  context,
                  //  ModalRoute.withName('/mainProfileScreen'),
                  // );
                  // Navigator.of(context)
                  //     .popUntil(ModalRoute.withName('/mainProfileScreen'));
                  // Navigator.pushAndRemoveUntil(
                  //   context,
                  //   MaterialPageRoute(
                  //     builder: (BuildContext context) => MainTabBarController(
                  //       analytics: null,
                  //       observer: null,
                  //       latitude: 100.23423234,
                  //       longitude: 12.324234234,
                  //       isProfileBlocked: false,
                  //       isVersionGood: true,
                  //     ),
                  //   ),
                  //   (route) => true,
                  // );
                } else {
                  var chatAndRequestProvider =
                      Provider.of<ChatAndRequestProvider>(context,
                          listen: false);
                  pageController.jumpToPage(1);
                  chatAndRequestController.animateTo(0);
                  Navigator.of(context).pop();
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => ChatScreen(
                        appMode:
                            chatAndRequestProvider.chatsList.first[kAppMode],
                        peerId: chatAndRequestProvider.chatsList.first[kUserId],
                        peerAvatar: chatAndRequestProvider
                            .chatsList.first[kProfilePictureUrl],
                        peerName: chatAndRequestProvider
                            .chatsList.first[kNameAndSurname],
                        friendshipStatus: chatAndRequestProvider
                            .chatsList.first['friendsStatus'],
                        analytics: widget.analytics,
                        observer: widget.observer,
                      ),
                    ),
                  );
                }
              },
              topText,
              bottomText,
              imageUrl,
            ),
          );
        }).then((val) {
      if (_timer.isActive) {
        _timer.cancel();
      }
      isDialogOpen = false;
    });
  }

1 Answers1

1

I will try make my answer as general as possible in order to make it easier for others to follow along.

The problem in a nutshell is that you have a nested set of screens distributed between a set of pageviews, and you want to switch between the pageviews from an external event (The overlay in this case).

Below is an example:

Structure


TL;DR

I couldn't provide the full code since I don't have your full source code. But here is an example

Note: This example uses Provider.

Sample Event Code

  // Remove all the screens in the route
  Navigator.of(context).popUntil((route) => route.isFirst); // If the screen is not the first replace the check

  // Change the second pageview page
  Provider.of<ChatSelectPageView>(context, listen: false).setPage(selectedSecondPageViewPage);

  // In case it is required to add intermediate screens between the first and the second pageview it must be added here

  // Change the main pageview page
  _mainPageViewcontroller.animateToPage(1);

Second PageView

  // Reads the page index present in the provider
  int selectedPage = Provider.of<ChatSelectPageView>(context, listen: false).page;

  // Changes to the cotroller page to the selected page
  _pageController.jumpToPage(selectedPage);

ChatSelectPageView

  class ChatSelectPageView extends ChangeNotifier {
    int page = 0;
  
    void setPage(int _page) {
      page = _page;

      // Depending on your implementation it might be better to remove this line to reduce the number of builds
      notifyListeners();
    }

  }

TS;WM

In order to achieve the desired behavior, there is multiple ways to achieve it. If we want to stick to your implementation we will be a bit constrained. But in this case what I would suggest you do is to use some kind of global state management library such as provider, it can be done without any library but the state will get very messy very quickly.

As you mentioned above you tried Navigator.popUntil but it didn't work, I suspect the reason for this is that you are providing the wrong context. Since Navigator.**** relies on the context to work, i.e. to pop a screen you must provide its context. Or the route check is wrong.

This code is to be written in the external event in your case it will be written in the click listener of the overlay.

Use a state management solution such as Provider to pass the state to the descendants of the main Pageview down to the screens. This provider will be of type ChangeNotifierProvider. When the overlay is clicked, a flag will be set to be the desired pageview page index (I am speaking about the 2nd pageview). In your case this flag is used to select chats or requests.

After that is done you call Navigator.of(context).popUntil((route) => route.isFirst); assuming that the pageview is present on the first page of your app. In the case where it is not on that page, you will have to use Navigator.of(context).popUntil() with a custom logic.

After that we will have to navigate back to the 2nd pageview, or change the first pageview to be the 2nd page in your case. The second pageview will be already switched since we changed the flag in provider before.

Mohammad Kurjieh
  • 1,043
  • 7
  • 16
  • This code works wonderful, I have implemented the Provider before, can you just say to me can I pop here to the second route: Instead of Navigator.of(context).popUntil((route) => route.isFirst); (isFirst) can it be Navigator.of(context).popUntil((route) => route.isSecond) (isSecond), this would solve my problem, because the first route loads me back to the loading screen that is initial screen. Thank Brother Mohammad soo Much! May Allah bless You! – Bilal Drndo Jan 09 '21 at 23:15
  • Brother, I solved it using this code count = 0; Navigator.popUntil(context, (route) { return count++ == 2; }); I will make now just a provider with taps where the index is and it will work like a charm. Thank You!!! You lead me to the right direction. I hope this becomes useful to others. – Bilal Drndo Jan 09 '21 at 23:24
  • This code that you proposed will work but not always, since this code will always remove 2 screens, i.e: it is not returning to the second screen! If you want a robust solution, I suggest you to use named routes, example [Link](https://stackoverflow.com/a/63479873/6785250). Most welcome, I am glad I was able to help . – Mohammad Kurjieh Jan 09 '21 at 23:35