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.

Android

Create the Fragment wrapper

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

    override fun getView(): View = view ?: kotlin.run {
        val fragment = AppointmentsFragment.newInstance()

        val viewId = Random().nextInt()

        val activity = context.getFragmentActivityOrThrow()
        val view = FragmentContainerView(context)
        view.setId(viewId)

        activity.supportFragmentManager.commit {
            replace(viewId, fragment)
        }

        this.view = view
        view
    }

    override fun dispose() {
        view = null
    }

    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:

flutterEngine.platformViewsController
    .registry
    .registerViewFactory("nablaAppointments", NablaAppointmentsViewFactory())

iOS

Create the ViewController wrapper

class NablaAppointmentsViewFactory : 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 NablaAppointmentsNativeView(
            frame: frame,
            viewIdentifier: viewId,
            arguments: args,
            binaryMessenger: messenger
        )
    }
}

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

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

        _viewController = NablaSchedulingClient.shared.views.createAppointmentListViewController()
        _viewController.view.frame = frame
    }

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

Flutter

Create a file named nablaappointmentswidget.dart:

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

const String viewType = 'nablaAppointments';

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 NablaAppointmentsWidget 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 NablaAppointmentsWidget in your Flutter code.