avioli / uni_links

Flutter plugin for accepting incoming links.
BSD 2-Clause "Simplified" License
564 stars 317 forks source link

Using uni_links and multi page app #64

Open ghost opened 4 years ago

ghost commented 4 years ago

I am trying to open a specific screen when a user clicks a URL link. If the user is on my home page or if the app is fully closed, I manage to intercept the link and show the correct page. If the user has navigated to another page of the app and the app is running (in background for example), clicking the link simply opens the app where the user left it. How can i intercept and parse the link to redirect him to the desired page no matter where he might be in the app ? So i have to copy the code on every single screen of the app ?

Thanks

`import 'dart:async';

import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:myapp/app.config.dart'; import 'package:myapp/mobile/screens/login.dart'; import 'package:myapp/mobile/screens/inviteView.dart';

import 'package:uni_links/uni_links.dart';

/// The goal of this splash screen is to give time to the app /// to retrieve the current database or redirect to the login page. /// We may want to add a different splash screen natively to avoid /// having a white screen when the user launches the app class SplashScreen extends StatefulWidget { const SplashScreen({Key key}) : super(key: key);

@override _SplashScreenState createState() => _SplashScreenState(); }

class _SplashScreenState extends State { Future ready;

Uri _latestUri;

StreamSubscription _sub;

@override void initState() { super.initState(); SystemChannels.textInput.invokeMethod('TextInput.hide'); ready = initPlatformStateForUriUniLinks(); }

@override void dispose() { if (_sub != null) _sub.cancel(); super.dispose(); }

/// An implementation using the [Uri] convenience helpers Future initPlatformStateForUriUniLinks() async { // Attach a listener to the Uri links stream _sub = getUriLinksStream().listen((Uri uri) { if (!mounted) return true; setState(() { _latestUri = uri; }); }, onError: (err) { if (!mounted) return true; setState(() { _latestUri = null; }); });

// Attach a second listener to the stream
getUriLinksStream().listen((Uri uri) {
  print('got uri: ${uri?.path} ${uri?.queryParameters}');
}, onError: (err) {
  print('got err: $err');
});

// Get the latest Uri
Uri initialUri;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
  initialUri = await getInitialUri();
} on PlatformException {
  initialUri = null;
} on FormatException {
  initialUri = null;
}

// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return true;

setState(() {
  _latestUri = initialUri;
});

return true;

}

@override Widget build(BuildContext context) { return FutureBuilder( future: ready, builder: (context, AsyncSnapshot snapshot) { if (snapshot.hasError) return Center(child: Text('Error validating invitation')); if (!snapshot.hasData) return Center(child: Text('Loading'));

    if (_latestUri != null) {
      final queryParams = _latestUri?.queryParameters;
      final config = AppConfig.of(context);
      final baseUrl = config.apiBaseUrl;
      switch (_latestUri.path) {
        case '/acceptInvite/':
          final token = queryParams['token'];
          return AcceptInviteView(baseUrl, token);
          break;
        default:
          break;
      }
    }

    return LoginScreen();
  },
);

}

}`

paulkastel commented 4 years ago

This would be a killer feature! I would love to for example use Navigator.pushReplacementNamed(context, 'address_i_got_from_uni_links'); navigating to specific page in app, without copy-pasting code uni_links handler all over widgets.

matanshukry commented 4 years ago

@charlesrostaing

The link handling should be done in 2 places:

  1. When the app starts. Use getInitialUri. If the app was opened with a uri, it will have the value.
  2. using getUriLinksStream. If the app is already open the stream will have a new value with the link in it. Note: When the user opens a link and your app already exist (in background), Android will first open your app and then you will get the event about the link. This means the user wll see the screen the app was before they moved it to the background, no matter what you do.

I hope that helps!

rohankandwal commented 3 years ago

@

@charlesrostaing

The link handling should be done in 2 places:

  1. When the app starts. Use getInitialUri. If the app was opened with a uri, it will have the value.
  2. using getUriLinksStream. If the app is already open the stream will have a new value with the link in it. Note: When the user opens a link and your app already exist (in background), Android will first open your app and then you will get the event about the link. This means the user wll see the screen the app was before they moved it to the background, no matter what you do.

I hope that helps!

Consider the case of app having Splash screen, Product Listing (HomeScreen), Product details screen & other screens. In this case, the shared urls are of different products which should ideally open in 'Product Details' screen.

If we add listeners in Product Listing, redirection is working fine, either from coming background or app was opened. But, if the user is in Product details & we open different link, nothing will happen unless we add listeners there too.

In such case, which place you suggest of adding listeners? Scenario being, in all cases, regardless of where the user is, redirection should happen, without adding listeners everywhere.

matanshukry commented 3 years ago

@rohankandwal

If we add listeners in Product Listing, redirection is working fine, either from coming background or app was opened. But, if the user is in Product details & we open different link, nothing will happen unless we add listeners there too.

You can have a common parent widget. So your top widget would be something like MyApp, which would have the listener (and other "global" stuff), and the build() method should call the appropriate child builder (HomeScreen, ProductDetailsScreen, etc).