Migration Guides

Migrating to Rover 4 from Rover Campaigns 3.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 setup. The Rover 4 SDK supersedes that one and this guide will walk you through the necessary changes.


Update SDK Installation

This version of the Rover SDK no longer supports Cocoapods or Carthage. Installation of the SDK must now be performed via the Swift Package Manager.

To add the Rover SDK to your Xcode project using Swift Package Manager, follow these steps:

  1. Open your Xcode project.
  2. Click on "File" > "Add Packages".
  3. In the "Search or Enter Package URL" text box, enter the following URL: https://github.com/roverplatform/rover-ios.git
  4. Set the Dependency Rule as ‘Up to Next Major Version’ with 4.0.0.
  5. Add to project with the correct target set.
  6. Click "Add Package".

The Rover SDK should now be added to your Xcode project and can be imported into your Swift code using import RoverFoundation as well as imports for any other modules used.


Top Level API Changes

In the new version of Rover SDK, the RoverFoundation object has been renamed to Rover and the shared object is no longer optional. This should remove the need for many optional checks and guards.

As an example a call to set the device token:

RoverFoundation.shared?.tokenManager.setToken(deviceToken)

No longer is optional, and now refers to the Rover shared object:

Rover.shared.tokenManager.setToken(deviceToken)

Calling the Rover shared object before initializing the Rover SDK will cause a fatal error. Make sure the SDK is initialized before calling the shared object.


Events - Event Name Changes

Event names have changed from Rover 3.x to Rover 4, prefixing the word classic in the event names. These events may continue to be observed from the default iOS NotificationCenter.

Observing the pollAnsweredNotification in Rover 3.x:

NotificationCenter.default.addObserver(
    forName: ScreenViewController.pollAnsweredNotification,
    object: nil,
    queue: nil,
    using: { notification in
        
        //integrate the event with your analytics here
    }
)

Observing the same event in Rover 4:

NotificationCenter.default.addObserver(
    forName: ClassicScreenViewController.classicPollAnsweredNotification,
    object: nil,
    queue: nil,
    using: { notification in
        
        //integrate the event with your analytics here        
    }
)

Events - Screen Handler Replacement

Screen Viewed Events are now handled with the shared Rover object. So the previous method of getting the screen viewed event:

NotificationCenter.default.addObserver(forName: ScreenViewController.screenPresentedNotification, object: nil, queue: nil) { notification in
    let screen = notification.userInfo?[ScreenViewController.screenUserInfoKey] as! Screen
    os_log("Rover experience screen viewed: %s", type: .default, screen.name)
    
    // MyAnalyticsSDK.trackScreen(screen.name)
}

becomes:

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)
}

Other events will continue to use the previous method of adding observers to the NotificationCenter


Events - Custom Actions

The Rover SDK can now handle custom actions as defined in the provided Rover file. A recommended pattern for having multiple behaviors for different custom actions in experiences is to use a metadata property called behavior on your experience layer with the action, and then handle it within the callback.

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, "🤷‍♂️")
    }
}

Notification Center renamed to Inbox

In Rover 4, the notification center has been renamed to inbox.

The inbox is still a View Controller, so it is still resolved from the Rover shared object. The name has simply been changed from notificationCenter to inbox.

The previous notification center was resolved from the Rover shared object with the name notificationCenter:

// An IBAction connected to a UIButton through Interface Builder inside a UIViewController
@IBAction func presentNotificationCenter(_ sender: Any) {
    guard let notificationCenter = Rover.shared?.resolve(UIViewController.self, name: "notificationCenter") else {
        return
    }
    
    present(notificationCenter, animated: true, completion: nil)
}

The name of the View Controller that is resolved has changed to inbox:

// An IBAction connected to a UIButton through Interface Builder inside a UIViewController
@IBAction func presentInbox(_ sender: Any) {

    // Resolve the Inbox view controller
    guard let inbox = Rover.shared.resolve(UIViewController.self, name: "inbox") else {
        return
    }
    
    present(inbox, animated: true, completion: nil)
}

