简体   繁体   中英

Handling deep links notifications in flutter

I'm trying to wrap my head around external data handling in Flutter application. The idea is following:

  1. An invitation comes as deep link of form https://host/join?id=<id>
  2. Router handles such deep link by opening invites page and calling acceptExternalInvitation() :
class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final notificationBloc = NotificationCubit();
    final dataSharingBloc = IncomingDataBloc(notificationBloc);
    return MultiBlocProvider(
        ...
        child: MaterialApp(
            ...
            home: SafeArea(child: _MyHomePage()),
            onGenerateRoute: (settings) => _getRoute(dataSharingBloc, settings)));
  }

  Route? _getRoute(IncomingDataBloc incomingDataBloc, RouteSettings settings) {
    if (settings.name == null) {
      return null;
    }
    final route = settings.name!;
    if (route.startsWith('/join')) {
      final match = RegExp(r"/join\?id=(.+)$").firstMatch(route);
      if (match != null) {
        incomingDataBloc.acceptExternalInvitation(match.group(1)!);
        return MaterialPageRoute(
            builder: (Navcontext) => InvitesPage(), settings: settings);
      }
    }
    return null;
  }
}

class _MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) =>
      BlocConsumer<NotificationCubit, NotificationState>(
          listener: (context, state) {
            ScaffoldMessenger.of(context)
                .showSnackBar(SnackBar(content: Text(state.message)));
          },
          builder: (context, state) => BlocBuilder<IncomingDataBloc, IncomingDataState>(
              builder: (context, state) => Scaffold(
                  appBar: AppBar(),
                  drawer: _getDrawer(context),
                  body: OverviewPage())));
   ...
}

  1. IncomingDataBloc is responsible for handling all events that come from outside of the application such as deep links here. IncomingDataBloc.acceptExternalInvitation is defined as following:
  void acceptExternalInvitation(String invitationEncodedString) {
      final invitation = MemberInvitation.fromBase64(invitationEncodedString);
      emit(NewAcceptedInviteState(invitation));
      _notificationCubit.notify("Got invitation from ${invitation.from}");
  }

Main point here is emitting the NewAcceptedInviteState(invitation) , which should be handled by the invitations page.

  1. Invitations page in general contains list of invitations and allows to accept or reject them. Invitations coming from deep links should trigger acceptance action:
class InvitesPage extends StatelessWidget {
  final MemberInvitation? invitation;

  InvitesPage({this.invitation});

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: Text("Invitations")),
        body: BlocListener<IncomingDataBloc, IncomingDataState>(
            listener: (context, state) async {
              Logger.root.finest("--------Accepting invitation");
              await _acceptInvite(context, (state as NewAcceptedInviteState).invitation);
              Navigator.pop(context);
            },
            listenWhen: (previous, current) => current is NewAcceptedInviteState,
            child: ...
        )
  );

The problem I have is with the listener in InvitesPage . When I send a link to the application using adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://host/join?id=MY_ID" my.app the handling of the link is performed without problems, the notification from p.3 is shown and the InvitesPage is opened. However the code of listener is not executed.

If however I set a break point in listener code it's hit and code is executed. How can I fix that? Can it be caused by using BlocListener<IncomingDataBloc, IncomingDataState> in home page, which is higher in widgets hierarchy?

And may be more generic question - is such approach of handling external event right one?

Ok, I've figured that out.

Indeed the problem was consuming states in two places. So instead of calling a Bloc method in route, which in my opinion wasn't conceptually correct I just open an invites pages with an invitation provided and let all invitation related logic remain contained in invites page, bloc and state:

  Route? _getRoute(RouteSettings settings) {
    if (settings.name == null) {
      return null;
    }
    final route = settings.name!;
    if (route == '/settings') {
      return MaterialPageRoute(builder: (context) => SettingsPage());
    }
    if (route.startsWith('/join')) {
      final match = RegExp(r"/join\?invitation=(.+)$").firstMatch(route);
      if (match != null) {
        return MaterialPageRoute(
            builder: (Navcontext) =>
                InvitesPage(invitation: MemberInvitation.fromBase64(match.group(1)!)),
            settings: settings);
      }
    }
    return null;
  }
}

only thing I don't really like is handling invitation upon InvitesPage creation. For that I had to make it a stateful widget and implement initState() :

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (invitation != null) {
        context.read<InviteListCubit>().acceptExternalInvitation(invitation!);
      }
    });
  }

But I think it makes sense as state of the page can change with new invitation coming from outside.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM