Разработка Platform Channel для Flutter-приложения (iOS)

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Услуги, которые мы предлагаем
Показано 1 из 1Все 1735 услуг
Разработка Platform Channel для Flutter-приложения (iOS)
Сложный
~3-5 дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    495

Разработка Platform Channel для Flutter-приложения (iOS)

Flutter отлично закрывает 80–90% задач через Dart-пакеты. Но когда нужен прямой доступ к нативному iOS-API — CoreNFC, NetworkExtension для VPN, AVFoundation с кастомными настройками сессии, PassKit для Wallet-пропусков — Dart-обёртки либо не существуют, либо устарели на 2 версии SDK. Вот здесь пишется Platform Channel вручную.

Три типа каналов и когда что использовать

Flutter предоставляет три механизма коммуникации с нативным кодом.

MethodChannel — запрос/ответ. Dart вызывает метод, Swift отвечает один раз. Подходит для большинства задач: биометрия, работа с Keychain, одноразовые запросы к системным API. Самый распространённый тип.

EventChannel — поток данных от нативного кода к Dart. Используется для подписок: данные с датчиков через CoreMotion, статус Bluetooth-соединения через CoreBluetooth, изменения сетевого интерфейса через NWPathMonitor. Данные текут непрерывно, пока Dart-сторона слушает.

BasicMessageChannel — произвольный обмен сообщениями в обе стороны с кастомным кодеком. Редкий случай, нужен когда стандартный StandardMessageCodec (поддерживает String, int, double, List, Map, Uint8List) недостаточен.

Смешивать типы без причины не стоит: видел проекты, где EventChannel использовали там, где достаточно MethodChannel с одним вызовом — это создаёт лишние подписки и утечки памяти, если StreamController не закрывается при уничтожении канала.

Где ломается чаще всего

Потоки и FlutterResult

FlutterResult — это Objective-C callback, который Flutter передаёт в Swift для ответа. Главное правило: вызывать его строго один раз. Вызвал дважды — крэш в рантайме с сообщением Call to FlutterResult callback after it has been released.

Типичная ловушка с AVCaptureSession: метод запускает сессию захвата асинхронно, результат приходит через completion на background queue. Если не диспатчить result через DispatchQueue.main.async, Flutter иногда получает ответ на неожиданном потоке — поведение непредсказуемое, баг воспроизводится не всегда.

channel.setMethodCallHandler { [weak self] call, result in
  guard call.method == "startCapture" else {
    result(FlutterMethodNotImplemented)
    return
  }
  self?.session.startRunning(completion: { success, error in
    DispatchQueue.main.async {
      if let error = error {
        result(FlutterError(code: "CAPTURE_ERROR",
                            message: error.localizedDescription,
                            details: nil))
      } else {
        result(success)
      }
    }
  })
}

Сериализация через StandardMessageCodec

StandardMessageCodec умеет в Uint8List, что спасает при передаче небольших бинарных данных (превью изображения, зашифрованный payload). Но для объектов сложнее словаря — всё равно нужна ручная сериализация. Попытка передать Data напрямую без конвертации в FlutterStandardTypedData приводит к silent failure: Dart получает null вместо данных.

EventChannel и утечки

FlutterEventSink нужно обнулять в onCancel:

final class SensorStreamHandler: NSObject, FlutterStreamHandler {
  private var motionManager = CMMotionManager()
  private var eventSink: FlutterEventSink?

  func onListen(withArguments arguments: Any?,
                eventSink events: @escaping FlutterEventSink) -> FlutterError? {
    eventSink = events
    motionManager.startAccelerometerUpdates(to: .main) { [weak self] data, _ in
      guard let data = data else { return }
      self?.eventSink?(["x": data.acceleration.x, "y": data.acceleration.y])
    }
    return nil
  }

  func onCancel(withArguments arguments: Any?) -> FlutterError? {
    motionManager.stopAccelerometerUpdates()
    eventSink = nil  // критично: без этого будут обращения к освобождённому объекту
    return nil
  }
}

Пропустить eventSink = nil — значит получить EXC_BAD_ACCESS через несколько минут работы, когда motionManager попытается отправить данные в уже уничтоженный канал.

Как мы строим канал

Начинаем с определения контракта: метод, аргументы, возвращаемый тип, коды ошибок. Документируем в комментариях на обеих сторонах синхронно. Это критично для команд, где iOS-разработчик и Flutter-разработчик — разные люди.

На Dart-стороне оборачиваем MethodChannel в отдельный класс-сервис с типизированным API:

class BiometricService {
  static const _channel = MethodChannel('com.app/biometric');

  Future<bool> authenticate(String reason) async {
    try {
      return await _channel.invokeMethod<bool>('authenticate', {'reason': reason}) ?? false;
    } on PlatformException catch (e) {
      if (e.code == 'BIOMETRIC_UNAVAILABLE') return false;
      rethrow;
    }
  }
}

Прямое обращение к MethodChannel из виджета — антипаттерн: теряется типизация, обработка ошибок расползается по всему дереву.

Тестируем с MockMethodCallHandler в unit-тестах на Dart-стороне и через XCTest на Swift-стороне. Изолированная проверка каждой части ускоряет отладку в разы.

Что входит в работу

  • Проектирование контракта канала (имена методов, типы аргументов, коды ошибок)
  • Реализация Swift-обработчика с правильной потокобезопасностью
  • Dart-сервис с типизированным публичным API
  • Обработка edge cases: устройство не поддерживает функцию, пользователь отказал в разрешении
  • Unit-тесты для обеих сторон
  • Проверка на реальном устройстве (не только симулятор — многие iOS API недоступны в симуляторе)

Сроки

3–5 дней. Простой MethodChannel для одного системного вызова — 2–3 дня с тестами. EventChannel с непрерывным потоком данных и управлением жизненным циклом — 4–5 дней. Стоимость рассчитывается индивидуально после анализа требований.