# In-App Experiences Configure and control In-App Experiences in Capacitor applications. # In-App Experiences > Pause, resume, and control display timing for In-App Experiences. In-App Experiences are automatically enabled when you integrate the Airship SDK. Use these methods to control when and how they are displayed. ## Pausing and Resuming Display You can pause and resume In-App Experiences to control when they are displayed to users. ```typescript // Pause in-app experiences await Airship.inApp.setPaused(true) // Resume in-app experiences await Airship.inApp.setPaused(false) // Check if paused const isPaused = await Airship.inApp.isPaused() ``` ### Auto-Pause on Launch You can configure the SDK to automatically pause In-App Experiences on launch. This is useful if you want to defer showing In-App Experiences until after onboarding or other critical app flows. ```typescript await Airship.takeOff({ production: { appKey: "", appSecret: "" }, development: { appKey: "", appSecret: "" }, inProduction: true, site: "us", autoPauseInAppAutomationOnLaunch: true }) ``` See the [Capacitor Plugin Setup guide](https://www.airship.com/docs/developer/sdk-integration/capacitor/installation/getting-started/) for complete `takeOff` configuration options. When you're ready to display In-App Experiences, call `setPaused(false)`: ```typescript await Airship.inApp.setPaused(false) ``` ## Display Interval Control the minimum time between In-App Experience displays to avoid overwhelming users. ```typescript // Set display interval to 5 seconds await Airship.inApp.setDisplayInterval(5000) // Get current display interval const interval = await Airship.inApp.getDisplayInterval() ``` The display interval is the minimum time (in milliseconds) that must pass between displaying In-App Experiences. # Custom Views > Register custom native views to use within Scenes. A *Custom View* is a native view from your mobile or web application embedded into a Scene. Custom Views can display any native content your app exposes, so you can reuse that existing content within any screen in a Scene. Custom Views allow you to embed native iOS and Android views within Scenes, giving you full control over design and layout while leveraging Airship's targeting and orchestration capabilities. ## Requirements To use Custom Views in Capacitor, you must extend the native Airship modules using the `AirshipPluginExtender`. See [Extending Airship](https://www.airship.com/docs/developer/sdk-integration/capacitor/installation/extending-airship/) for setup instructions. ## Registering Custom Views Custom Views must be registered on each native platform separately: ### iOS See the [Apple Custom Views documentation](https://www.airship.com/docs/developer/sdk-integration/apple/in-app-experiences/custom-views/) for detailed implementation instructions. Custom Views should be registered after takeOff in your native iOS code: ```swift import AirshipKit @objc(AirshipExtender) class AirshipExtender: NSObject, AirshipPluginExtenderDelegate { func onAirshipReady() { // Register custom views AirshipCustomViewManager.shared.register(name: "my-custom-view") { args in // Return your SwiftUI view MyCustomView(args: args) } } } ``` ### Android See the [Android Custom Views documentation](https://www.airship.com/docs/developer/sdk-integration/android/in-app-experiences/custom-views/) for detailed implementation instructions. Custom Views should be registered during the `onAirshipReady` callback in your native Android code: ```kotlin import com.urbanairship.AirshipCustomViewManager class AirshipExtender : AirshipPluginExtender { override fun onAirshipReady(context: Context) { // Register custom views AirshipCustomViewManager.register("my-custom-view") { context, args -> // Return your Android View MyCustomView(context, args) } } } ``` ## Using Custom Views Once registered, Custom Views can be added to Scenes in the Airship dashboard: 1. Create or edit a Scene 2. Add the **Custom View** content element to a screen 3. Enter the view name (e.g., `my-custom-view`) that matches the name you registered in your native code 4. Optionally add key-value pairs to pass custom properties to the view The native view will be displayed within the Scene with the properties you configured. ## Example: Embedding Capacitor Web Views A powerful use case for Custom Views in Capacitor is embedding your Capacitor web app (or a subset of it) directly within a Scene. This allows you to display specific routes or pages from your web app inside an Airship Scene. ### iOS Implementation ```swift import Foundation import AirshipFrameworkProxy import Capacitor import AirshipCore import SwiftUI import UIKit @objc(AirshipPluginExtender) public class AirshipPluginExtender: NSObject, AirshipPluginExtenderProtocol { @MainActor public static func onAirshipReady() { AirshipCustomViewManager.shared.register(name: "capacitor") { args in CapView( startPath: args.properties?.object?["path"]?.string ) .edgesIgnoringSafeArea(.all) .frame(maxWidth: .infinity, maxHeight: .infinity) } } } struct CapView: UIViewControllerRepresentable { let startPath: String? func makeUIViewController(context: Context) -> CustomCapViewController { let controller = CustomCapViewController() controller.startPath = startPath return controller } func updateUIViewController(_ uiViewController: CustomCapViewController, context: Context) { // Handle updates (if necessary) } class CustomCapViewController: CAPBridgeViewController { var startPath: String? = nil override func instanceDescriptor() -> InstanceDescriptor { let descriptor = super.instanceDescriptor() descriptor.appStartPath = startPath return descriptor } } } ``` ### Android Implementation First, create a layout file at `res/layout/custom_view.xml`: ```xml ``` Then, register the custom view in your `AirshipExtender.kt`: ```kotlin package com.example.plugin import android.content.Context import android.content.res.Configuration import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import androidx.annotation.Keep import androidx.appcompat.app.AppCompatActivity import androidx.core.view.doOnAttach import androidx.fragment.app.Fragment import com.getcapacitor.Bridge import com.getcapacitor.CapConfig import com.getcapacitor.Logger import com.getcapacitor.PluginLoadException import com.getcapacitor.PluginManager import com.urbanairship.UAirship import com.urbanairship.android.framework.proxy.AirshipPluginExtender import com.urbanairship.android.layout.AirshipCustomViewArguments import com.urbanairship.android.layout.AirshipCustomViewHandler import com.urbanairship.android.layout.AirshipCustomViewManager import com.urbanairship.json.optionalField @Keep class AirshipExtender: AirshipPluginExtender { override fun onAirshipReady(context: Context, airship: UAirship) { AirshipCustomViewManager.register("capacitor", CustomCapViewHandler()) } } class CustomCapViewHandler: AirshipCustomViewHandler { override fun onCreateView(context: Context, args: AirshipCustomViewArguments): View { return CapView(context).also { it.startPath = args.properties.optionalField("path") } } } class CapView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 ) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { var startPath: String? = null init { id = View.generateViewId() val fm = (context as AppCompatActivity).supportFragmentManager fm.findFragmentById(id)?.let { fm.beginTransaction() .remove(it) .commit() } doOnAttach { findViewById(id)?.let { fm.beginTransaction() .setReorderingAllowed(true) .add(id, CapFragment.newInstance(startPath)) .commitNowAllowingStateLoss() } } } } class CapFragment: Fragment() { private var bridge: Bridge? = null private val path: String? by lazy { arguments?.getString(ARG_PATH) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.custom_view, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) load(savedInstanceState) } override fun onStart() { super.onStart() bridge?.onStart() } override fun onResume() { super.onResume() bridge?.app?.fireStatusChange(true) bridge?.onResume() } override fun onPause() { super.onPause() bridge?.app?.fireStatusChange(false) bridge?.onPause() } override fun onStop() { super.onStop() bridge?.onStop() } override fun onDestroy() { super.onDestroy() bridge?.onDestroy() bridge?.onDetachedFromWindow() } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) bridge?.onConfigurationChanged(newConfig) } private fun load(savedInstanceState: Bundle?) { if (bridge == null) { bridge = Bridge.Builder(this) .apply { try { val loader = PluginManager(requireActivity().assets) addPlugins(loader.loadPluginClasses()) } catch (ex: PluginLoadException) { Logger.error("Error loading plugins.", ex) } } .setConfig( CapConfig.Builder(context) .setStartPath(this.path) .create() ) .setInstanceState(savedInstanceState) .create() } } companion object { const val ARG_PATH: String = "path" @JvmStatic @JvmOverloads fun newInstance(path: String? = null): CapFragment = CapFragment().apply { arguments = Bundle().apply { path?.let { putString(ARG_PATH, it) } } } } } ``` ### Usage in Scenes When creating a Scene in the Airship dashboard: 1. Add a **Custom View** content element 2. Set the view name to `capacitor` 3. Optionally add a `path` property to specify which route in your Capacitor app to display (e.g., `/products`, `/settings`) > **Note:** **Sizing limitation**: The custom view must use hard-coded sizing (percentages or pixels). Auto-sizing is not currently supported for embedded Capacitor views. This implementation creates a fully functional Capacitor web view within the Scene, complete with all your Capacitor plugins and routing capabilities. The optional `path` property allows you to display specific routes or pages from your web app.