Мир мобильной разработки постоянно меняется. В поиске эффективных решений разработчики пошли по пути новых технологий и интеграций. Одним из самых ярких маяков на этом пути стал Flutter — набор инструментов пользовательского интерфейса от Google, который позволяет пользователям создавать нативно скомпилированные приложения для веб, mobile и desktop с единой кодовой базой.

Преимущество Flutter — в его возможности создавать приложения «похожие» на нативные. Это реально благодаря большому выбору настраиваемых виджетов, которые позволяют быстро создавать «нативные» интерфейсы. Но даже с такими широкими возможностями бывают случаи, когда приложениям Flutter необходимы определенные функции нативных SDK.


Нативные SDK — это специальные инструменты для Android и iOS. SDK открывают доступ к функциям отдельных устройств и платформ. И они недоступны во Flutter. Например, это улучшенная обработка платежей, расширенные функции датчиков устройств или интеграция с ПО различных платформ. Всё это говорит не только о гибкости Flutter как инструмента, но и о практической пользе для разработчиков, которые хотят улучшить пользовательский опыт и производительность своих приложений.


Представьте, как круто создавать мобильные приложения с классным UI и UX и отличной производительностью, которые еще и без проблем взаимодействует с платформо-специфическими функциями. Всё это возможно благодаря интеграции Flutter с нативными SDK.

В этой статье мы подробно расскажем, как эта интеграция может улучшить ваше мобильное приложение. Рассмотрим пример, где мы усовершенствовали Flutter-приложение с помощью нативных SDK платежной системы Stripe. Бэкэнд там был на Go, но его мы вынесем за рамки обсуждения.

В первую очередь мы хотим показать, как сочетание универсальности Flutter с мощью нативных SDK открывает новые возможности для ваших мобильных приложений, делает их более надежными, гибкими и удобными для пользователей.

Платформенные интеграции во Flutter

Интеграция нативных SDK в Flutter-приложение — классный способ использовать функции и возможности, недоступные во Flutter. Для интеграций используют Platform Channels, которые позволяют Flutter общаться с нативной частью приложения, то есть отправлять и получать сообщения.

Разбираемся с Platform Channels

Источник изображения тут

Platform Channels — это мостики, с помощью которых Flutter взаимодействует с нативным кодом. Они позволяют запускать нативный код из вашего Flutter-приложения. Flutter поддерживает разные типы сообщений для Platform Channels, включая простые сообщения и вызовы методов. Стандартный декодер обеспечивает эффективную двоичную сериализацию простых значений типа JSON. Сюда относятся логические значения, числа, строки, массивы байт, а также списки и мапы.

Объявляем канал

Сперва нужно объявить Platform Channel в вашем Flutter-приложении. Выберите тип канала в зависимости от ваших потребностей:

  • MethodChannel: используется в большинстве случаев, когда вызов метода во Flutter запускает нативный метод. Поддерживает асинхронные вызовы методов.

  • EventChannel: для передачи потоков данных из нативного кода во Flutter.

  • BasicMessageChannel: для отправки простых сообщений между Flutter и нативным кодом.

MethodChannel чаще всего используют для интеграции нативных SDK. Рассмотрим его на примере.

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

class CartPage extends StatelessWidget {
  static const MethodChannel _channel = MethodChannel('co.wawand/stripe');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // Widget...
    );
  }
}

Выполняем нативный код

На нативной стороне (как для Android, так и для iOS) нам нужно реализовать метод, который вызывается из Flutter. Для этого нужно внести изменения в файлы нативного кода. Как вы помните, мы рассматриваем пример с нативным Stripe SDK. Нам нужно установить зависимость для каждой из нативных платформ с помощью их менеджера зависимостей и настроить логику проведения платежа.

Как это работает для Android

По опыту мы рекомендуем работать в Android Studio. Так будет проще выявить зависимости, синхронизировать их и добавить кастомный нативный код.

Stripe Android SDK. Добавим Stripe SDK для платформы Android, изменив файл build.gradle в каталоге android/app/, и синхронизируем зависимости gradle.

dependencies {
    // ...
    implementation 'com.stripe:stripe-android:20.37.4'
}

Android Activity. Нам нужно создать метод, который будет вызываться из Flutter в файле MainActivity. В зависимости от параметров вашего проекта, либо файл Java, либо Kotlin будет в каталоге android/app/src/main/java/<your_package_name>/.

package co.wawand.stripe_payment

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

