Wrap native ViewController/Fragment

This section provides a guide about how to create a bridge for a native ViewController (on iOS) and Fragment (on Android) for Flutter.

It uses AppointmentsFragment/AppointmentListViewController as an example, but you can adapt it to any ViewController/Fragment of the SDK.


Create the Fragment wrapper

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

    init {
        val fragment = AppointmentsFragment.newInstance()

        val viewId = Random().nextInt()
        val view = FragmentContainerView(context)

        view.doOnAttach {
            val activity = it.context.getFragmentActivityOrThrow()
            activity.supportFragmentManager.findFragmentByTag("flutter_fragment")?.let { flutterFragment ->
                flutterFragment.childFragmentManager.commit {
                    replace(it.id, fragment)

        this.view = view

    override fun getView(): View = view

    override fun dispose() {}

    private fun Context.getFragmentActivityOrThrow(): FragmentActivity {
        if (this is FragmentActivity) {
            return this

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

        throw IllegalStateException("Unable to find activity")

class NablaAppointmentsViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
    override fun create(context: Context?, viewId: Int, args: Any?): PlatformView {
        val creationParams = args as Map<String?, Any?>?
        return NablaAppointmentsView(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 {

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {

        // Your other wiring here...

            .registerViewFactory("nablaAppointments", NablaAppointmentsViewFactory())


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


Create the ViewController wrapper

class NablaAppointmentsViewFactory : NSObject, FlutterPlatformViewFactory {
    private let messenger: FlutterBinaryMessenger
    private let rootViewController: UIViewController

    init(messenger: FlutterBinaryMessenger, rootViewController: UIViewController) {
        self.messenger = messenger
        self.rootViewController = rootViewController

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

class NablaAppointmentsNativeView : NSObject, FlutterPlatformView {
    private var _viewController: UIViewController

        frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?,
        binaryMessenger messenger: FlutterBinaryMessenger,
        parentController: UIViewController
    ) {
        _viewController = UIViewController(nibName: nil, bundle: nil)

        _viewController = NablaSchedulingClient.shared.views.createAppointmentListViewController()
        _viewController.view.frame = frame
        _viewController.didMove(toParent: parentController)

    func view() -> UIView {
        return _viewController.view

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 = NablaAppointmentsViewFactory(messenger: registrar!.messenger(), rootViewController: flutterViewController)
    withId: "nablaAppointments"


Create a file named nablaappointmentswidget.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';

const String viewType = 'nablaAppointments';

Widget _buildAndroid(BuildContext context, Map<String, dynamic> creationParams) {
  return PlatformViewLink(
    viewType: viewType,
        (context, controller) {
      return AndroidViewSurface(
        controller: controller as AndroidViewController,
        gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
        hitTestBehavior: PlatformViewHitTestBehavior.opaque,
    onCreatePlatformView: (params) {
      return PlatformViewsService.initExpensiveAndroidView(
        id: params.id,
        viewType: viewType,
        layoutDirection: TextDirection.ltr,
        creationParams: creationParams,
        creationParamsCodec: const StandardMessageCodec(),
        onFocus: () {

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

class NablaAppointmentsWidget extends StatelessWidget {
  const NablaAppointmentsWidget({
    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 NablaAppointmentsWidget in your Flutter code.