Конфигурационные файлы

Нативные файлы Android и iOS правятся прямо во FlutterFlow, не покидая редактора.

Нужно это обычно при подключении сторонних пакетов — аналитики, рекламных сетей, платёжных сервисов.

Какие файлы доступны:

  • AndroidManifest.xml — разрешения, метаданные и intent-фильтры Android.
  • build.gradle — сборка Android: версии SDK, зависимости, типы сборки, подпись.
  • ProGuard — сжатие и обфускация кода в релизной сборке Android.
  • Info.plist — настройки приложения iOS, включая разрешения.
  • Entitlements.plist — привилегии iOS: push-уведомления, Apple Pay и прочее.
  • AppDelegate.swift — запуск приложения iOS: регистрация плагинов Flutter, инициализация сервисов вроде Firebase, события жизненного цикла и диплинки.
  • main.dart — точка входа приложения Flutter.
внимание

Ошибка в нативном файле обходится дорого: пропущенный тег или неверный ключ ломают сборку или роняют приложение при запуске, а лишние разрешения приводят к отказу при публикации в магазине.

Правьте нативный код только когда это действительно нужно, записывайте, что именно поменяли, и проверяйте приложение после каждой правки.

Как править

Есть два способа: вставка фрагментов и ручной режим.

Способ 1: фрагменты

Фрагмент — небольшой кусок кода, который FlutterFlow сам вставляет в нужное место файла. Открывать весь файл не приходится, и это безопаснее: так добавляют разрешение или тег с метаданными.

Фрагменты для Android

В AndroidManifest.xml фрагмент вставляется в один из трёх участков:

  • Activity Tags — внутрь блока MainActivity. Сюда идут <intent-filter> и <meta-data>: диплинки, тема, режим запуска.
  • Application Tags — атрибуты самого тега <application>: android:icon, android:label, android:allowBackup.
  • App Component Tags — целые компоненты внутри <application>...</application>: дополнительные activity, службы, приёмники широковещательных сообщений, провайдеры содержимого.

Откройте Custom Code в меню слева, выберите Configuration Files и файл AndroidManifest.xml. Нажмите плюс (+) у нужного участка, задайте имя фрагмента — оно попадёт в файл комментарием — и вставьте код.

Фрагменты для iOS

У iOS нет вложенной структуры application/activity: Info.plist и Entitlements.plist — это словари пар «ключ-значение». Фрагмент попадает прямо в корневой элемент <dict>.

Порядок тот же: Custom Code > Configuration Files, нужный файл, кнопка плюс (+), имя фрагмента и код.

совет

Способ 2: ручной режим

Manual Edit Mode открывает файл целиком — как обычный текстовый редактор внутри FlutterFlow.

Откройте Custom Code > Configuration Files, выберите файл и нажмите значок замка.

внимание

После разблокировки файл остаётся в ручном режиме, пока вы не заблокируете его обратно. Повторная блокировка возвращает файл к версии, которую генерирует FlutterFlow, — все ручные правки при этом теряются.

совет
  • Не удаляйте то, что добавил FlutterFlow, если не уверены. Безопаснее только дописывать.
  • Ручной режим оправдан для крупных или сложных правок: переставить теги, что-то убрать, вставить большой блок настроек. После этого обязательно проверьте, что приложение собирается и запускается.
  • Здесь тоже доступны значения окружения и значения библиотеки.

Переменные в нативном коде

Ключи API и настройки, зависящие от окружения, не стоит прописывать в AndroidManifest.xml, Info.plist и прочих файлах прямо значением. Вместо этого берут значения окружения и значения библиотеки.

Схема такая: создаётся переменная уровня файла, ей назначается значение окружения или библиотеки, а в самом файле ставится подстановка вида {{apiToken}}. При генерации кода подстановки заменяются настоящими значениями.

совет
  • Подстановку {{variableName}} можно вписать сразу в код — переменную уровня файла FlutterFlow создаст сам.
  • Одна и та же переменная уровня файла доступна всем фрагментам этого файла.

Несколько примеров.

Пример 1: ключ API в AndroidManifest.xml

Пакету Mapbox нужен токен в манифесте. Вместо самого токена ставится подстановка:

<meta-data
    android:name="com.mapbox.token"
    android:value="{{MAPBOX_ACCESS_TOKEN}}"/>

Здесь {{MAPBOX_ACCESS_TOKEN}} — переменная уровня файла со значением окружения.

Пример 2: настройка Info.plist

Разрешение соединений без HTTPS через App Transport Security:

<key>NSAllowsArbitraryLoads</key>
<{{ALLOW_HTTP_TRAFFIC}}/>

Если ALLOW_HTTP_TRAFFIC в значениях окружения равно true, приложение разрешит запросы по HTTP.

Пример 3: значения библиотеки

Если вы собираете библиотеку FlutterFlow, ключи в нативном коде задаются через значения библиотеки — тогда каждый, кто её подключит, подставит свои.

<application>
    <meta-data
        android:name="com.google.android.geo.API_KEY"
        android:value="{{WEATHER_API_KEY}}" />
</application>

Пользователь библиотеки задаёт своё значение при подключении, а при сборке {{WEATHER_API_KEY}} заменяется на него.

Что можно править

AndroidManifest.xml (Android)

AndroidManifest.xml — главный файл настроек приложения Android. Он лежит в android/app/src/main и сообщает системе и Google Play всё существенное: имя пакета, компоненты (activity, службы, приёмники) и запрашиваемые разрешения.

Здесь же объявляются требования к оборудованию — Bluetooth, GPS, датчики, — настраиваются intent-фильтры (реакция на системные события и диплинки) и метаданные подключённых SDK.

Когда его нужно править:

Пример 1: объявление компонентов

Дополнительные экраны, фоновые службы и приёмники должны быть объявлены в манифесте:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.app">

    <application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name">

        <!-- Здесь добавляется своя activity -->
        <activity android:name=".NewScreenActivity" />

    </application>

</manifest>

Пример 2: запрос разрешений

Доступ к защищённым возможностям — удержанию экрана, записи звука — объявляется ручной правкой манифеста. Свои разрешения можно добавить и через настройки разрешений во FlutterFlow.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.yourappname">

    <!-- Разрешения -->
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>

    <application
        android:label=""
        tools:replace="android:label"
        android:icon="@mipmap/ic_launcher"
        android:requestLegacyExternalStorage="true">

        <activity android:name=".NewScreenActivity"/>

    </application>
</manifest>

Без них приложение не сможет ни держать экран включённым, ни записывать звук.

Пример 3: метаданные для SDK

Многие пакеты — Google Maps, Firebase, AdMob — требуют тег <meta-data> с настройками. Для инициализации Mapbox:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.app">

    <application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name">

        <!-- Ваш фрагмент -->
        <meta-data
            android:name="com.example.MAPS_API_KEY"
            android:value="YOUR_API_KEY" />

    </application>
</manifest>

Пример 4: требования к устройству

Требование камеры, GPS или сенсорного экрана не даст установить приложение туда, где их нет:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.app">

    <!-- Ваш фрагмент -->
    <uses-feature android:name="android.hardware.camera" />

    <application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name">

        <!-- остальные компоненты -->

    </application>

</manifest>

Пример 5: незашифрованные соединения

Если приложению нужен HTTP — для отладки или из-за старого сервера, — в тег <application> добавляется android:usesCleartextTraffic="true":

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.app">

    <application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:usesCleartextTraffic="true"> <!-- эта строка -->

        <!-- остальные компоненты -->

    </application>

</manifest>
совет

AndroidManifest.xml правится и фрагментом, и вручную.

build.gradle (Android)

build.gradle — сценарий сборки модуля Android. Лежит в android/app/ и определяет, как приложение компилируется и упаковывается:

  • версии SDK (compileSdkVersion, minSdkVersion, targetSdkVersion);
  • зависимости от сторонних библиотек;
  • типы сборки (отладочная и релизная);
  • настройки подписи релиза;
  • параметры Kotlin и Flutter;
  • MultiDex и правила ProGuard;
  • параметры упаковки.

Пример 1: версии SDK

android {
    compileSdkVersion 33

    defaultConfig {
        applicationId "com.example.myapp"
        minSdkVersion 21
        targetSdkVersion 33
        versionCode 1
        versionName "1.0"
    }
}

Нужно при переходе на новый уровень API или для совместимости с конкретной библиотекой.

Пример 2: сторонние библиотеки

dependencies {
    implementation 'com.google.android.gms:play-services-maps:18.1.0'
    implementation 'androidx.work:work-runtime:2.7.1'
}

Так подключаются Google Maps, Firebase Messaging, WorkManager.

Пример 3: правила ProGuard для релиза

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

Уменьшает размер APK и затрудняет разбор кода.

Пример 4: MultiDex

Если приложение упирается в предел в 64 тысячи методов — обычная история при большом числе зависимостей:

defaultConfig {
    ...
    multiDexEnabled true
}

Признак — ошибка сборки Too many methods.

совет

build.gradle правится и фрагментом, и вручную.

ProGuard (Android)

Файл proguard-rules.pro управляет сжатием и обфускацией кода: уменьшает размер APK или AAB и затрудняет обратную разработку. Лежит он в android/app/proguard-rules.pro и применяется при релизной сборке с включённым сжатием.

