0

We inherited 'MarketingCloudSDKURLHandlingDelegate' for the class that runs MobilePush for Marketing Cloud and overrode the methods. We wanted the following method to be called as handler when we tried opening the push notification: "public func sfmc_handle(URL(string: userinfo["_od"] as! String), type: String)" However, method "self.sfmc_handle(_ url: URL, type: SFMCURLTypeOpenDirect) " is called explicitly instead.

With the explicitly called method mentioned right above, when we open the push notification and the app runs in the background, opening another push notification causes the following error: sfmc_setNotificationRequest called before the SDK is ready. Deferring until isReady == true.

isReady' in 'MarketingCloudSDK+Base.h' is initialized by SDK, so when we try to use, its value becomes 'true'. What is the situation in which its value becomes 'false'?

Also, 'sfmc_handle(URL(string: userinfo["_od"] as! String), type: String)' , is it supposed to be explicitly called?

Implementation code

import Flutter
import UIKit
import MarketingCloudSDK
import AdSupport


public class SwiftFlutterSmcSdkPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, UNUserNotificationCenterDelegate, 
MarketingCloudSDKURLHandlingDelegate{ 
    private var eventSink: FlutterEventSink?
    let EVENT_NAME:String = "eventName"
    let EVENT_RESULT:String = "eventResult"
    let ERROR_EVENT_NAME:String = "errorEventName"
    var articleUrl: String?
    enum EVENT_NAMES {
        static let onBehaviorOfAdvertisingIdCondition = "onBehaviorOfAdvertisingIdCondition"
        static let onBehaviorOfNotificationMessageDisplayed = "onBehaviorOfNotificationMessageDisplayed"
        static let onRequestAdvertisingId = "onRequestAdvertisingId"
    }

    public static func register(with registrar: FlutterPluginRegistrar) {
        let channel = FlutterMethodChannel(name: "flutter_smc_sdk_plugin_method_channel", binaryMessenger: registrar.messenger())
        let eventchannel = FlutterEventChannel.init(name: "flutter_smc_sdk_plugin_event_channel", binaryMessenger: registrar.messenger())
        let instance = SwiftFlutterSmcSdkPlugin()

        MarketingCloudSDK.sharedInstance().sfmc_setURLHandlingDelegate(instance as MarketingCloudSDKURLHandlingDelegate)

        registrar.addMethodCallDelegate(instance, channel: channel)
        registrar.addApplicationDelegate(instance)
        eventchannel.setStreamHandler(instance)
        if #available(iOS 10.0, *) {
            let center = UNUserNotificationCenter.current()
            center.delegate = instance
            print("initialize iOS UNNUserNotification Delegate")
        } else {
        }
        print("Mens App: [App Call Method] :: Flutter Plugin Registretion")
    }


    public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        //debugPrint(call.method, call.arguments)
        switch call.method {
        case "getPlatformVersion": getPlatformVersion(result)
        case "registerMemberId": registerMemberId(call: call)
        case "getContactKey": self.getContactKey(result: result)
        case "isSmcSdkStartupSuccess": isSmcSdkStartupSuccess(result: result)
        case "requestPermissionForPushNotification": requestNotificationPermission()
        case "getAdvertisingId": getAdvertisingId(result: result)
        default: result(nil)
        }
    }


    public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [AnyHashable : Any] = [:]) -> Bool {
        NSLog("LaunchOptionsKey : \(launchOptions.description as? [AnyHashable : Any])")

        if launchOptions[UIApplicationLaunchOptionsKey.remoteNotification] != nil {
            NSLog("PushNotification Tap Lunch")
            let userInfo =  launchOptions[UIApplicationLaunchOptionsKey.remoteNotification] as? [AnyHashable : Any]
            NSLog("UserInfo : \(String(describing: userInfo?.values))")
            articleUrl = userInfo?["_od"] as! String?

            if articleUrl == nil{
                return true
            }else{
                DispatchQueue.main.asyncAfter( deadline: DispatchTime.now() + 5){
                    self.sfmc_handle(URL(string: userInfo!["_od"] as! String), type: SFMCURLTypeOpenDirect)
                }
            }
        }
        return true
    }

    public func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) -> Bool {
        switch application.applicationState {
        case .inactive:
            NSLog("I tap the Push notification from the state where the app is closed")
            NSLog("Application Active")
            if articleUrl != nil && articleUrl != ""{
                NSLog("running OpenUrl")
                DispatchQueue.main.async {
                    self.sfmc_handle(URL(string: self.articleUrl ?? ""), type: SFMCURLTypeOpenDirect)
                }
            }
        case .active:
            break
        case .background:
            break
        }
        completionHandler(UIBackgroundFetchResult.noData)
        return true
    }

    public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        print("called :: onListen")
        self.eventSink = events
        return nil
    }

    public func onCancel(withArguments arguments: Any?) -> FlutterError? {
        print("called :: onCancel")
        eventSink = nil
        return nil
    }

    public func requestNotificationPermission() -> Bool {

        let sdk : MarketingCloudSDK = MarketingCloudSDK.sharedInstance()
        let advertising_id = ASIdentifierManager.shared().advertisingIdentifier.uuidString
        var error : NSError? = nil
        sdk.sfmc_tearDown()
        let isSuccess : Bool = sdk.sfmc_configure(&error)
        let userDeaults = UserDefaults.standard
        userDeaults.set(isSuccess, forKey: "isSmcSdkStartupSuccess")

        print("Error ::: \(String(describing: error?.domain.description))")
        if isSuccess == true {
            // The SDK has been fully configured and is ready for use!
            // turn on logging for debugging. Not recommended for production apps.
            sdk.sfmc_setDebugLoggingEnabled(true)
            //DispatchQueue.global(qos: .default).async {}
            DispatchQueue.main.async (execute:{
                // configure the Marketing Cloud SDK
                if #available(iOS 10.0, *) {
                    // iOS 10
                    UNUserNotificationCenter.current().delegate = self
                    UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .sound, .alert], completionHandler: { (granted, error) in
                        if error != nil {
                            return
                        }
                        if granted {
                            DispatchQueue.main.async {
                                UIApplication.shared.registerForRemoteNotifications()
                            }
                            print("set app_device_id")
                            sdk.sfmc_setAttributeNamed("app_device_id", value: advertising_id)

                        } else {
                        }
                    })
                } else {
                    // iOS 9
                    let settings = UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil)
                    UIApplication.shared.registerUserNotificationSettings(settings)
                }
            })
        }else {
            print("MarketingCloudSDK sfmc_configure failed with error = \(String(describing: error?.localizedDescription))", error!)
        }
        return isSuccess;
        //GeneratedPluginRegistrant.register(with: self)
        //print("isRegisteredForRemoteNotifications :::  \(application.isRegisteredForRemoteNotifications)")
    }

    private func getPlatformVersion(_ result: @escaping FlutterResult) {
        result("iOS " + UIDevice.current.systemVersion)
    }

    private func registerMemberId(call: FlutterMethodCall) {
        guard
            let args = call.arguments as? [String: Any]
            else {
                return
        }

        let memberId :String? = args["memberId"] as? String

        let contactNumber :String? = args["contactNumber"] as? String

        let hogeCardRank :String? = args["hogeCardRank"] as? String

        let sdk : MarketingCloudSDK = MarketingCloudSDK.sharedInstance()
        let advertising_id = ASIdentifierManager.shared().advertisingIdentifier.uuidString

        if(memberId != nil){

            debugPrint("set sfmc_setContactKey")

            sdk.sfmc_setContactKey(memberId!)

            if(contactNumber != nil){
                debugPrint("set sfmc_setAttributeNamed :: otiaws_no :: ")
                sdk.sfmc_setAttributeNamed("otiaws_no", value: contactNumber!)
            }

            if(hogeCardRank != nil){
                debugPrint("set sfmc_setAttributeNamed :: gcp_stage ::")
                sdk.sfmc_setAttributeNamed("gcp_stage", value: hogeCardRank!)
            }
        }

        debugPrint("set sfmc_setAttributeNamed ::  app_device_id ::  \(hogeCardRank)")
        sdk.sfmc_setAttributeNamed("app_device_id", value: advertising_id)

    }

    func identifierForAdvertising() -> String? {

        // Check whether advertising tracking is enabled
        print("AdvertisingFlag ::: \(ASIdentifierManager.shared().isAdvertisingTrackingEnabled)")

        // Check whether advertising tracking is enabled
        if ASIdentifierManager.shared().isAdvertisingTrackingEnabled {
            var dic = Dictionary<NSObject, NSObject>()
            dic[EVENT_NAME as NSObject] = EVENT_NAMES.onBehaviorOfAdvertisingIdCondition as NSObject
            dic[EVENT_RESULT as NSObject] = ASIdentifierManager.shared().isAdvertisingTrackingEnabled as NSObject
            eventSink!(dic)
        }else{
            var dic = Dictionary<NSObject, NSObject>()
            dic[EVENT_NAME as NSObject] = EVENT_NAMES.onBehaviorOfAdvertisingIdCondition as NSObject
            dic[EVENT_RESULT as NSObject] = ASIdentifierManager.shared().isAdvertisingTrackingEnabled as NSObject
            eventSink!(dic)
        }

        // Get and return IDFA
        return ASIdentifierManager.shared().advertisingIdentifier.uuidString
    }

    private func isSmcSdkStartupSuccess (result: @escaping FlutterResult) -> Bool {
        let userDeaults = UserDefaults.standard
        print("call method  isSmcSdkStartupSuccess")
        print("sdk status ::: \(userDeaults.bool(forKey: "isSmcSdkStartupSuccess"))")
        result(userDeaults.bool(forKey: "isSmcSdkStartupSuccess"))
        return userDeaults.bool(forKey: "isSmcSdkStartupSuccess")
    }

    private func getContactKey(result: @escaping FlutterResult){
        result(MarketingCloudSDK.sharedInstance().sfmc_contactKey()!)
    }

    private func getAdvertisingId(result: @escaping FlutterResult){
        result(ASIdentifierManager.shared().advertisingIdentifier.uuidString)
    }


    @available(iOS 10.0, *)
    public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        print(response.notification.request.content.userInfo)
        NSLog("Tap Push Notification Center")
        let userinfo = response.notification.request.content.userInfo
        let sdk : MarketingCloudSDK = MarketingCloudSDK.sharedInstance()
        sdk.sfmc_setNotificationRequest(response.notification.request)

        if userinfo["_od"] != nil {
            self.sfmc_handle(URL(string: userinfo["_od"] as! String), type: SFMCURLTypeOpenDirect)
        }

        completionHandler()
    }

    @available(iOS 10.0, *)
    public func userNotificationCenter(_ center: UNUserNotificationCenter,
                                       willPresent notification: UNNotification,
                                       withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void) {

         print(notification.request.content.userInfo)
         let userinfo = notification.request.content.userInfo
         let sdk : MarketingCloudSDK = MarketingCloudSDK.sharedInstance()
         sdk.sfmc_setNotificationRequest(notification.request)

         if userinfo["_od"] != nil {
         sfmc_handle(URL(string: userinfo["_od"] as! String), type: SFMCURLTypeOpenDirect)
         }*/

        completionHandler([.alert, .sound])
        //...
    }


    public func application(_ application: UIApplication, open url: URL, sourceApplication: String, annotation: Any) -> Bool {

        print("url : \(url.absoluteString)")
        print("scheme : \(url.scheme!)")
        print("host : \(url.host!)")
        print("port : \(url.port!)")
        print("query : \(url.query!)")

        return true
    }


    func notifiPush(userInfo:[NSObject : AnyObject]){
        var dic = Dictionary<NSObject, NSObject>()
        dic[EVENT_NAME as NSObject] = EVENT_NAMES.onBehaviorOfNotificationMessageDisplayed as NSObject
        dic[EVENT_RESULT as NSObject] = userInfo as NSObject
        eventSink!(dic)
    }

    public func sfmc_handle(_ url: URL!, type: String) {

        if try! NSRegularExpression(pattern: "https?://([-\\w\\.]+)+(:\\d+)?(/([\\w/_\\.]*(\\?\\S+)?)?)?", options: NSRegularExpression.Options(rawValue: 0)).isEqual(url){
            return
        }

        if let anUrl = url {
            if UIApplication.shared.canOpenURL(anUrl) == true {
                if #available(iOS 10.0, *) {
                    print("url ::: \(url)")
                    let customURL: URL = (URL(string: "jp.hoge.huga://open-direct/" + url.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)!)

                    UIApplication.shared.open(customURL, options: [:], completionHandler: { success in
                        if success {
                            NSLog("UIApplication.shared.open url \(customURL) opened successfully")
                            print("url \(customURL) opened successfully")
                        } else {
                            NSLog("UIApplication.shared.open url \(customURL) could not be opened")
                            print("UIApplication.shared.open url \(customURL) could not be opened")
                        }
                    })
                } else {
                    if UIApplication.shared.openURL(anUrl) == true {
                        if url != nil {
                            NSLog("UIApplication.shared.openURL url \(anUrl) opened successfully")
                            print("UIApplication.shared.openURL url \(anUrl) opened successfully")
                        }
                    } else {
                        NSLog("UIApplication.shared.openURL url \(anUrl) could not be opened")
                        print("UIApplication.shared.openURL url \(anUrl) could not be opened")
                    }
                }
            }
        }
    }
}

1 Answer 1

0

First: sfmc_handleURL:type: is designed to be called by the SDK as part of the MarketingCloudSDKURLHandlingDelegate protocol, defined and documented in MarketingCloudSDK+URLHandling.h. It is not meant to be explicitly called.

Your SwiftFlutterSmcSdkPlugin class implements the method, thereby adhering to the protocol.

Your class also sets the MobilePush url handling delegate via sfmc_setURLHandlingDelegate, which would allow the SDK to call the delegate method you have implemented.

What I cannot determine (from the code above) is the order in which you make calls to the SDK. In all cases, you must call sdk.sfmc_configure(&error) prior to making additional calls to the SDK (like setting the URL handling delegate).

As you are handling a notification prior to the SDK being configured, and then calling sfmc_setNotificationRequest, the MobilePush SDK is going to defer handling it until you've successfully configured, which based on your code may not happen until you call requestNotificationPermission.

It looks like you've followed our implementation docs somewhat. I encourage you to move your SDK configuration to earlier in your app lifecycle to avoid this async behavior - i.e. into register and before you set the URL handling delegate.

Best,

Brian

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .