Mball
Mball

Reputation: 11

Flutter DeepLink work on android but not in iOS

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

Answers (2)

Zounedou
Zounedou

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

Shoua Ul Qammar
Shoua Ul Qammar

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

Related Questions