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.

Android

Create the view wrapper

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

    init {
        val view = ConversationListView(context)

        view.doOnAttach {
            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 getView(): View = view

    override fun dispose() {}

    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:

class MainActivity: FlutterFragmentActivity() {
    private val delegate = AppCompatDelegate.create(this, null)

    init {
        addOnContextAvailableListener {
            delegate.installViewFactory()
        }
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        // Your other wiring here...

        flutterEngine.platformViewsController
            .registry
            .registerViewFactory("nablaConversations", NablaAndroidConversationsViewFactory())
    }
}

⚠️

If you activity is of type FlutterActivity, change it to FlutterFragmentActivity (io.flutter.embedding.android.FlutterFragmentActivity)

iOS

Create the view wrapper

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

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

    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?

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

        _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")
            return
        }

        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()
        appearance.configureWithOpaqueBackground()
        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())
registrar!.register(
    factory,
    withId: "nablaConversations"
)

Flutter

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> creationParams) {
  return AndroidView(
    viewType: viewType,
    layoutDirection: TextDirection.ltr,
    creationParams: creationParams,
    creationParamsCodec: const StandardMessageCodec(),
  );
}

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

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

  @override
  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);
      default:
        throw UnsupportedError('Unsupported platform view');
    }
  }
}

You can now use the NablaConversationsWidget in your Flutter code.