What are deep links?
Deep links are a type of link that send users directly to an app instead of a website.
Why we need this?
To provide a seamless user experience in both web and mobile. Another reason is that when a business has a web site and a mobile app, it can escape the “Mobile first” web design patters by using deep links.
Implementation
find AndroidManifest.xml
file under android/app/src/main
Add these links inside <activity>
<intent-filter android:label="InviteLink">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="only4.dev"
android:pathPrefix="/invite"
/>
</intent-filter>
Android operating systems provides APIs to register certain action to some apps, for example when we click on a mp3
file Android prompts user to select an app from the list of supported apps.
If I click on a link which starts from only4.dev/invite
it will always prompt to open this link with the mobile app.
Lets handle the link in the app
There can be 2 types of events:
- When the app is not running
- when the app is already running.
We create 2 types of messages for that and also create a Broadcast receiver.
A broadcast receiver (receiver) is an Android component which allows you to register for system or application events. All registered receivers for an event are notified by the Android runtime once this event happen
private static final String CHANNEL = "initial";
private static final String EVENTS = "eventWhileAppIsRunning";
private String startString;
private BroadcastReceiver linksReceiver;
Let’s handle the cases. We create a new MethodChanenel
for the 1st case and new EventChannel
for the 2nd case.
On the Android side MethodChannel
Android (API) and on the iOS side FlutterMessageChannel
(API) is used for receiving method calls and sending back result. If required, method calls can also be sent in the reverse direction, with the Android/IOS platform acting as client and method implemented in Dart.
An EventChannel
is used when you want to stream data. This results in having a Stream on the Dart side of things and being able to feed that stream from the native side. (In other words when the app is already running).
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
Intent intent = getIntent();
Uri data = intent.getData();
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
if (call.method.equals("initialLink")) {
if (startString != null) {
result.success(startString);
}
}
}
}
);
new EventChannel(getFlutterView(), EVENTS).setStreamHandler(
new EventChannel.StreamHandler() {
@Override
public void onListen(Object args, final EventChannel.EventSink events) {
linksReceiver = createChangeReceiver(events);
}
@Override
public void onCancel(Object args) {
linksReceiver = null;
}
}
);
if (data != null) {
startString = data.toString();
if(linksReceiver != null) {
linksReceiver.onReceive(this.getApplicationContext(), intent);
}
}
}
Take your time to read and understand this.
ake your time to understand this if you need to implement deep links.
here is final MainActivity.java
package my.app.com;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "initial";
private static final String EVENTS = "eventWhileAppIsRunning";
private String startString;
private BroadcastReceiver linksReceiver;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
Intent intent = getIntent();
Uri data = intent.getData();
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
if (call.method.equals("initialLink")) {
if (startString != null) {
result.success(startString);
}
}
}
}
);
new EventChannel(getFlutterView(), EVENTS).setStreamHandler(
new EventChannel.StreamHandler() {
@Override
public void onListen(Object args, final EventChannel.EventSink events) {
linksReceiver = createChangeReceiver(events);
}
@Override
public void onCancel(Object args) {
linksReceiver = null;
}
}
);
if (data != null) {
startString = data.toString();
if(linksReceiver != null) {
linksReceiver.onReceive(this.getApplicationContext(), intent);
}
}
}
@Override
public void onNewIntent(Intent intent){
super.onNewIntent(intent);
if(intent.getAction() == android.content.Intent.ACTION_VIEW && linksReceiver != null) {
linksReceiver.onReceive(this.getApplicationContext(), intent);
}
}
private BroadcastReceiver createChangeReceiver(final EventChannel.EventSink events) {
return new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// NOTE: assuming intent.getAction() is Intent.ACTION_VIEW
String dataString = intent.getDataString();
if (dataString == null) {
events.error("UNAVAILABLE", "Link unavailable", null);
} else {
events.success(dataString);
}
}
};
}
}
Next step is to pass these events to flutter code
We create a StreamController()
in a DeepLinkBloc
class.
Create the constructer for this class
DeepLinkBloc() {
startUri().then(_onRedirected);
stream.receiveBroadcastStream().listen((d) => _onRedirected(d));
}
When ever we receive an event it calls the _onRedirected
method. Now we finally have the initial link in the flutter code.
Here’s the full code for the class.
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
abstract class Bloc {
void dispose();
}
class DeepLinkBloc extends Bloc {
DeepLinkBloc() {
startUri().then(_onRedirected);
stream.receiveBroadcastStream().listen((d) => _onRedirected(d));
}
// Initial event channel
static const platform = MethodChannel('initial');
// Runtime Event channel
static const stream = EventChannel('eventWhileAppIsRunning');
final StreamController<String> _stateController = StreamController();
Stream<String> get state => _stateController.stream;
Sink<String> get stateSink => _stateController.sink;
//Adding the listener into constructor
void _onRedirected(String uri) {
debugPrint(uri);
stateSink.add(uri);
}
@override
void dispose() {
_stateController.close();
}
Future<String> startUri() async {
try {
return platform.invokeMethod('initialLink');
} on PlatformException catch (e) {
return "Failed to Invoke: '${e.message}'.";
}
}
}
Now simply wrap the widgets in Provider<DeepLinkBloc>.value(value: _bloc)
where you want to use the links.
Lets create a new widget which uses this provider.
class DeepLinkWrapper extends StatelessWidget {
@override
Widget build(BuildContext context) {
final _bloc = Provider.of<DeepLinkBloc>(context);
return StreamBuilder<String>(
stream: _bloc.state,
builder: (context, snapshot) {
// if app is started normally, no deep link is clicked show your old home page widget
if (!snapshot.hasData) {
return Container(
child: const HomePage(),
);
} else {
final splitInviteLink = snapshot.data.split('/');
final inviteToken = splitInviteLink[splitInviteLink.length - 1];
return RegisterStartPage(key: UniqueKey(),inviteToken: inviteToken,);
}
});
}
}
Just this last change and we are good to go. Make this modification in your main.dart
file.
home: Scaffold(
body: Provider<DeepLinkBloc>(
builder: (context) => _bloc,
dispose: (context, bloc) => bloc.dispose(),
child: DeepLinkWrapper()
)
)
Here is the code for the final MyApp
class in main.dart.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final _bloc = DeepLinkBloc();
return MultiProvider(
providers: [
Provider<DeepLinkBloc>.value(value: _bloc),
// ...other providers
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: const [
Locale('de'),
Locale('en'),
],
routes: {
HomePage.route: (context) => const HomePage(),
LoginPage.route: (context) => LoginPage(),
},
home: Scaffold(
body: Provider<DeepLinkBloc>(
create: (context) => _bloc,
dispose: (context, bloc) => bloc.dispose(),
child: DeepLinkWrapper()
)
)
),
);
}
}
Top comments (4)
Well, there is a package that does just that with support for iOS too: pub.dev/packages/uni_links
how that _bloc comes in main file , it gives undefined in my code,
please reply ASAP
@sahyog I updated the post with full code of
MyApp
class.exist some gitHub project about this topic? for example this complete example?