**Last updated**: 17 March 2025

# Flutter integration with Native SDKs

## Overview

The integration leverages [Flutter platform channels](https://docs.flutter.dev/platform-integration/platform-channels) to configure and render our Native SDKs for [iOS](/access/products/checkout/ios) and [Android](/access/products/checkout/android).

You create a form containing the Access Checkout UI components in the iOS and Android projects.
This form is then rendered on the Flutter application using a `PlatformViewLink` for Android or a `UiKitView` for iOS.

The communication between Native and Flutter is done via method channels.

## Get started

To integrate our Checkout Native iOS and Android SDKs into Flutter you **must**:

- add our Checkout SDKs to iOS and Android
- implement platform-specific native views
- create a method channel for communication between Native and Flutter
- expose the Native views to Flutter
- optionally implement validation  and event listeners


Full sample integration
You can see an example demo of a Flutter integration with Native iOS and Android SDKs [in our `access-checkout-flutter-demo` GitHub repository](https://github.com/Worldpay/access-checkout-flutter-demo/tree/main/access_checkout_flutter_native_sdk_demo)

## On the Native side

### Add our iOS and Android Checkout SDK

Firstly, you must add the iOS and Android dependencies manually to the Android and iOS projects within your Flutter application.

#### Android: add SDK dependency

You must add the Android Checkout SDK to your `android/app/build.gradle`.

To ensure compatibility, the SDK also requires a minimum SDK version of `26`.


build.gradle.kts (Kotlin)
android/app/build.gradle.kts

```java android/app/build.gradle.kts
android {
    defaultConfig {
        minSdk = 26
    }
}

dependencies {
    implementation("com.worldpay.access:access-checkout-android:4.0.0")
}
```

build.gradle (Gradle)
android/app/build.gradle

```java android/app/build.gradle
android {
    defaultConfig {
        minSdk 26
    }
}

dependencies {
    implementation 'com.worldpay.access:access-checkout-android:4.0.0'
}
```

#### iOS: add SDK dependency

You must add the iOS Checkout SDK to your `ios/Podfile`.


Podfile
ios/Podfile

```ruby ios/Podfile
target 'Runner' do
  pod 'AccessCheckoutSDK'
end
```

### Implement native views

The Access Checkout SDK provides the UI elements, but to integrate them into Flutter you must make use of `PlatformViews`.

#### Android: create custom view

- `MainActivity.kt` - the entry point responsible for registering the ViewFactory and binding the ViewFactory to
a platform channel.
- `AccessCheckoutView.kt` - renders the Access Checkout UI, handles session generation and validation and communicates to
Flutter via the Method Channel.
- `AccessCheckoutViewFactory.kt` - creates and configures instances of `AccessCheckoutView`.
- `access_checkout_layout.xml` - contains the UI layout of the Access Checkout fields (Pan, Expiry and CVC).


MainActivity.kt
android/app/src/main/kotlin/com/example/flutter/MainActivity.kt

```kt android/app/src/main/kotlin/com/example/flutter/MainActivity.kt
package com.example.access_checkout_flutter_native_sdk_demo

import com.example.access_checkout_flutter_native_sdk_demo.AccessCheckoutViewFactory
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine

class MainActivity : FlutterActivity() {
    private val METHOD_CHANNEL_NAME = "com.worldpay.flutter/accesscheckout"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        val registry = flutterEngine
            .platformViewsController
            .registry

        registry
            .registerViewFactory(
                "com.worldpay.flutter/accesscheckout",
                AccessCheckoutViewFactory(
                    flutterEngine.dartExecutor.binaryMessenger,
                    METHOD_CHANNEL_NAME,
                    this
                )
            )
    }
}
```

AccessCheckoutView.kt
android/app/src/main/kotlin/com/example/flutter/AccessCheckoutView.kt

```kt android/app/src/main/kotlin/com/example/flutter/AccessCheckoutView.kt
package com.example.access_checkout_flutter_native_sdk_demo

import android.content.Context
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.view.LayoutInflater
import android.view.View
import androidx.lifecycle.LifecycleOwner
import com.worldpay.access.checkout.client.api.exception.AccessCheckoutException
import com.worldpay.access.checkout.client.session.AccessCheckoutClient
import com.worldpay.access.checkout.client.session.AccessCheckoutClientBuilder
import com.worldpay.access.checkout.client.session.listener.SessionResponseListener
import com.worldpay.access.checkout.client.session.model.CardDetails
import com.worldpay.access.checkout.client.session.model.SessionType
import com.worldpay.access.checkout.client.validation.AccessCheckoutValidationInitialiser
import com.worldpay.access.checkout.client.validation.config.CardValidationConfig
import com.worldpay.access.checkout.client.validation.listener.AccessCheckoutCardValidationListener
import com.worldpay.access.checkout.client.validation.model.CardBrand
import com.worldpay.access.checkout.ui.AccessCheckoutEditText
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView


class AccessCheckoutView(
    private val lifecycleOwner: LifecycleOwner,
    private val methodChannel: MethodChannel,
    context: Context,
    creationParams: Map<String, Any>,
) :
    PlatformView {
    private val layout: View
    private val panInput: AccessCheckoutEditText
    private val expiryInput: AccessCheckoutEditText
    private val cvcInput: AccessCheckoutEditText

    private var baseUrl: String = creationParams["baseUrl"] as String
    private var checkoutId: String = creationParams["checkoutId"] as String
    private var useCardValidation: Boolean =
        creationParams["useCardValidation"] as? Boolean ?: false

    private var accessCheckoutClient: AccessCheckoutClient

    init {
        layout = LayoutInflater.from(context)
            .inflate(R.layout.access_checkout_layout, null)

        panInput = layout.findViewById(R.id.pan_input)
        expiryInput = layout.findViewById(R.id.expiry_date_input)
        cvcInput = layout.findViewById(R.id.cvc_input)

        if (useCardValidation) {
            initializeCardValidation()
        }
        accessCheckoutClient = AccessCheckoutClientBuilder()
            .baseUrl(baseUrl)
            .checkoutId(checkoutId)
            .lifecycleOwner(lifecycleOwner)
            .sessionResponseListener(
                object : SessionResponseListener {
                    override fun onError(error: AccessCheckoutException) {
                        methodChannel.invokeMethod("onSessionError", "Could not create session")
                    }

                    override fun onSuccess(sessionResponseMap: Map<SessionType, String>) {
                        // Important: Flutter will not understand SessionType
                        // therefore it needs to be converted into a JSON-serializable format
                        val sessionData = sessionResponseMap.mapKeys { it.key.name }
                        methodChannel.invokeMethod("onSessionGenerated", sessionData)
                    }
                }
            )
            .context(context)
            .build()

        methodChannel.setMethodCallHandler { call, result ->
            when (call.method) {
                "generateSession" -> generateSession()
                else -> result.notImplemented()
            }
        }
    }

    //    private fun styleComponent() {}
    private fun initializeCardValidation() {
        val cardValidationConfig = CardValidationConfig.Builder()
            .baseUrl(baseUrl)
            .pan(panInput)
            .expiryDate(expiryInput)
            .cvc(cvcInput)
            .lifecycleOwner(lifecycleOwner)
            .enablePanFormatting()
            .validationListener(object : AccessCheckoutCardValidationListener {
                override fun onBrandChange(cardBrand: CardBrand?) {
                    // TODO: Update the brand image using your SVG loader
                }

                override fun onCvcValidated(isValid: Boolean) {
                    updateUIField(cvcInput, isValid)
                    if (!isValid) {
                        methodChannel.invokeMethod("onValidationUpdated", false)
                    }
                }

                override fun onExpiryDateValidated(isValid: Boolean) {
                    updateUIField(expiryInput, isValid)
                    if (!isValid) {
                        methodChannel.invokeMethod("onValidationUpdated", false)
                    }
                }

                override fun onPanValidated(isValid: Boolean) {
                    updateUIField(panInput, isValid)
                    if (!isValid) {
                        methodChannel.invokeMethod("onValidationUpdated", false)
                    }

                }

                override fun onValidationSuccess() {
                    methodChannel.invokeMethod("onValidationUpdated", true)
                }

            })
            .build()

        AccessCheckoutValidationInitialiser.initialise(cardValidationConfig)
    }

    private fun updateUIField(field: AccessCheckoutEditText, isValid: Boolean) {
        val colour = if (isValid) Color.GREEN else Color.RED

        //Update Text color
        field.setTextColor(colour)
        //Update Border
        val border = GradientDrawable()
        border.setStroke(3, colour)
        field.background = border
    }


    private fun generateSession() {

        val cardDetails = CardDetails.Builder()
            .pan(panInput)
            .expiryDate(expiryInput)
            .cvc(cvcInput)
            .build()


        accessCheckoutClient.generateSessions(cardDetails, listOf(SessionType.CARD))
    }

    override fun getView(): View = layout
    override fun dispose() {}
}
```

AccessCheckoutViewFactory.kt
android/app/src/main/kotlin/com/example/flutter/AccessCheckoutViewFactory.kt

```kt android/app/src/main/kotlin/com/example/flutter/AccessCheckoutViewFactory.kt
package com.example.access_checkout_flutter_native_sdk_demo

import android.content.Context
import androidx.lifecycle.LifecycleOwner
import com.example.access_checkout_flutter_native_sdk_demo.AccessCheckoutView
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory

class AccessCheckoutViewFactory(
    private val messenger: BinaryMessenger,
    private val channel: String,
    private val lifecycleOwner: LifecycleOwner
) :
    PlatformViewFactory(StandardMessageCodec.INSTANCE) {

    override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
        val methodChannel = MethodChannel(messenger, channel)
        val creationParams: Map<String, Any> = args as Map<String, Any>
        return AccessCheckoutView(lifecycleOwner, methodChannel, context, creationParams)
    }
}
```

access_checkout_layout.xml
android/app/src/main/res/layout/access_checkout_layout.xml

```xml android/app/src/main/res/layout/access_checkout_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/access_checkout_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:orientation="horizontal">

        <com.worldpay.access.checkout.ui.AccessCheckoutEditText
            android:id="@+id/pan_input"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="8"
            android:hint="Card number" />

        <ImageView
            android:id="@+id/brand_logo"
            android:layout_width="0dp"
            android:layout_height="45dp"
            android:layout_marginStart="8dp"
            android:scaleType="centerInside"
            android:layout_weight="2"
            android:src="@drawable/card_unknown_logo" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="10dp"
        android:orientation="horizontal">

        <com.worldpay.access.checkout.ui.AccessCheckoutEditText
            android:id="@+id/expiry_date_input"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:ems="4"
            android:hint="Expiry date"
            android:imeOptions="actionDone|flagNoFullscreen"
            android:inputType="text" />

        <com.worldpay.access.checkout.ui.AccessCheckoutEditText
            android:id="@+id/cvc_input"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_weight="1"
            android:ems="4"
            android:hint="CVC"
            android:imeOptions="actionDone|flagNoFullscreen"
            android:inputType="text" />
    </LinearLayout>
</LinearLayout>
```

#### iOS: create custom view

- `AppDelegate.swift` - the entry point responsible for registering the ViewFactory and binding the ViewFactory to
a platform channel.
- `AccessCheckoutView.swift` - renders the Access Checkout UI, handles session generation and validation and communicates
to Flutter via the Method Channel.
- `AccessCheckoutViewFactory.swift` - creates and configures instances of `AccessCheckoutView`.
- `AccessCheckoutViewController.swift` - manages the layout in storyboard and connects the UI elements.
- `AccessCheckoutView.storyboard` - contains the UI layout of the Access Checkout fields (Pan, Expiry and CVC).


AppDelegate.swift
ios/Runner/AppDelegate.swift

```swift ios/Runner/AppDelegate.swift
import Flutter
import UIKit

@main
@objc class AppDelegate: FlutterAppDelegate {
    private var METHOD_CHANNEL_NAME = "com.worldpay.flutter/accesscheckout"

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)

        weak var pluginRegistrar = registrar(forPlugin: "com.worldpay.flutter/accesscheckout")
        
        let factory = AccessCheckoutViewFactory(
            messenger: pluginRegistrar!.messenger(),
            channel: METHOD_CHANNEL_NAME)
        
        pluginRegistrar!.register(
            factory,
            withId: "com.worldpay.flutter/accesscheckout"
    
        )
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}
```

AccessCheckoutView.swift
ios/Runner/AccessCheckoutView.swift

```swift ios/Runner/AccessCheckoutView.swift
import AccessCheckoutSDK
import Flutter
import UIKit

class AccessCheckoutView: NSObject, FlutterPlatformView {
    @IBOutlet private var panInput: AccessCheckoutUITextField!
    @IBOutlet private var expiryInput: AccessCheckoutUITextField!
    @IBOutlet private var cvcInput: AccessCheckoutUITextField!
    private var accessCheckoutClient: AccessCheckoutClient
    private var methodChannel: FlutterMethodChannel
    private var _view: UIView

    private var baseUrl: String
    private var checkoutId: String
    private var useCardValidation: Bool

    private var controller: AccessCheckoutViewController


    init(
        methodChannel channel: FlutterMethodChannel,
        frame: CGRect,
        viewIdentifier viewId: Int64,
        binaryMessenger messenger: FlutterBinaryMessenger?,
        creationParams: [String: Any]
    ) {
        baseUrl = creationParams["baseUrl"] as? String ?? ""
        checkoutId = creationParams["checkoutId"] as? String ?? ""
        useCardValidation = creationParams["useCardValidation"] as? Bool ?? false
        methodChannel = channel

        accessCheckoutClient = try! AccessCheckoutClientBuilder()
            .accessBaseUrl(baseUrl)
            .checkoutId(checkoutId)
            .build()

        let storyboard = UIStoryboard(name: "AccessCheckoutView", bundle: Bundle.main)
        controller = storyboard.instantiateViewController(withIdentifier:"ViewController") as! AccessCheckoutViewController

        _view = controller.view!

        super.init()

        methodChannel.setMethodCallHandler({
            [weak self] (call, result) in
            guard let self = self else {return}

            switch call.method{
            case "generateSession":
                generateSession()
            default:
                result(FlutterMethodNotImplemented)

            }
        })

        // iOS views can be created here
        referenceNativeView()

        if(useCardValidation){
            initializeCardValidation()
        }
    }

    func initializeCardValidation() {
        let validationConfig = try! CardValidationConfig.builder()
            .pan(panInput)
            .expiryDate(expiryInput)
            .cvc(cvcInput)
            .accessBaseUrl(baseUrl)
            .validationDelegate(self)
            .enablePanFormatting()
                .build()


        AccessCheckoutValidationInitialiser().initialise(validationConfig)
    }

    func updateUIField(field: AccessCheckoutUITextField, isValid: Bool) {
        var  colour = isValid ? UIColor.green : UIColor.red

        //Update Text & border color
        field.textColor = colour
        field.borderColor = colour
    }

    func generateSession() {

        let cardDetails = try! CardDetailsBuilder().pan(panInput)
            .expiryDate(expiryInput)
            .cvc(cvcInput)
            .build()

        do { try accessCheckoutClient.generateSessions(
            cardDetails: cardDetails,
            sessionTypes: [SessionType.card]) { result in
                DispatchQueue.main.async {
                    switch result {
                    case .failure(let error):
                        print(error)
                        self.methodChannel.invokeMethod("onSessionError", arguments: "Could not create session")
                        
                    case .success(let sessions):
                        var sessionData: [String: String] = [:]
                        for (key, value) in sessions {
                            let keyName = String(describing: key).uppercased()
                            sessionData[keyName] = value
                        }
                        self.methodChannel.invokeMethod("onSessionGenerated", arguments: sessionData)
                    }
                }
            }
        }
        catch {
            self.methodChannel.invokeMethod("onSessionError", arguments: "Could not create session")
        }
    }

    func referenceNativeView() {
        panInput = controller.panInput
        expiryInput = controller.expiryInput
        cvcInput = controller.cvcInput
    }


    func view() -> UIView {
        return _view
    }

}

extension AccessCheckoutView: AccessCheckoutCardValidationDelegate {
    func cardBrandChanged(cardBrand: AccessCheckoutSDK.CardBrand?) {
        //TODO
    }

    func panValidChanged(isValid: Bool) {
        self.updateUIField(field: self.panInput, isValid: isValid)
        if(!isValid){
            self.methodChannel.invokeMethod("onValidationUpdated", arguments:false)
        }
    }

    func expiryDateValidChanged(isValid: Bool) {
        self.updateUIField(field: self.expiryInput, isValid: isValid)
        if(!isValid){
            self.methodChannel.invokeMethod("onValidationUpdated", arguments:false)
        }
    }

    func cvcValidChanged(isValid: Bool) {
        self.updateUIField(field: self.cvcInput, isValid: isValid)
        if(!isValid){
            self.methodChannel.invokeMethod("onValidationUpdated", arguments:false)
        }
    }

    func validationSuccess() {
        self.methodChannel.invokeMethod("onValidationUpdated", arguments:true)

    }

}
```

AccessCheckoutViewFactory.swift
ios/Runner/AccessCheckoutViewFactory.swift

```swift ios/Runner/AccessCheckoutViewFactory.swift
import Flutter
import UIKit

class AccessCheckoutViewFactory: NSObject, FlutterPlatformViewFactory {
    private var messenger: FlutterBinaryMessenger
    private var channel: String 

    init(messenger: FlutterBinaryMessenger, channel: String) {
        self.messenger = messenger
        self.channel = channel
        super.init()
    }

    
    func create(
        withFrame frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?
    ) -> FlutterPlatformView {
        let creationParams = args as? [String: Any] ?? [:]
        return AccessCheckoutView(
            methodChannel: FlutterMethodChannel(name: channel, binaryMessenger: messenger),
            frame: frame,
            viewIdentifier: viewId,
            binaryMessenger: messenger,
            creationParams: creationParams
        )
    }
    
    /// Implementing this method is only necessary when the `arguments` in `createWithFrame` is not `nil`.
    public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
        return FlutterStandardMessageCodec.sharedInstance()
    }
}
```

AccessCheckoutViewController.swift
ios/Runner/AccessCheckoutViewController.swift

```swift ios/Runner/AccessCheckoutViewController.swift
import AccessCheckoutSDK
import Flutter
import UIKit

class AccessCheckoutViewController: UIViewController {
    @IBOutlet weak var panInput: AccessCheckoutUITextField!
    @IBOutlet weak var cvcInput: AccessCheckoutUITextField!
    @IBOutlet weak var expiryInput: AccessCheckoutUITextField!
}
```

AccessCheckoutView.storyboard
ios/Runner/AccessCheckoutView.storyboard

```xml ios/Runner/AccessCheckoutView.storyboard
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
    <device id="retina4_7" orientation="portrait" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
        <capability name="System colors in document resources" minToolsVersion="11.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--Access Checkout View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController storyboardIdentifier="ViewController" id="BYZ-38-t0r" customClass="AccessCheckoutViewController" customModule="Runner" customModuleProvider="target" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="hy8-fq-jdj"/>
                        <viewControllerLayoutGuide type="bottom" id="P4U-sM-I5g"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                        <subviews>
                            <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uOE-eK-LJK" userLabel="Pan" customClass="AccessCheckoutUITextField" customModule="AccessCheckoutSDK">
                                <rect key="frame" x="0.0" y="20" width="375" height="45"/>
                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                                <accessibility key="accessibilityConfiguration" identifier="pan" label="Pan">
                                    <bool key="isElement" value="YES"/>
                                </accessibility>
                                <userDefinedRuntimeAttributes>
                                    <userDefinedRuntimeAttribute type="string" keyPath="placeholder" value="Card Number"/>
                                </userDefinedRuntimeAttributes>
                            </view>
                            <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ts8-Kj-GhN" userLabel="Expiry date" customClass="AccessCheckoutUITextField" customModule="AccessCheckoutSDK">
                                <rect key="frame" x="0.0" y="73" width="231" height="45"/>
                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                                <accessibility key="accessibilityConfiguration" identifier="expiryDate" label="Expiry date">
                                    <bool key="isElement" value="YES"/>
                                </accessibility>
                                <userDefinedRuntimeAttributes>
                                    <userDefinedRuntimeAttribute type="string" keyPath="placeholder" value="Expiry Date"/>
                                </userDefinedRuntimeAttributes>
                            </view>
                            <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="A4Q-yr-qYy" userLabel="Cvc" customClass="AccessCheckoutUITextField" customModule="AccessCheckoutSDK">
                                <rect key="frame" x="239" y="73" width="136" height="45"/>
                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                                <accessibility key="accessibilityConfiguration" identifier="cvc" label="Cvc">
                                    <bool key="isElement" value="YES"/>
                                </accessibility>
                                <userDefinedRuntimeAttributes>
                                    <userDefinedRuntimeAttribute type="string" keyPath="placeholder" value="CVC"/>
                                </userDefinedRuntimeAttributes>
                            </view>
                        </subviews>
                    </view>
                    <connections>
                        <outlet property="cvcInput" destination="A4Q-yr-qYy" id="N9y-q9-Abt"/>
                        <outlet property="expiryInput" destination="ts8-Kj-GhN" id="V4f-nQ-htV"/>
                        <outlet property="panInput" destination="uOE-eK-LJK" id="uvL-eo-qJW"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="-698.39999999999998" y="831.63418290854577"/>
        </scene>
    </scenes>
    <resources>
        <systemColor name="systemBackgroundColor">
            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
    </resources>
</document>
```

## On the Flutter side

### Set up method channels for communication

To communicate with the native side you must create a `service` that handles the communication between Flutter and Native.

In this class, you connect to our previously defined method channel `com.worldpay.flutter/accesscheckout` on the Native
iOS and Android platforms.

You must tell Flutter how to handle the callbacks issues when invoking the `generateSession` method on the native side.

Optionally you can also define the logic for handling validation updates, in case you would like to handle states.

Prerequisite
The name of the method channel and methods must be the same between the Flutter and Native sides.
We are using `com.worldpay.flutter/accesscheckout` as the method channel and `generateSession` as the method name, as an example.

access_checkout_flutter.dart
lib/service/access_checkout_flutter.dart

```java lib/service/access_checkout_flutter.dart
import 'package:flutter/services.dart';

class AccessCheckoutFlutter {
  static const channel = MethodChannel('com.worldpay.flutter/accesscheckout');

  static Future<void> listenForValidationUpdates(Function(bool) onValidationUpdated) async {
    channel.setMethodCallHandler((call) async {
      if (call.method case "onValidationUpdated") {
        onValidationUpdated(call.arguments as bool);
      }
    });
  }

  static Future<void> generateSession(
      {required Function(Map<dynamic, dynamic>) onSuccess,
      required Function(String) onError}) async {
    channel.setMethodCallHandler((call) async {
      switch (call.method) {
        case "onSessionGenerated":
          onSuccess(call.arguments);

        case "onSessionError":
          onError(call.arguments);
      }
    });

    await channel.invokeMethod<String>('generateSession');
  }
}
```

### Embed native views in Flutter

To render our native views, you must handle the target platform and display a `PlatformViewLink` for Android or a
`UiKitView` for iOS.

We recommend to create a widget to handle this logic `lib/widgets/access_checkout_native_widget.dart`,
so that this widget is the only one responsible for rendering the native views.

access_checkout_native_widget.dart
lib/widgets/access_checkout_native_widget.dart

```java lib/widgets/access_checkout_native_widget.dart
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';

class AccessCheckoutNativeWidget extends StatelessWidget {
  final String checkoutId;
  final String baseUrl;
  final bool useCardValidation;

  const AccessCheckoutNativeWidget({
    super.key,
    required this.checkoutId,
    required this.baseUrl,
    required this.useCardValidation,
  });

  static const StandardMessageCodec _decoder = StandardMessageCodec();

  @override
  Widget build(BuildContext context) {
    const String viewType = "com.worldpay.flutter/accesscheckout";
    final Map<String, dynamic> creationParams = <String, dynamic>{
      "baseUrl": baseUrl,
      "checkoutId": checkoutId,
      "useCardValidation": useCardValidation
    };

    switch (defaultTargetPlatform) {
      case TargetPlatform.android:

  return PlatformViewLink(
    viewType: viewType,
    surfaceFactory: (context, controller) {
      return AndroidViewSurface(
        controller: controller as AndroidViewController,
        gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
        hitTestBehavior: PlatformViewHitTestBehavior.opaque,
      );
    },
    onCreatePlatformView: (params) {
      return PlatformViewsService.initSurfaceAndroidView(
          id: params.id,
          viewType: viewType,
          layoutDirection: TextDirection.ltr,
          creationParams: creationParams,
          creationParamsCodec: _decoder,
          onFocus: () {
            params.onFocusChanged(true);
          },
        )
        ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
        ..create();
    },
  );
      case TargetPlatform.iOS:
        return UiKitView(
          viewType: viewType,
          layoutDirection: TextDirection.ltr,
          creationParams: creationParams,
          creationParamsCodec: const StandardMessageCodec(),
        );
      default:
        throw UnsupportedError("Unsupported platform view");
    }
  }
}
```

### Render the Flutter component

You can now render our newly created widget that integrates our Native Checkout SDKs into Flutter.

To achieve this, create another widget where you can orchestrate, render and handle some state for when a session is created.

This widget will also be responsible for initializing and configuring the SDKs, using parameters such as `checkoutId`,
`baseUrl`, `useCardValidation`

Finally, you must display this `AccessCheckoutWidget` in our Flutter page and configure the parameters it requires.


native_sdk_page.dart
lib/screens/native_sdk_page.dart

```java lib/screens/native_sdk_page.dart
import 'package:access_checkout_flutter_native_sdk_demo/widgets/access_checkout.dart';
import 'package:flutter/material.dart';

class NativeSdkPage extends StatelessWidget {
  const NativeSdkPage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Center(
        child: AccessCheckoutWidget(
            // TODO: Replace the checkout id and base url with the values provided to you
            checkoutId: "00000000-0000-0000-0000-000000000000",
            baseUrl: "https://try.access.worldpay-bsh.securedataplatform.com",
            useCardValidation: true));
  }
}
```

access_checkout.dart
lib/widgets/access_checkout.dart

```java lib/widgets/access_checkout.dart
import 'package:access_checkout_flutter_native_sdk_demo/service/access_checkout_flutter.dart';
import 'package:access_checkout_flutter_native_sdk_demo/widgets/access_checkout_native_widget.dart';
import 'package:flutter/material.dart';

class AccessCheckoutWidget extends StatefulWidget {
  final String checkoutId;
  final String baseUrl;
  final bool useCardValidation;

  const AccessCheckoutWidget({
    super.key,
    required this.checkoutId,
    required this.baseUrl,
    required this.useCardValidation,
  });

  @override
  AccessCheckoutWidgetState createState() => AccessCheckoutWidgetState();
}

class AccessCheckoutWidgetState extends State<AccessCheckoutWidget> {
  bool isSubmitButtonEnabled = false;
  String sessionToken = "";
  late String checkoutId;
  late String baseUrl;
  late bool useCardValidation;

  @override
  void initState() {
    super.initState();
    checkoutId = widget.checkoutId;
    baseUrl = widget.baseUrl;
    useCardValidation = widget.useCardValidation;

    if (useCardValidation) {
      AccessCheckoutFlutter.listenForValidationUpdates((isInputValid) {
        setState(() {
          isSubmitButtonEnabled = isInputValid;
        });
      });
    } else {
      setState(() {
        isSubmitButtonEnabled = true;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Padding(
            padding: const EdgeInsets.all(20),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                SizedBox(
                    height: 150,
                    child: AccessCheckoutNativeWidget(
                        checkoutId: checkoutId,
                        baseUrl: baseUrl,
                        useCardValidation: useCardValidation)),
                Row(children: <Widget>[
                  Expanded(
                    flex: 1,
                    child: OutlinedButton(
                      onPressed: isSubmitButtonEnabled
                          ? () => generateSession()
                          : null,
                      child: const Text('Submit'),
                    ),
                  )
                ]),
                if (sessionToken != "") Text(sessionToken),
              ],
            )));
  }

  Future<void> generateSession() async {
    await AccessCheckoutFlutter.generateSession(onSuccess: (sessions) {
      setState(() {
        sessionToken = sessions["CARD"]!;
      });
      showSnackBar(sessions["CARD"]!);
    }, onError: (error) {
      showSnackBar(error);
    });
  }

  void showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(message), duration: const Duration(seconds: 5)));
  }
}
```