Правила задают, что нельзя удалять и переименовывать.

Пример 1: сторонние библиотеки

Обфускация ломает библиотеки, полагающиеся на имена классов:

# Firebase
-keep class com.google.firebase.** { *; }

# Gson (сериализация JSON)
-keep class com.google.gson.** { *; }
-keepattributes *Annotation*

Без этого Firebase и Gson падают на сериализации.

Пример 2: отладка проблем ProGuard

Если в релизе приложение падает, а в отладочной сборке работает, скорее всего ProGuard вырезал нужное:

-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
    public static *** i(...);
}

Правило убирает отладочные записи из релиза, оставляя их в отладочной сборке.

Пример 3: убрать отладочную информацию

-dontwarn android.util.Log

Пример 4: нативные библиотеки (JNI)

-keep class com.example.native.** { *; }
-keepclassmembers class * {
    native <methods>;
}

Сохраняет нативные методы нетронутыми.

Пример 5: код, использующий рефлексию

-keep class * implements android.os.Parcelable { *; }
-keepclassmembers class ** {
    @android.webkit.JavascriptInterface <methods>;
}

Библиотеки, вызывающие методы через рефлексию, иначе перестают работать.

Info.plist (iOS)

Info.plist — файл настроек приложения iOS в формате XML. Он обязателен для любого приложения и лежит в /ios/Runner/.

В нём указаны идентификатор пакета, отображаемое имя, версия и, что важнее всего, тексты запросов разрешений.

Пример 1: разрешения

Доступ к местоположению во время работы приложения и в фоне. Свои разрешения можно добавить и через настройки разрешений.

<key>NSLocationWhenInUseUsageDescription</key>
<string>This app requires location access while in use to provide location-based services.</string>

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app requires background location access to enable continuous location tracking.</string>

Пример 2: соединения по HTTP

iOS по умолчанию требует HTTPS. Для работы с сервером без него:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

Настройка снимает ограничение для всех запросов сразу — пользуйтесь ей осторожно.

Пример 3: фоновые режимы

Воспроизведение звука и отслеживание местоположения в фоне:

<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
    <string>location</string>
</array>

Пример 4: ключи сторонних пакетов

Для карт Mapbox нужен токен доступа:

<key>io.flutter.embedded_views_preview</key>
<true/>
<key>MGLMapboxAccessToken</key>
<string>YOUR_MAPBOX_ACCESS_TOKEN</string>

Ключ MGLMapboxAccessToken инициализирует карты, а io.flutter.embedded_views_preview со значением true разрешает встраивать нативные представления во Flutter.

совет

Info.plist правится и фрагментом, и вручную.

Entitlements.plist (iOS)

Этот файл перечисляет привилегии приложения — доступ к сервисам Apple: iCloud, push-уведомлениям, App Groups, фоновым режимам, связке ключей. Лежит он в /ios/Runner/ под именем Runner.entitlements.

Пример 1: iCloud

<key>com.apple.developer.icloud-container-identifiers</key>
<array>
    <string>iCloud.com.yourcompany.appname</string>
</array>

<key>com.apple.developer.icloud-services</key>
<array>
    <string>CloudDocuments</string>
</array>

Пример 2: связка ключей

<key>keychain-access-groups</key>
<array>
    <string>com.yourcompany.appname</string>
</array>

Позволяет безопасно хранить учётные данные, токены и ключи шифрования.

Пример 3: App Groups

Общее хранилище для приложения и его расширений — виджета, команды Siri:

<key>com.apple.security.application-groups</key>
<array>
    <string>group.com.yourcompany.shared</string>
</array>

Пример 4: Apple Wallet

<key>com.apple.developer.pass-type-identifiers</key>
<array>
    <string>pass.com.yourcompany.appname</string>
</array>
совет

Entitlements.plist правится и фрагментом, и вручную.

AppDelegate.swift (iOS)

AppDelegate.swift — точка входа приложения iOS: настройка окружения, события жизненного цикла (запуск, уход в фон, завершение), регистрация плагинов Flutter и инициализация SDK вроде Firebase или Branch.

Лежит в ios/Runner/AppDelegate.swift.

Пример: регистрация своего плагина

Плагины, которые не регистрируются автоматически, подключаются вручную:

override func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
  let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
  let myPlugin = CustomPlugin()
  myPlugin.register(with: controller)
  return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
совет

AppDelegate.swift правится и фрагментом, и вручную.

main.dart (Flutter)

main.dart — точка входа приложения: файл, который выполняется первым. Он инициализирует приложение, настраивает зависимости и задаёт корневой виджет. Лежит в каталоге lib/ и содержит функцию main().

