Data & Customization
Embedding Experiences
Embedded experiences allow you to embed a single Rover experience directly into your own app UI.
Not for deep link routing
Embedded experiences are not an alternative to standard deep link routing. If you need to handle Rover deep links, see Deep Links instead.
This enables use cases such as populating a tab in a tab bar with content designed in Rover, adding a section of Rover content to a large screen, and many more.
Additionally you can use handling custom actions to enable custom buttons within an embedded experience to perform arbitrary behaviour in the app.
Intrinsic Size
Experiences do not have an intrinsic size - so for both platforms, the experience will adopt whatever size you offer it, but cannot be auto-sized to fit its content.
iOS
Using UIKit
An experience may be embedded within another view controller, by adding the ExperienceViewController as a child view controller, using the standard pattern for embedding view controllers.
Once the view controller has been added as a child, its frame must be given a fixed size, then its view can be added to the parent. Once the view has been added to the parent, call ViewController.didMove(toParent:). When done with the experience, call ViewController.removeFromParent().
The ExperienceViewController does not support autosizing from content, so it will need to be given a fixed size.
Additional details are available at: Implementing a Container View Controller
import UIKit
import RoverExperiences
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let url = URL(string: "<Your Experience URL>") {
let experienceVC = ExperienceViewController.openExperience(with: url)
addChild(experienceVC)
experienceVC.view.frame = view.bounds //Fixed size is required here.
view.addSubview(experienceVC.view)
experienceVC.didMove(toParent: self)
}
}
}
Using SwiftUI
In SwiftUI, you can use an ExperienceViewController within a View by creating a type that conforms to the UIViewControllerRepresentable protocol. A struct of this type can then be used just like any other SwiftUI view. The type should have the URL of the experience as well.
Additional details are available at: UIViewControllerRepresentable
import SwiftUI
import UIKit
import RoverExperiences
struct ExperienceRepresentable: UIViewControllerRepresentable {
var url: URL
init(url: URL) {
self.url = url
}
func makeUIViewController(context: Context) -> ExperienceViewController {
return ExperienceViewController.openExperience(with: self.url)
}
func updateUIViewController(_ uiViewController: ExperienceViewController, context: Context) {
//NO-OP
}
}
import SwiftUI
import RoverExperiences
struct RoverExampleView: View {
var body: some View {
VStack {
if let url = URL(string: "<Your Experience URL>") {
ExperienceRepresentable(url: url)
}
}
}
}
Full-Screen Presentation in SwiftUI
As an optional extra, you can present specific Rover experiences full screen in SwiftUI instead of using Rover's standard modal routing. To opt in to this behaviour, include a query parameter on the deep link URL that you control. When that parameter is present, inspect the incoming URL and use a fullScreenCover to present an ExperienceRepresentable; otherwise, fall back to handing the URL to Rover's standard router.
// Wraps URL to satisfy Identifiable for fullScreenCover(item:).
private struct FullScreenExperience: Identifiable {
var id: URL { url }
let url: URL
}
@main
struct MyApp: App {
@State private var fullScreenExperience: FullScreenExperience?
var body: some Scene {
WindowGroup {
RootView()
.onOpenURL { url in
if isFullScreenURL(url) {
fullScreenExperience = FullScreenExperience(url: url)
} else if !Rover.shared.router.handle(url) {
// Handle URLs not intended for Rover here.
}
}
.fullScreenCover(item: $fullScreenExperience) { experience in
ExperienceRepresentable(url: experience.url)
}
}
}
// Replace "rv-myapp", "myapp.rover.io", and "fullscreen" with your
// app's URL scheme, host, and chosen query parameter.
private func isFullScreenURL(_ url: URL) -> Bool {
guard url.scheme == "rv-myapp",
url.host == "myapp.rover.io",
let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return false
}
return components.queryItems?.contains(where: { $0.name == "fullscreen" && $0.value == "true" }) ?? false
}
}
Android
Android offers two options for embedding your experiences. You can use a Fragment or a Composable.
Using a Fragment
The SDK includes ExperienceFragment, a fragment which can be embedded into an activity.
Within a FragmentActivity, you can use the Fragment Manager to add the fragment to the activity in the typical way.
In this example implementation of onCreate(), the built-in Android content placeholder is replaced with the fragment, which fills the entire activity with the fragment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.supportFragmentManager
.beginTransaction()
.replace(
android.R.id.content,
ExperienceFragment("<EXAMPLE-EXP-URL>")
)
.commit()
}
Using a Composable
The SDK includes ExperienceComposable, a Jetpack Compose composable function which can be embedded into a Compose UI.
Sizing Behavior
ExperienceComposable will expand to fill the maximum space given to it by its constraints. It does not size down to fit content. If you wish to limit the size it will take up, use a modifier such as size(), width(), or height().
ExperienceComposable(
Uri.parse("<EXAMPLE-EXP-URL>"),
// Example use of a modifier to constrain size:
// ExperienceComposable's sizing behavior is to expand
// to the maximum size given. In many instances, you will
// want to limit it. For example, by using the height
// modifier below, the Experience will expand to fill
// width of the display but only take 300dp in height.
modifier = Modifier.height(300.dp),
)