Respond to flow actions - Kotlin Multiplatform

If you are building flows or paywalls using the Adapty Flow Builder or Paywall Builder, it’s crucial to set up buttons properly:

  1. Add a button in the builder and assign it either a pre-existing action or create a custom action ID.
  2. Write code in your app to handle each action you’ve assigned.

This guide shows how to handle custom and pre-existing actions in your code.

Only purchases, restorations, flow/paywall closures, and opening links are handled automatically. All other button actions, such as custom actions, require proper response implementation in the app code.

Set up the AdaptyUIFlowsEventsObserver

To handle flow actions, you need to implement the AdaptyUIFlowsEventsObserver interface and set it up with AdaptyUI.setFlowsEventsObserver(). This should be done early in your app’s lifecycle, typically in your main activity or app initialization.

import com.adapty.kmp.AdaptyUI
import com.adapty.kmp.AdaptyUIFlowsEventsObserver

// In your app initialization
AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver())

All button actions arrive in the flowViewDidPerformAction(view, action) callback as an AdaptyUIAction sealed class: CloseAction, AndroidSystemBackAction, OpenUrlAction, or CustomAction.

Overriding flowViewDidPerformAction replaces the default handling for all actions, not just the one you’re interested in. Keep the default branches for CloseAction (dismiss the flow) and OpenUrlAction (open the URL) unless you mean to change them, as shown in the examples below.

Close flows and paywalls

To add a button that will close your flow or paywall:

  1. In the builder, add a button and assign it the Close action.
  2. In your app code, implement a handler for the close action that dismisses the flow.

In the Kotlin Multiplatform SDK, the CloseAction triggers closing the flow or paywall by default. However, you can override this behavior in your code if needed. For example, closing one flow might trigger opening another.

import com.adapty.kmp.AdaptyUI
import com.adapty.kmp.AdaptyUIFlowsEventsObserver
import com.adapty.kmp.models.AdaptyUIAction
import com.adapty.kmp.models.AdaptyUIFlowView
import kotlinx.coroutines.launch

class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver {
    override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) {
        when (action) {
            is AdaptyUIAction.CloseAction ->
                mainUiScope.launch { view.dismiss() } // default behavior
            is AdaptyUIAction.OpenUrlAction ->
                AdaptyUI.openWebUrl(action.url, action.openIn) // default behavior
            else -> Unit
        }
    }
}

// Set up the observer
AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver())

If you are using createNativeFlowView, calling view.dismiss() has no effect — the view is embedded in your layout, not presented through the KMP stack. Remove the view from your layout and call dispose() on it instead.

Handle the Android system back button

Pressing the Android system back button (or using the back gesture) emits AdaptyUIAction.AndroidSystemBackAction. By default, this action is ignored — the flow stays open, and the user leaves it through a path you define, such as a Close button or an on_device_back action in the builder. If you want the system back button to dismiss the flow, handle the action yourself:

class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver {
    override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) {
        when (action) {
            is AdaptyUIAction.CloseAction ->
                mainUiScope.launch { view.dismiss() } // default behavior
            is AdaptyUIAction.AndroidSystemBackAction ->
                mainUiScope.launch { view.dismiss() } // not handled by default
            is AdaptyUIAction.OpenUrlAction ->
                AdaptyUI.openWebUrl(action.url, action.openIn) // default behavior
            else -> Unit
        }
    }
}

Open URLs from flows and paywalls

If you want to add a group of links (e.g., terms of use and purchase restoration), add a Link element in the builder and handle it the same way as buttons with the Open URL action.

To add a button that opens a link from your flow or paywall (e.g., Terms of use or Privacy policy), in the builder, add a button, assign it the Open URL action, and enter the URL you want to open.

By default, the SDK opens the received URL natively — in an external or in-app browser, depending on action.openIn — so no code is required. Override the handler only if you want custom logic, such as showing a confirmation dialog first:

import androidx.compose.ui.platform.UriHandler
import com.adapty.kmp.AdaptyUI
import com.adapty.kmp.AdaptyUIFlowsEventsObserver
import com.adapty.kmp.models.AdaptyUIAction
import com.adapty.kmp.models.AdaptyUIDialogActionType
import com.adapty.kmp.models.AdaptyUIFlowView
import com.adapty.kmp.models.getOrNull
import kotlinx.coroutines.launch

