Как вызвать нативный код из Flutter?

«Как вызвать нативный код из Flutter?» — вопрос из категории Платформенное взаимодействие, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Для взаимодействия с нативным кодом (Android/iOS) в Flutter используется Platform Channels. Вот полный пример получения уровня заряда батареи:

1. Настройка MethodChannel в Dart:

import 'package:flutter/services.dart';

class BatteryService {
  static const MethodChannel _channel = 
      MethodChannel('com.example.app/battery');

  static Future<int> getBatteryLevel() async {
    try {
      final int level = await _channel.invokeMethod('getBatteryLevel');
      return level;
    } on PlatformException catch (e) {
      print('Ошибка получения уровня батареи: ${e.message}');
      return -1;
    }
  }
}

// Использование:
int batteryLevel = await BatteryService.getBatteryLevel();
print('Уровень батареи: $batteryLevel%');

2. Реализация на Android (Kotlin):

package com.example.app

import android.content.Context
import android.content.Context.BATTERY_SERVICE
import android.os.BatteryManager
import androidx.annotation.NonNull
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.app/battery"

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

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
            .setMethodCallHandler { call, result ->
                when (call.method) {
                    "getBatteryLevel" -> {
                        val batteryLevel = getBatteryLevel()
                        if (batteryLevel != -1) {
                            result.success(batteryLevel)
                        } else {
                            result.error(
                                "UNAVAILABLE", 
                                "Не удалось получить уровень батареи", 
                                null
                            )
                        }
                    }
                    else -> result.notImplemented()
                }
            }
    }

    private fun getBatteryLevel(): Int {
        val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
        return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
    }
}

3. Реализация на iOS (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 batteryChannel = FlutterMethodChannel(
            name: "com.example.app/battery",
            binaryMessenger: controller.binaryMessenger
        )

        batteryChannel.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) in
            guard call.method == "getBatteryLevel" else {
                result(FlutterMethodNotImplemented)
                return
            }

            self?.receiveBatteryLevel(result: result)
        }

        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

    private func receiveBatteryLevel(result: FlutterResult) {
        let device = UIDevice.current
        device.isBatteryMonitoringEnabled = true

        if device.batteryState == .unknown {
            result(FlutterError(
                code: "UNAVAILABLE",
                message: "Информация о батарее недоступна",
                details: nil
            ))
        } else {
            result(Int(device.batteryLevel * 100))
        }
    }
}

4. Альтернативные подходы:

  • EventChannel — для потоковой передачи данных от нативной платформы
  • BasicMessageChannel — для простых сообщений с автоматической сериализацией
  • Pigeon — кодогенерация для type-safe каналов (рекомендуется для production)
  • dart:ffi — для прямого вызова C/C++ библиотек (минуя Platform Channels)

Best Practices:

  1. Используйте уникальные имена каналов с reverse domain notation
  2. Всегда обрабатывайте ошибки и случай notImplemented
  3. Для сложных структур данных используйте JSON-сериализацию
  4. Тестируйте на обеих платформах, так как поведение может отличаться
  5. Для часто используемого функционала создавайте плагины