class MainActivity: FlutterActivity() {
    private val CHANNEL = "co.wawand/stripe"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        
        // Setup stripe payment config
        PaymentConfiguration.init(applicationContext, "publishable stripe key")
        
        //Catching method channel call invoke
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
            call, result ->
            when (call.method) {
                "processPayment" -> {
                    val paymentDetails = call.arguments<Map<String, Any>>()
                    try {
                        val paymentResult = processPayment(paymentDetails)
                        result.success(paymentResult)
                    } catch (e: Exception) {
                        result.error("ERROR", "Payment processing failed: ${e.localizedMessage}", null)
                    }
                }
                else -> result.notImplemented()
            }
        }
    }

    private fun processPayment(paymentDetails: Map<String, Any>): String {
        // payment processing logic here.
        return "Payment processed for ${paymentDetails["amount"].toString()}"
    }
}

Некоторые особенности работы с Android

  • Часто в проектах Android класс Activity наследуется от базовых классов, таких как ComponentActivity, FragmentActivity или Activity. Во Flutter это решено с помощью специального класса FlutterActivity, который наследуется от базового класса Activity и позволяет добавлять кастомные методы для связи Flutter-приложения с нативным кодом.

  • Файл Groovy с именем flutter.groovy содержит конфигурации для сборок Android, с помощью которых можно управлять совместимостью исходного и конечного кода, задачами gradle, версиями SDK и так далее. Обычно эти конфигурации можно найти в файле gradle.

  • Во Flutter есть несколько файлов gradle: для работы с плагинами, для загрузки конфигураций Flutter, загрузки нативных зависимостей и так далее. На самом деле, не так просто понять, как внести изменения в нативные зависимости или в кастомный нативный код.

Как это работает для iOS

Для проектов на iOS лучшим выбором будет XCode, официальная среда разработки Apple. В этом примере мы будем редактировать код на Swift, изменять и создавать контроллеры представлений и работать с файлом Main.storyboard.

Stripe iOS SDK. Нам нужно импортировать Stripe SDK с помощью CocoaPods,  изменив файл Podfile.

target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
  target 'RunnerTests' do
    inherit! :search_paths
  end
  #Stripe Pod
  pod 'StripePaymentSheet'
end

Затем запустите команду pod install в папке ios, чтобы установить модуль Stripe.

iOS AppDelegate. Нам нужно настроить файл AppDelegate.swift внутри каталога ios/Runner/, чтобы задать метод для вызова из Flutter.

import UIKit
import Flutter
import StripePaymentSheet

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  private let channelName = "co.wawand/stripe"

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    guard let controller = window?.rootViewController as? FlutterViewController else {
      fatalError("rootViewController is not type FlutterViewController")
    }
      
    StripeAPI.defaultPublishableKey = "publishable stripe key"
      
    let methodChannel = FlutterMethodChannel(name: channelName,
                                             binaryMessenger: controller.binaryMessenger)
    methodChannel.setMethodCallHandler({
      (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
      // Check if method call is processPayment
      if call.method == "processPayment" {
        self.processPayment(call: call, result: result)
      } else {
        result(FlutterMethodNotImplemented)
      }
    })

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  private func processPayment(call: FlutterMethodCall, result: @escaping FlutterResult) {
    // Assume call.arguments is a dictionary with payment details
    guard let args = call.arguments as? [String: Any],
          let amount = args["amount"] as? String else {
      result(FlutterError(code: "INVALID_ARGUMENTS", message: "Invalid iOS arguments received for processing payment", details: nil))
      return
    }
    // your payment processing logic here
    result("Payment processed for \(amount)")
  }
}

Некоторые особенности работы с iOS

  • Класс AppDelegate, наследуемый от FlutterAppDelegate, — это решение, которое облегчает интеграцию движка Flutter на любом этапе жизненного цикла iOS-приложения. Эта настройка обеспечивает нативную плавность и удобство интерфейса для Flutter-приложений.

  • Некоторые SDK требуют дополнительных конфигураций или ключей для файла Info.plist. Сюда входят ключи API, параметры конфигурации или настройки SDK, необходимые для правильной его работы.

  • Параметры сборки Xcode и конфигурации проекта могут содержать множество вариантов настроек сборки вашего приложения. При интеграции нативных SDK вам может потребоваться настройка таких параметров, как deployment target, параметры архитектуры или флаги компиляции. Добавление нативных SDK часто предполагает связку с дополнительными платформами и библиотеками.

Вызов нативного кода из Flutter