class MyAdaptyUIFlowsEventsObserver(
    private val uriHandler: UriHandler
) : AdaptyUIFlowsEventsObserver {
    override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) {
        when (action) {
            is AdaptyUIAction.OpenUrlAction -> {
                // Show confirmation dialog before opening URL
                mainUiScope.launch {
                    val selectedAction = view.showDialog(
                        title = "Open URL?",
                        content = action.url,
                        primaryActionTitle = "Cancel",
                        secondaryActionTitle = "Open"
                    ).getOrNull()

                    when (selectedAction) {
                        AdaptyUIDialogActionType.PRIMARY -> {
                            // User cancelled
                        }
                        AdaptyUIDialogActionType.SECONDARY -> {
                            // User confirmed - open URL
                            uriHandler.openUri(action.url)
                        }
                        else -> Unit
                    }
                }
            }
            else -> Unit
        }
    }
}

// Set up the observer with UriHandler
AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver(uriHandler))

Log into the app

To add a button that logs users into your app:

  1. In the builder, add a button and assign it a Custom action with the ID “login”.
  2. In your app code, implement a handler for the custom action that identifies your user.
import com.adapty.kmp.AdaptyUIFlowsEventsObserver
import com.adapty.kmp.models.AdaptyUIAction
import com.adapty.kmp.models.AdaptyUIFlowView

class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver {
    override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) {
        when (action) {
            is AdaptyUIAction.CustomAction -> {
                if (action.action == "login") {
                    // Handle login action - navigate to login screen
                    // This depends on your app's navigation system
                    // For example, in Compose Multiplatform:
                    // navController.navigate("login")
                }
            }
            else -> Unit
        }
    }
}

Handle custom actions

To add a button that handles any other actions:

  1. In the builder, add a button, assign it the Custom action, and assign it an ID.
  2. In your app code, implement a handler for the action ID you’ve created.

For example, if you have another set of subscription offers or one-time purchases, you can add a button that will display another flow or paywall:

import com.adapty.kmp.AdaptyUI
import com.adapty.kmp.AdaptyUIFlowsEventsObserver
import com.adapty.kmp.models.AdaptyUIAction
import com.adapty.kmp.models.AdaptyUIFlowView

class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver {
    override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) {
        when (action) {
            is AdaptyUIAction.CustomAction -> {
                when (action.action) {
                    "openNewFlow" -> {
                        // Display another flow or paywall
                    }
                }
            }
            else -> Unit
        }
    }
}

// Set up the observer
AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver())

Only purchases and restorations are handled automatically. All the other button actions, such as closing paywalls or opening links, require implementing proper responses in the app code.

If you are building paywalls using the Adapty paywall builder, it’s crucial to set up buttons properly:

  1. Add a button in the paywall builder and assign it either a pre-existing action or create a custom action ID.
  2. Write code in your app to handle each action you’ve assigned.

This guide shows how to handle custom and pre-existing actions in your code.

Set up the AdaptyUIPaywallsEventsObserver

To handle paywall actions, you need to implement the AdaptyUIPaywallsEventsObserver interface and set it up with AdaptyUI.setPaywallsEventsObserver(). This should be done early in your app’s lifecycle, typically in your main activity or app initialization.

import com.adapty.kmp.AdaptyUI
import com.adapty.kmp.AdaptyUIPaywallsEventsObserver

// In your app initialization
AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver())

Close paywalls

To add a button that will close your paywall:

  1. In the paywall builder, add a button and assign it the Close action.
  2. In your app code, implement a handler for the close action that dismisses the paywall.

In the Kotlin Multiplatform SDK, the CloseAction and AndroidSystemBackAction trigger closing the paywall by default. However, you can override this behavior in your code if needed. For example, closing one paywall might trigger opening another.

import com.adapty.kmp.AdaptyUI
import com.adapty.kmp.AdaptyUIPaywallsEventsObserver
import com.adapty.kmp.models.AdaptyUIAction
import com.adapty.kmp.models.AdaptyUIPaywallView