Сюда добавляют код, который должен выполниться на старте: инициализацию сторонних SDK, глобальные настройки, разовые вызовы.

инфо

Фрагменты для main.dart недоступны — только ручной режим.

Пример 1: инициализация стороннего пакета

Пакеты аналитики и отслеживания ошибок — Sentry и подобные — требуют вызова инициализации до запуска приложения:

import 'dart:async';

import 'package:flutter/widgets.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

Future<void> main() async {
  runZonedGuarded(() async {
    await SentryFlutter.init(
      (options) {
        options.dsn = 'https://example@sentry.io/add-your-dsn-here';
      },
    );

    runApp(MyApp());
  }, (exception, stackTrace) async {
    await Sentry.captureException(exception, stackTrace: stackTrace);
  });
}

Пример 2: вид строки состояния

Цвет строки состояния и яркость значков задаются до runApp():


import 'package:flutter/services.dart';

void main() {
  SystemChrome.setSystemUIOverlayStyle(
    SystemUiOverlayStyle(
      statusBarColor: Colors.redAccent, // цвет строки состояния
      statusBarIconBrightness: Brightness.dark, // тёмные значки на Android
      statusBarBrightness: Brightness.light, // светлые значки на iOS
    ),
  );

  runApp(MyApp());
}

Пример 3: фиксация ориентации экрана

import 'package:flutter/services.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await SystemChrome.setPreferredOrientations([
    DeviceOrientation.landscapeLeft,
    DeviceOrientation.landscapeRight,
  ]);

  runApp(MyApp());
}

Приложение будет работать только в альбомной ориентации.

Пример 4: отслеживание жизненного цикла

Реакция на уход приложения в фон и возврат:

import 'package:flutter/widgets.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  WidgetsBinding.instance.addObserver(AppLifecycleObserver());
  runApp(MyApp());
}

class AppLifecycleObserver with WidgetsBindingObserver {
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      print('App is in foreground');
    } else if (state == AppLifecycleState.paused) {
      print('App is in background');
    }
  }
}

Что стоит учесть

  • Сохраняйте копию. Перед правкой нативного файла сохраните исходный текст или сделайте коммит — чтобы было куда вернуться.
  • По одной правке за раз. Внесли изменение — проверили приложение. Иначе при поломке непонятно, что именно её вызвало.
  • Читайте документацию пакета. Обычно там приведён готовый фрагмент — его и вставляйте. При сомнениях сверяйтесь с документацией Android или Apple: например, точное значение для фоновой загрузки (fetch в массиве UIBackgroundModes) указано у Apple.
  • Ничего лишнего. Разрешения и привилегии «на всякий случай» усложняют приложение и вызывают вопросы при проверке в магазине.
  • Комментируйте. Через полгода комментарий вида <!-- Added for Payments SDK on Feb 2025 --> объяснит, откуда взялась строка.
  • Проверяйте на устройствах. Часть проблем с Info.plist и привилегиями на симуляторе не видна. Правила ProGuard проявляются только в релизной сборке.
  • Смотрите логи. После правок следите за консолью Xcode или logcat — предупреждения о неверной настройке обычно появляются там.
  • Не считайте эти файлы секретными. Всё, что лежит в Info.plist и AndroidManifest.xml, доступно любому, кто распакует приложение. Для ключей, которые приходится включать, используйте приватные значения окружения и следите за их использованием.

Частые вопросы

Приложение не устанавливается на устройство iOS

Сверьте привилегии в `Entitlements.plist` с профилем подготовки. Ошибка «Missing entitlement» означает, что вы добавили привилегию, которой профиль не разрешает: уберите её или обновите профиль в кабинете разработчика Apple.

Ошибка «Manifest merger failed» на Android

Это конфликт в `AndroidManifest.xml` — чаще всего дублирующиеся разрешения или атрибуты, например два ``. Конфликтующая строка указана в самом сообщении об ошибке.

После правки `main.dart` не запускается Test Mode с включённым Supabase

Это известное ограничение. В обход используйте [**Local Run**](../../testing-deployment-publishing/running-your-app/local-run.md).

Можно ли править конфигурационные файлы в проекте-библиотеке?

Да. При подключении библиотеки её фрагменты для `AndroidManifest.xml`, `Info.plist` и `Entitlements.plist` вливаются в файлы проекта автоматически.

Более того, библиотека может передавать в эти фрагменты значения — те же ключи API — через Library Values.

config-values-in-library

Благодаря этому подключение PostHog, Sentry, CleverTap, flutter_local_notifications, flutter_nfc_kit и прочего сводится к установке библиотеки из маркетплейса.

перевод официальной документации FlutterFlow

обновлено

ESC