What are the steps to integrate native code with a Flutter application for both Android and iOS?
Published: 2024-06-24 14:05:17

Integrating native code with a Flutter application allows you to utilize platform-specific features and APIs that may not be available directly through Flutter's framework. This process involves writing platform-specific code in languages like Java or Kotlin for Android, and Swift or Objective-C for iOS. Here, we'll walk through the steps to integrate native code with a Flutter app for both Android and iOS, including detailed explanations and examples.

## Understanding Flutter's Platform Channels

Flutter uses platform channels to communicate between the Dart code and the native code. A platform channel allows sending messages between the Flutter framework and the platform-specific code. These channels use a binary message codec to encode and decode messages and can be used for both method invocation and event handling.

**Steps to Integrate Native Code with a Flutter Application**

**1. Setting Up a New Flutter Project**

First, create a new Flutter project if you don't already have one:

flutter create native_integration_example
cd native_integration_example

**2. Understanding the Project Structure**

In the Flutter project, the platform-specific code resides in the android and ios directories:

android: Contains the Android-specific code in Java/Kotlin.
ios: Contains the iOS-specific code in Swift/Objective-C.

**3. Creating a Platform Channel**

Define a method channel in your Flutter code. This channel will be used to communicate with the native code.

In your Flutter project's lib/main.dart file, create a method channel:

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  static const platform = MethodChannel('com.example.native_integration_example/channel');

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Native Code Integration')),
        body: Center(
          child: ElevatedButton(
            onPressed: _getNativeMessage,
            child: Text('Get Native Message'),
          ),
        ),
      ),
    );
  }

  Future<void> _getNativeMessage() async {
    String message;
    try {
      final String result = await platform.invokeMethod('getNativeMessage');
      message = 'Native Message: $result';
    } on PlatformException catch (e) {
      message = "Failed to get native message: '${e.message}'.";
    }

    print(message);
  }
}


**4. Implementing Native Code for Android**

Navigate to the android directory in your Flutter project. Open the MainActivity.java or MainActivity.kt file located at android/app/src/main/java/com/example/native_integration_example/.

Add the following code to handle the platform channel and return a native message:

**Java (MainActivity.java):**

package com.example.native_integration_example;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;

public class MainActivity extends FlutterActivity {
  private static final String CHANNEL = "com.example.native_integration_example/channel";

  @Override
  public void configureFlutterEngine(FlutterEngine flutterEngine) {
    super.configureFlutterEngine(flutterEngine);

    new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
      .setMethodCallHandler((call, result) -> {
        if (call.method.equals("getNativeMessage")) {
          result.success("Hello from Android Native Code");
        } else {
          result.notImplemented();
        }
      });
  }
}


## Kotlin (MainActivity.kt):

package com.example.native_integration_example

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
  private val CHANNEL = "com.example.native_integration_example/channel"

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

    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
      if (call.method == "getNativeMessage") {
        result.success("Hello from Android Native Code")
      } else {
        result.notImplemented()
      }
    }
  }
}


**5. Implementing Native Code for iOS**

Navigate to the ios directory in your Flutter project. Open the AppDelegate.swift or AppDelegate.m file located at ios/Runner/.

Add the following code to handle the platform channel and return a native message:

**Swift (AppDelegate.swift):**

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    let controller = window?.rootViewController as! FlutterViewController
    let channel = FlutterMethodChannel(name: "com.example.native_integration_example/channel",
                                       binaryMessenger: controller.binaryMessenger)
    channel.setMethodCallHandler { (call, result) in
      if call.method == "getNativeMessage" {
        result("Hello from iOS Native Code")
      } else {
        result(FlutterMethodNotImplemented)
      }
    }
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}


**Objective-C (AppDelegate.m):**

#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];

  FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
  FlutterMethodChannel* channel = [FlutterMethodChannel
      methodChannelWithName:@"com.example.native_integration_example/channel"
            binaryMessenger:controller.binaryMessenger];
  [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    if ([@"getNativeMessage" isEqualToString:call.method]) {
      result(@"Hello from iOS Native Code");
    } else {
      result(FlutterMethodNotImplemented);
    }
  }];

  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

**6. Running the Application**

After implementing the native code for both Android and iOS, you can run your Flutter application on a device or emulator:

flutter run

When you press the "Get Native Message" button, the app will communicate with the native code and display the message from the respective platform.

## Conclusion

Integrating native code with a Flutter application involves creating a platform channel for communication, implementing the native code in the appropriate platform directories, and handling method calls and results. This approach allows you to leverage platform-specific features and APIs while maintaining a single codebase for your Flutter application. By following the steps outlined above, you can seamlessly integrate native code for both Android and iOS, enhancing the functionality and performance of your Flutter apps.