Вернувшись в приложение Flutter, используйте MethodChannel для вызова нативного метода. Это делается с помощью указания имени канала (которое должно совпадать как во Flutter, так и на нативной стороне) и вызова метода с помощью функции invokeMethod. Крайне важно учитывать исключения и ошибки, которые могут возникнуть.

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

class CartPage extends StatelessWidget {
  static const MethodChannel _channel = MethodChannel('co.wawand/stripe');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Cart'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // Example paymentDetails.
            Map paymentDetails = {
              'amount': 1000, // Example amount
              'itemName': 'banana', // Example item
            };
            initialize(paymentDetails);
          },
          child: Text('Pay with Stripe'),
        ),
      ),
    );
  }

  Future<void> initialize(Map paymentDetails) async {
    try {
      await _channel.invokeMethod('processPayment', paymentDetails);
    } on PlatformException catch (e) {
      if (kDebugMode) {
        print(e.message);
      }
    }
  }
}

Технические аспекты

После завершения интеграции Stripe SDK с Flutter-приложением можно сделать несколько ключевых выводов, как она влияет на разработку и производительность приложения. Интеграция подчеркивает универсальность Flutter и демонстрирует потенциал для создания надежных, многофункциональных мобильных приложений.

Ниже поговорим подробнее об отладке, производительности и совместимости решений.

Отладка

Интеграция нативных SDK в Flutter-приложение усложняет отладку. Проблемы могут возникнуть как на стороне Dart, так и на нативной стороне Swift/Kotlin или каналах связи между ними. Использование platform channels для интеграции требует тщательного подхода к отладке, как в нативной части, так и во Flutter. Такие инструменты, как Flutter DevTools и встроенные отладчики (Xcode для iOS, Android Studio для Android) незаменимы при выявлении ошибок. 

Производительность

Производительность — важнейший аспект любого мобильного приложения. Интеграция нативного SDK, например Stripe SDK, во Flutter-приложение может повлиять на его скорость не в лучшую сторону. Однако, если всё сделать правильно, интеграция может обеспечить производительность наравне с нативными приложениями. Эффективное использование platform channels и оптимизация нативного кода сводят к минимуму потенциальные издержки и гарантируют, что интеграционные функции не ухудшат пользовательский опыт. И, конечно, после интеграции очень важно контролировать производительность приложения и отслеживать уязвимости.

Совместимость с Flutter-приложением

Успех интеграции SDK в Flutter-приложение во многом зависит от бесшовного взаимодействия между кодом Flutter и нативными модулями. Flutter здесь работает бок о бок с нативным кодом. Именно это позволяет разработчикам использовать весь спектр функций, который предлагают сторонние нативные SDK. Следуйте лучшим практикам работы с platform channels, — а это определение точного описания методов и обеспечение совместимости типов данных, — и тогда вы сможете достичь высокого уровня совместимости.

Кейсы из практики

Интеграция нативных SDK с Flutter расширяет функции мобильных приложений за пределы возможностей Dart. Выше мы рассматривали интеграцию Stripe SDK для настройки платежей. Но описанный подход можно использовать и для других задач. Приведем несколько таких примеров ниже, чтобы доказать универсальность и потенциал интеграции.

  1. Расширенный доступ к аппаратному обеспечению устройства.

    Нативные SDK предлагают более расширенный доступ к аппаратному обеспечению устройства по сравнению с тем, который доступен через плагины Flutter. Сюда входит управление камерой, данными датчиков (гироскоп, акселерометр) и связью Bluetooth для IoT-устройств.

  2. Интеграция карт и служб геолокации.

    Хотя у Flutter есть библиотеки для карт и геолокации, нативные SDK предлагают более детализированное управление картами, улучшенное отслеживание геолокации, настраиваемые маркеры и интерактивные функции.

Заключение

Интеграция нативных SDK с Flutter — это классный инструмент, который открывает широкий спектр возможностей для разработки мобильных приложений. Если использовать сильные стороны Flutter и нативных платформ, вы сможете создавать красивые мощные приложения, у которых есть доступ к основными возможностями и сервисами каждой платформы. Такой гибридный подход позволяет разрабатывать многофункциональные, оптимизированные под каждую платформу приложения, которые нравятся пользователям и выделяются среди конкурентов.

P. S. Пишите в комментариях, если вы уже пробовали интегрировать нативные SDK. Может быть, вы столкнулись с каким-то сложностями? Давайте обсудим.

А еще подписывайте 
на мой телеграм-канал. Там много полезного про Flutter .

Комментарии (1)


  1. Nashev
    14.03.2024 20:55

    Ужасно много маркетинговой воды. Вы это продаете менеджерам?