class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver {
    override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) {
        when (action) {
            AdaptyUIAction.CloseAction, AdaptyUIAction.AndroidSystemBackAction -> view.dismiss()
        }
    }
}

// Set up the observer
AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver())

If you are using createNativePaywallView, calling view.dismiss() has no effect — the view is embedded in your layout, not presented through the KMP stack. Remove the view from your layout and call dispose() on it instead.

Open URLs from paywalls

If you want to add a group of links (e.g., terms of use and purchase restoration), add a Link element in the paywall builder and handle it the same way as buttons with the Open URL action.

To add a button that opens a link from your paywall (e.g., Terms of use or Privacy policy):

  1. In the paywall builder, add a button, assign it the Open URL action, and enter the URL you want to open.
  2. In your app code, implement a handler for the openUrl action that opens the received URL in a browser.

In the Kotlin Multiplatform SDK, the OpenUrlAction provides the URL that should be opened. You can implement custom logic to handle URL opening, such as showing a confirmation dialog or using your app’s preferred URL handling method.

import androidx.compose.ui.platform.UriHandler
import com.adapty.kmp.AdaptyUI
import com.adapty.kmp.AdaptyUIPaywallsEventsObserver
import com.adapty.kmp.models.AdaptyUIAction
import com.adapty.kmp.models.AdaptyUIDialogActionType
import com.adapty.kmp.models.AdaptyUIPaywallView
import com.adapty.kmp.models.getOrNull

class MyAdaptyUIPaywallsEventsObserver(
    private val uriHandler: UriHandler
) : AdaptyUIPaywallsEventsObserver {
    override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) {
        when (action) {
            is AdaptyUIAction.OpenUrlAction -> {
                // Show confirmation dialog before opening URL
                mainUiScope.launch {
                    val selectedAction = view.showDialog(
                        title = "Open URL?",
                        content = action.url,
                        primaryActionTitle = "Cancel",
                        secondaryActionTitle = "Open"
                    ).getOrNull()

                    when (selectedAction) {
                        AdaptyUIDialogActionType.PRIMARY -> {
                            // User cancelled
                        }
                        AdaptyUIDialogActionType.SECONDARY -> {
                            // User confirmed - open URL
                            uriHandler.openUri(action.url)
                        }
                        else -> Unit
                    }
                }
            }
        }
    }
}

// Set up the observer with UriHandler
AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver(uriHandler))

Log into the app

To add a button that logs users into your app:

  1. In the paywall builder, add a button and assign it a Custom action with the ID “login”.
  2. In your app code, implement a handler for the custom action that identifies your user.
import com.adapty.kmp.AdaptyUIObserver
import com.adapty.kmp.models.AdaptyUIAction
import com.adapty.kmp.models.AdaptyUIView

class MyAdaptyUIObserver : AdaptyUIObserver {
    override fun paywallViewDidPerformAction(view: AdaptyUIView, action: AdaptyUIAction) {
        when (action) {
            is AdaptyUIAction.CustomAction -> {
                if (action.action == "login") {
                    // Handle login action - navigate to login screen
                    // This depends on your app's navigation system
                    // For example, in Compose Multiplatform:
                    // navController.navigate("login")
                }
            }
        }
    }
}

Handle custom actions

To add a button that handles any other actions:

  1. In the paywall builder, add a button, assign it the Custom action, and assign it an ID.
  2. In your app code, implement a handler for the action ID you’ve created.

For example, if you have another set of subscription offers or one-time purchases, you can add a button that will display another paywall:

import com.adapty.kmp.AdaptyUI
import com.adapty.kmp.AdaptyUIPaywallsEventsObserver
import com.adapty.kmp.models.AdaptyUIAction
import com.adapty.kmp.models.AdaptyUIPaywallView

class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver {
    override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) {
        when (action) {
            is AdaptyUIAction.CustomAction -> {
                when (action.action) {
                    "login" -> {
                        // Handle login action - navigate to login screen
                        // This depends on your app's navigation system
                        // For example, in Compose Multiplatform:
                        // navController.navigate("login")
                    }
                }
            }
        }
    }
}

// Set up the observer
AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver())