Migration Guides

Migrating to Rover 4 from Rover Campaigns 3.x and Judo 1.x - iOS

Welcome to Rover 4.

The purpose of this guide is to aid you in adopting Rover 4 from your existing Rover (sometimes referred to as Rover Campaigns) 3.x and Judo 1.x setup. The Rover 4 SDK supersedes both the Rover Campaigns 3.x and Judo 1.x SDKs.


Initialization changes from Judo to Rover 4

The Rover SDK 4.0 is the successor to the Judo SDK. Judo experiences are now included as a core component of the Rover SDK. There is no longer a need to initialize a second SDK.

To remove the Judo SDK from the project, follow these steps:

  1. Select the project in the Project Navigator.
  2. Click on the "Package Dependencies" tab.
  3. Select the Judo package.
  4. Click on the "-" button to remove it.

Removing the package from the project will also remove the frameworks from all the targets in the project.

Remove the initialization code:

import JudoSDK
import JudoModel

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let configuration = Configuration(accessToken: "<YOUR_JUDO_SDK_TOKEN>", domain: "<YOUR_JUDO_DOMAIN>")
    Judo.initialize(configuration: configuration)
}

Universal and deep link handling of Judo links will now be entirely handled through the Rover SDK.
Remove the existing Judo link handling:

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    return Judo.sharedInstance.openURL(url, animated: true)
}
    
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    return Judo.sharedInstance.continueUserActivity(userActivity, animated: true)
}

Replace it with routing from the Rover SDK, if it isn't already there:

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    return Rover.shared.router.handle(url)
}
    
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    return Rover.shared.router.handle(userActivity)
}

Add the former Judo domain to Rover's Associated Domains list

When Rover is initialized, you gave it a list of associated domains. You should now add your Judo domain to this list.

Rover.initialize(
    // ...
    UIAssembler(
        associatedDomains: ["myapp.rover.io", "myapp.judo.app"],
        // ...
    ),
    // ...
)

Be sure that your Judo domain is the second item in the associated domains list.

Now the new Rover 4 SDK can open both Judo and Rover experiences.


User Identification Changes

Instead of calling the identify method on the Judo singleton to enable personalization, this is now provided to the Rover SDK through the user info methods. If the Judo SDK was integrated along with Rover 3, the Judo call to identify() can simply be removed. Otherwise, in Rover 4 the required values can be passed to the Rover UserInfoManager

The previous Judo method of calling Identify with a custom object conforming to Codable:

// Pass user ID and custom properties to Judo for personalization
struct UserTraits: Codable {
    let name: String
    let pointsBalance: Int
    let premiumTier: Bool
    let tags: [String]
}

Judo.sharedInstance.identify(
    // NB: don't use an email address here!
    userID: "1234567",
    traits: UserTraits(
        name: "John Doe",
        email: "john@example.com",
        pointsBalance: 50_000,
        premiumTier: true,
        tags: ["foo", "bar", "baz"]
    )
)

In Rover 4, the UserInfoManager is used instead with the updateUserInfo method:

// Set custom info about the current user
Rover.shared.resolve(UserInfoManager.self)?.updateUserInfo { attributes in
    attributes["userID"] = "1234567"
    attributes["name"] = "John Doe"
    attributes["email"] = "john@example.com"
    attributes["pointsBalance"] = 50_000
    attributes["premiumTier"] = true
    attributes["tags"] = ["foo", "bar", "baz"]
}

This information remains persisted across app restarts, it is recommended to call it every time your user data changes (such as logging in, logging out, profile updates, etc).


Authorizers

Authorizers are no longer setup when configuring the SDK. Now, they can be setup anytime after initialization of the Rover SDK. This is done from the Rover shared object in a similar manner to the Judo SDK. The domain is required, as well as a block that accepts the url request to add headers to.

With the previous method of setting the authorizer when configuring the Judo SDK:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    var configuration = Configuration(
        accessToken: "<ACCESS-TOKEN>",
        domain: "myapp.judo.app"
    )
    
    configuration.authorize("api.example.com", with: { urlRequest in
        urlRequest.setValue("xxx", forHTTPHeaderField: "Example-Token")
    })
    
    Judo.initialize(configuration: configuration)
}

Using the new Rover shared object:

Rover.shared.authorize(pattern: "api.example.com") { urlRequest in
    urlRequest.setValue("xxx", forHTTPHeaderField: "Example-Token")
}

Embedding View Controllers

The ExperienceViewController remains the focal point of Experiences. The manner in which it is instantiated differs from Judo to Rover 4. Previously, a url, cache setting, optional user info and optional authorizer were required to instantiate the ExperienceViewController. In Rover 4, all that is required is the url of the Experience.

If your experience made use of UserInfo, this is now provided to the Rover SDK with the UserInfoManager.

An ExperienceViewController instantiated previously in Judo:

let experienceViewController = ExperienceViewController(
    url: url,
    ignoreCache: false,
    userInfo: userInfo,
    authorize: authorizer)

In Rover 4, it is simply:

let experienceViewController = ExperienceViewController.openExperience(with: url)

//user info is now handled by the Rover UserInfoManager
//authorizers are now set in the Rover singlton

The returned view controller can be used as before, embedded within another view controller or used with SwiftUI.


Analytics

The screen viewed notification no longer uses the NotificationCenter , so observing the Judo.screenViewedNotification no longer works. The SDK now uses a callback that provides a ScreenViewedEvent.

Previously, the screen viewed event was observed as follows:

screenViewedObserver = NotificationCenter.default.addObserver(
    forName: Judo.screenViewedNotification,
    object: nil,
    queue: OperationQueue.main,
    using: { notification in
        let screen = notification.userInfo!["screen"] as! Screen
        let experience = notification.userInfo!["experience"] as! Experience

        os_log("Judo experience screen viewed: %s", type: .default, screen.name)
        // MyAnalyticsSDK.trackScreen(screen.name)                    
    }
)

Now, the callback for screen viewed events is registered:

Rover.shared.registerScreenViewedCallback { event in
    let screenName = "\(event.experienceName ?? "Experience") / \(event.screenName ?? "Screen")"
     os_log("Rover experience screen viewed: %s", type: .default, screenName)
            
     // MyAnalyticsSDK.trackScreen(screenName)
}

Custom Action Handlers

Custom action handlers have changed slightly from Judo to Rover 4. The custom action handler is now set using the Rover shared object, instead of the shared Judo object.

With the Judo shared object:

Judo.sharedInstance.registerCustomActionCallback { actionEvent in
    switch actionEvent.metadata?.properties["behavior"] {
        case "openWebsite":
            UIApplication.shared.open(URL(string: "https://rover.io/")!)
        case "printLogMessage":
            os_log(.default, "Hello from Rover!")
        default:
            os_log(.error, "🤷‍♂️")
    }
}

Using the new Rover shared object:

Rover.shared.registerCustomActionCallback { actionEvent in
    switch actionEvent.nodeProperties["behavior"] {
        case "openWebsite":
            UIApplication.shared.open(URL(string: "https://rover.io/")!)
        case "printLogMessage":
            os_log(.default, "Hello from Rover!")
        default:
            os_log(.error, "🤷‍♂️")
    }
}
Previous
iOS - Rover