Wrap native views

This section provides a guide about how to create a bridge for a native View for Flutter.

It uses the ConversationListView as an example, but you can adapt it to any View of the SDK.

See the Flutter Android view bridging doc and the Flutter iOS view bridging doc for more details.


Create the view wrapper

class NablaAndroidConversationsView(
    private val context: Context,
    id: Int,
    creationParams: Map<String?, Any?>?,
) : PlatformView {
    private var view: ConversationListView? = null

    override fun getView(): View = view ?: kotlin.run {
        val view = ConversationListView(context)

        val activity = context.getComponentActivityOrThrow()
        val viewModel: ConversationListViewModel by activity.viewModels {
            ConversationListViewModelFactory(owner = activity)

        view.bindViewModel(viewModel, onConversationClicked = { conversationId ->
            activity.startActivity(ConversationActivity.newIntent(context, conversationId))

        this.view = view

    override fun dispose() {
        view = null

    private fun Context.getComponentActivityOrThrow(): ComponentActivity {
        if (this is ComponentActivity) {
            return this

        var currentContext = this
        while (currentContext is ContextWrapper) {
            if (currentContext is ComponentActivity) {
                return currentContext
            currentContext = currentContext.baseContext

        throw IllegalStateException("Unable to find activity")

class NablaAndroidConversationsViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
    override fun create(context: Context?, viewId: Int, args: Any?): PlatformView {
        val creationParams = args as Map<String?, Any?>?
        return NablaAndroidConversationsView(context!!, viewId, creationParams)

Wire the wrapper

In the MainActivity file, add these lines to wire your newly created native view:

    .registerViewFactory("nablaConversations", NablaAndroidConversationsViewFactory())


Create the view wrapper

class NablaConversationViewFactory : NSObject, FlutterPlatformViewFactory {
    private let messenger: FlutterBinaryMessenger

    init(messenger: FlutterBinaryMessenger) {
        self.messenger = messenger

    func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
        return NablaConversationNativeView(
            frame: frame,
            viewIdentifier: viewId,
            arguments: args,
            binaryMessenger: messenger

class NablaConversationNativeView : NSObject, FlutterPlatformView, ConversationListDelegate {
    private var _view: UIView
    private var nablaNavigationController: UINavigationController?

        frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?,
        binaryMessenger messenger: FlutterBinaryMessenger
    ) {
        _view = UIView()

        _view = NablaMessagingClient.shared.views.createConversationListView(delegate: self)
        _view.frame = frame

    func view() -> UIView {
        return _view

    func conversationList(didSelect conversation: Conversation) {
        guard let controller : UIViewController = _view.window?.rootViewController else {
            print("Unable to find parent UIViewController")

        let conversationViewController = NablaClient.shared.messaging.views.createConversationViewController(conversationId)

        // We wrap the ConversationViewController inside a UINavigationController
        // so that the navigation bar is displayed in the conversation

        nablaNavigationController = UINavigationController(
            rootViewController: conversationViewController
        conversationViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(
            image: UIImage(systemName: "xmark"),
            style: .plain,
            target: self,
            action: #selector(self.dismissNavigationController))
        let appearance = UINavigationBarAppearance()
        nablaNavigationController?.navigationBar.standardAppearance = appearance
        nablaNavigationController?.navigationBar.scrollEdgeAppearance = appearance
        nablaNavigationController?.modalPresentationStyle = .fullScreen

        controller.present(nablaNavigationController!, animated: true, completion: nil)

    @objc private func dismissNavigationController() {
        nablaNavigationController?.dismiss(animated: true)
        nablaNavigationController = nil

Wire the wrapper

In the AppDelegate file, add these lines to wire your newly created view:

weak var registrar = self.registrar(forPlugin: "nabla")

let factory = NablaConversationViewFactory(messenger: registrar!.messenger())
    withId: "nablaConversations"


Create a file named nablaconversationlistwidget.dart:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/foundation.dart';

const String viewType = 'nablaConversations';

Widget _buildAndroid(BuildContext context, Map<String, dynamic> params) {
  return AndroidView(
    viewType: viewType,
    layoutDirection: TextDirection.ltr,
    creationParams: params,
    creationParamsCodec: const StandardMessageCodec(),

Widget _buildIoS(BuildContext context, Map<String, dynamic> params) {
  return UiKitView(
    viewType: viewType,
    layoutDirection: TextDirection.ltr,
    creationParams: params,
    creationParamsCodec: const StandardMessageCodec(),

class NablaConversationsWidget extends StatelessWidget {
  const NablaConversationsWidget({
    Key? key,
  }) : super(key: key);

  Widget build(BuildContext context) {
    // Pass parameters to the platform side.
    final Map<String, dynamic> params = <String, dynamic>{};

    switch (defaultTargetPlatform) {
      case TargetPlatform.android:
        return _buildAndroid(context, params);
      case TargetPlatform.iOS:
        return _buildIoS(context, params);
        throw UnsupportedError('Unsupported platform view');

You can now use the NablaConversationsWidget in your Flutter code.