Reputation: 11
I have a trouble with deep links on iOS Flutter. I join at team that need the deep links on ios and they only worked on android. I have 3 classes:
The flutter deeplink bloc
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:fertil_wallet/models/deep_link_route.dart';
import 'package:fertil_wallet/pages/pagar/pago_con_link/pago_con_link.dart';
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';
import 'package:url_launcher/url_launcher_string.dart';
part 'deeplink_event.dart';
part 'deeplink_state.dart';
class DeeplinkBloc extends Bloc<DeeplinkEvent, DeeplinkState> {
//Event Channel creation
static const streamObj = const EventChannel('wallet/events');
final String webDomain;
final TargetPlatform _targetPlatform;
//Method channel creation
static const platform = const MethodChannel('wallet/channel');
DeeplinkBloc(this.webDomain, this._targetPlatform)
: super(DeeplinkState(null, false)) {
startUri();
streamObj.receiveBroadcastStream().listen((d) => _onRedirected(d));
on<LinkRecieved>((event, emit) => emit(DeeplinkState(event.route, false)));
on<OnboardingCompletedExternally>(
(event, emit) => emit(DeeplinkState(null, true)));
}
_onRedirected(String uri) {
// Here can be any uri analysis, checking tokens etc, if it’s necessary
// Throw deep link URI into the BloC's stream
var uriSections = uri.split("/");
var i = uriSections.indexOf(webDomain);
if (i == -1) return; //no es un link de el dominio de la app
uriSections = uriSections.sublist(i + 1, uriSections.length);
if (uriSections[0] != "app") return;
DeepLinkRoute? route;
switch (uriSections[1]) {
case "lc":
route = DeepLinkRoute(
route: PagoConLinkPage.route,
arguments: uriSections.length >= 2 ? uriSections[2] : "",
);
this.add(LinkRecieved(route));
return;
case "ob":
this.add(OnboardingCompletedExternally());
return;
case "recover_password":
case "account_activation":
launchUrlString(uri, mode: LaunchMode.externalApplication);
return;
default:
return;
}
}
Future<void> startUri() async {
try {
//if (_targetPlatform != TargetPlatform.iOS) {
var url = await platform.invokeMethod('nani');
_onRedirected(url);
// return url;
} on PlatformException catch (e) {
return "Failed to Invoke: '${e.message}'." as Future;
} catch (e) {
return "Failed to Invoke: '$e'." as Future;
}
}
}
after that I have the android native class:
package com.ayj.wallet
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterFragmentActivity
// import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.EventChannel.EventSink
import android.content.Intent
import android.content.Context
import android.content.BroadcastReceiver
class MainActivity: FlutterFragmentActivity () {
private val CHANNEL = "wallet/channel"
private val EVENTS = "wallet/events"
private var startString: String? = null
private var linksReceiver: BroadcastReceiver? = null
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
val intent2 = getIntent()
startString = intent2.data?.toString()
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "nani") {
if (startString != null) {
result.success(startString)
}
}
}
EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENTS).setStreamHandler(
object : EventChannel.StreamHandler {
override fun onListen(args: Any?, events: EventSink) {
linksReceiver = createChangeReceiver(events)
}
override fun onCancel(args: Any?) {
linksReceiver = null
}
}
)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.action === Intent.ACTION_VIEW) {
linksReceiver?.onReceive(this.applicationContext, intent)
}
}
fun createChangeReceiver(events: EventSink): BroadcastReceiver? {
return object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { // NOTE: assuming intent.getAction() is Intent.ACTION_VIEW
val dataString = intent.dataString ?:
events.error("UNAVAILABLE", "Link unavailable", null)
events.success(dataString)
}
}
}
}
and the last it's a appdelegate class that I put the code inside:
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
private let CHANNEL = "wallet/channel"
private let EVENTS = "wallet/events"
private var startString: String?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
let flutterViewController: FlutterViewController = window?.rootViewController as! FlutterViewController
let methodChannel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: flutterViewController.binaryMessenger)
methodChannel.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: FlutterResult) in
if call.method == "nani" {
if let startString = self?.startString {
result(startString)
}
}
}
let eventChannel = FlutterEventChannel(name: EVENTS, binaryMessenger: flutterViewController.binaryMessenger)
eventChannel.setStreamHandler(StreamHandler())
// Handling deep link if the app is opened via a deep link
if let url = launchOptions?[.url] as? URL {
processDeepLink(url)
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func application(_ app: UIApplication, open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
processDeepLink(url)
return true
}
private func processDeepLink(_ url: URL) {
startString = url.absoluteString
if let flutterViewController = window?.rootViewController as? FlutterViewController {
let methodChannel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: flutterViewController.binaryMessenger)
methodChannel.invokeMethod("nani", arguments: nil) { (result) in
print("Deep link processed: \(String(describing: result))")
}
}
}
}
class StreamHandler: NSObject, FlutterStreamHandler {
var eventSink: FlutterEventSink?
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSink = events
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
eventSink = nil
return nil
}
}
The problem is: iOS app not redirect me to a view, appdelegate can't get the deeplink and sent of flutter
When I debug the lines on bloc go well but on method _onRedirect iOS never entry. If I send a deeplink on out of the app always sent me to home and can't redirect, when I try the same on android work well. I have all configurations (apple-app-site-association, associated domains, EnableFlutterLink on Yes)
Upvotes: 1
Views: 287
Reputation: 11
I think go_router is the best tool for implementing deeplinks in flutter, I've used it myself and it worked well on Android and IOS.
Upvotes: 0
Reputation: 233
make sure you have defined you DeepLink Path in info.plist (ios\Runner\Info.plist) file same as in AndroidManifest.xml (android\app\src\main\AndroidManifest.xml)
Upvotes: 0