When customizing the appearance of the inbox, much of the code remains the same as the previous version of Rover, with the name of the NotificationCenterViewController and NotificationCell being changed to InboxViewController and InboxCell respectively. The cell reuse identifier has also changed from notification to inboxCell.

From this overview of how to customize the cells of the Rover 3.x notification center:

// 1. Create custom notification center cell
public class MyNotificationCell : NotificationCell {
    // ... override methods as you deem fit here.
}

// 2. Register the custom cell for reuse within the custom notification center view controller
public class MyNotificationCenterViewController : NotificationCenterViewController {
    override public func registerReusableViews() {
        // Rover by default has a UITableViewCell called NotificationCell.  You can replace it here with your own implementation:
        tableView.register(MyNotificationCell.self, forCellReuseIdentifier: "notification")
        
    }
}

// 3. Create a custom assembler 
public struct CustomNotificationCenterAssembler : Assembler {
    public func assemble(container: Container) {
        container.register(UIViewController.self, name: "notificationCenter") { resolver in
            let presentWebsiteActionProvider: MyNotificationCenterViewController.ActionProvider = { [weak resolver] url in
                resolver?.resolve(Action.self, name: "presentWebsite", arguments: url)
            }
            
            return MyNotificationCenterViewController(
                dispatcher: resolver.resolve(Dispatcher.self)!,
                eventQueue: resolver.resolve(EventQueue.self)!,
                imageStore: resolver.resolve(ImageStore.self)!,
                notificationStore: resolver.resolve(NotificationStore.self)!,
                router: resolver.resolve(Router.self)!,
                sessionController: resolver.resolve(SessionController.self)!,
                syncCoordinator: resolver.resolve(SyncCoordinator.self)!,
                presentWebsiteActionProvider: presentWebsiteActionProvider
            )
        }
    }
}

// 4. When initializing the Rover SDK, add the custom assembler here. 
Rover.initialize(assemblers: [
    // ... (the other assemblers go here, as usual).
    CustomNotificationCenterAssembler() // make sure you put this last in the list!
])

// 5. To use the custom notification center, resolve it as before
let customNotificationCenter = Rover.shared!.resolve(UIViewController.self, name: "notificationCenter")!

With the changes for Rover 4:

// 1. Create custom inbox cell
public class MyInboxCell : InboxCell {
    // ... override methods as you deem fit here.
}

// 2. Register the custom cell for reuse within the custom inbox view controller
public class MyCustomInboxViewController : InboxViewController {
    override public func registerReusableViews() {
        // Rover by default has a UITableViewCell called NotificationCell.  You can replace it here with your own implementation:
        tableView.register(MyInboxCell.self, forCellReuseIdentifier: "inboxCell")
        
    }
}

// 3. Create a custom assembler 
public struct CustomInboxAssembler : Assembler {
    public func assemble(container: Container) {
        container.register(UIViewController.self, name: "inbox") { resolver in
            let presentWebsiteActionProvider: MyCustomInboxViewController.ActionProvider = { [weak resolver] url in
                resolver?.resolve(Action.self, name: "presentWebsite", arguments: url)
            }
            
            return MyCustomInboxViewController(
                dispatcher: resolver.resolve(Dispatcher.self)!,
                eventQueue: resolver.resolve(EventQueue.self)!,
                imageStore: resolver.resolve(ImageStore.self)!,
                notificationStore: resolver.resolve(NotificationStore.self)!,
                router: resolver.resolve(Router.self)!,
                sessionController: resolver.resolve(SessionController.self)!,
                syncCoordinator: resolver.resolve(SyncCoordinator.self)!,
                presentWebsiteActionProvider: presentWebsiteActionProvider
            )
        }
    }
}

// 4. When initializing the Rover SDK, add the custom assembler here. 
Rover.initialize(assemblers: [
    // ... (the other assemblers go here, as usual).
    CustomInboxAssembler() // make sure you put this last in the list!
])

// 5. To use the custom notification center, resolve it as before
let customInbox = Rover.shared.resolve(UIViewController.self, name: "inbox")!
Previous
Overview