Integrating with Other Code

Flutter stands out for creating visually appealing apps across multiple platforms with a single codebase. However, developers often face situations where Flutter needs to interact with platform-specific code or native libraries.

This integration is crucial for accessing device capabilities or incorporating existing native code into Flutter apps. Let's explore how Flutter achieves this seamless integration.

Platform Channels: Bridging Dart and Native Code

Platform channels are the primary way Flutter communicates with native code. This method is essential when your app needs to use device-specific features not available in Flutter's framework, such as accessing battery level, using Bluetooth, or implementing a native third-party SDK.

How Platform Channels Work

Platform channels use a simple yet powerful system for message passing. You define a channel in your Dart code and specify how it communicates with the native side of your app, whether it's written in Kotlin/Java for Android or Swift/Objective-C for iOS.

Dart Side:

You initiate communication with a method call over the channel, specifying the method name and any arguments.

const channel = MethodChannel('com.example/battery');
final batteryLevel = await channel.invokeMethod('getBatteryLevel');

Native Side (Kotlin for Android example):

You respond to these method calls on the native side, implementing the logic required and returning the result back to Dart.

MethodChannel(flutterEngine.dartExecutor, "com.example/battery").setMethodCallHandler {
    call, result ->
    if (call.method == "getBatteryLevel") {
        val batteryLevel = getBatteryLevel()
        result.success(batteryLevel)
    } else {
        result.notImplemented()
    }
}

This two-way communication channel allows Flutter apps to utilize native features and libraries seamlessly.

Foreign Function Interface (FFI): Direct Native Code Invocation

For direct interaction with C-based libraries, Flutter offers the Foreign Function Interface (FFI). This interface is more direct and often faster than platform channels because it allows Dart code to call C functions directly, without the need for message serialization.

C Function Example

Imagine we have a C function that multiplies two numbers.

// multiply.c
#include <stdint.h>

int32_t multiply(int32_t x, int32_t y) {
    return x * y;
}

Compiling C Code to a Library

You compile this C code into a shared library (libmultiply.so or libmultiply.dylib), depending on your operating system.

Dart FFI with Typedef

In Dart, you use dart:ffi to interoperate with this C function. First, define the typedefs matching the C function signature, then load the library and call the function.

import 'dart:ffi';
import 'package:ffi/ffi.dart';

// Typedefs for the C function
typedef MultiplyC = Int32 Function(Int32 x, Int32 y);
typedef MultiplyDart = int Function(int x, int y);

void main() {
  // Load the shared library
  final dylib = DynamicLibrary.open('libmultiply.so'); // Adjust the library name as needed

  // Get a reference to the multiply function
  final MultiplyDart multiply = dylib
      .lookupFunction<MultiplyC, MultiplyDart>('multiply');

  // Call the C function from Dart
  final result = multiply(4, 5);
  print('4 * 5 = $result');
}

In this streamlined example, DynamicLibrary.open loads the compiled C library, lookupFunction maps the multiply C function to a callable Dart function, and then you can use multiply just like any Dart function. This demonstrates the basic steps for integrating C code into a Dart (Flutter) application using FFI and typedef for clear and type-safe function signatures.

This approach is particularly useful for CPU-intensive tasks like image processing, cryptographic operations, or direct interaction with native system APIs.

Rendering Native Controls within Flutter Widgets

In some cases, you might want to display native Android or iOS controls within your Flutter app. Flutter accommodates this through platform views (AndroidView and UiKitView), allowing you to embed native components directly within your Flutter widget tree.

Implementing Platform Views

You can use AndroidView or UiKitView widgets to embed native components. This method is typically used for complex controls that would be impractical to recreate in Flutter, such as a web view or a map view.

Widget build(BuildContext context) {
  return Platform.isAndroid 
    ? AndroidView(viewType: 'native-view')
    : UiKitView(viewType: 'native-view');
}

While powerful, platform views should be used judiciously due to their performance implications, as they introduce overhead in rendering and event handling.

Embedding Flutter in Existing Applications

Conversely, Flutter allows embedding its content into existing Android or iOS applications. This feature is invaluable for gradually migrating an existing app to Flutter or adding new Flutter-based features to an established app.

Integrating Flutter Modules

To integrate Flutter into an existing app, you create a Flutter module and include it in your Android or iOS project. This process involves configuring the native project to host Flutter content, ensuring a smooth user experience and performance.

// Android example
val flutterFragment = FlutterFragment.createDefault()
supportFragmentManager
    .beginTransaction()
    .add(R.id.flutter_container, flutterFragment)
    .commit()

This flexibility enables developers to leverage Flutter's productivity and UI capabilities without rewriting their entire application.

Key Takeaways

  • Platform Channels offer a flexible way to communicate between Dart and native code, enabling Flutter apps to use native features.

  • Foreign Function Interface (FFI) provides direct access to C functions from Dart, suitable for performance-critical native operations.

  • Platform Views allow embedding native Android and iOS controls within Flutter apps, useful for complex native components.

  • Flutter Integration in Native Apps enables adding Flutter modules to existing Android or iOS applications, facilitating gradual migration or feature enhancement.

Last updated