Flutter SDK: обзор и быстрый старт
Gravity Field SDK для Flutter передаёт просмотры экранов и события, получает персонализированный контент и показывает кампании в приложении. Решения о таргетинге и выборе A/B-варианта принимает сервер. Приложение обрабатывает навигацию и может полностью управлять отрисовкой.
Руководство описывает gravity_sdk 0.24.1. Требования, указанные в пакете: Flutter ≥3.38.1 и Dart ^3.10.0. Примеры проверены анализатором и сборкой Flutter bundle на Flutter 3.38.1 / Dart 3.10.0 и Flutter 3.44.0 / Dart 3.12.0. Пакет на pub.dev · API Reference.
Установка
Добавьте зависимости в pubspec.yaml:
dependencies:
flutter:
sdk: flutter
gravity_sdk: 0.24.1
url_launcher: ^6.3.1
webview_flutter: ^4.11.1
Выполните flutter pub get. url_launcher и webview_flutter нужны приложению из примера для открытия URL; SDK сам навигацию не выполняет.
На Android для запросов требуется разрешение android.permission.INTERNET в android/app/src/main/AndroidManifest.xml, в том числе для release-сборки:
<uses-permission android:name="android.permission.INTERNET" />
SDK читает статус App Tracking Transparency на iOS, но сам не запрашивает разрешение. Если ваше приложение запрашивает ATT, добавьте NSUserTrackingUsageDescription в ios/Runner/Info.plist и описание использования данных. Отказ в ATT не отменяет обычную интеграцию SDK. Статус push передаётся отдельно.
Подготовка кампании
- Получите
apiKeyиsectionдля вашего проекта. - Для inline-рекомендаций создайте и опубликуйте мобильную кампанию с selector
product_recommendations. - Настройте контекст товара:
PRODUCT, SKUsku-123и locationapp://product/sku-123. Для другого товара замените SKU и в кампании, и в примере. - Для проверки автопоказа создайте in-app кампанию с активацией по просмотру этого экрана либо по событию
add-to-cart-v1. - Для проверки навигации используйте HTTPS-ссылку или диплинк
app://product/sku-123.
SKU, категории и lng должны совпадать с фидом, включая регистр. Настройка в кабинете описана в разделе Кампании в приложении.
Минимальное приложение
Скопируйте quick_start.dart в lib/main.dart, замените ключ и секцию, затем выполните flutter run на iOS или Android.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:gravity_sdk/gravity_sdk.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';
final navigatorKey = GlobalKey<NavigatorState>();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await GravitySDK.instance.initialize(
apiKey: 'YOUR_API_KEY',
section: 'YOUR_SECTION_ID',
gravityEventCallback: (event) {
if (event is FollowUrlEvent) {
unawaited(openCampaignUrl(event));
} else if (event is FollowDeeplinkEvent) {
// В этом примере поддерживается маршрут app://product/<sku>.
final uri = Uri.tryParse(event.deeplink);
final navigator = navigatorKey.currentState;
if (uri != null &&
uri.scheme == 'app' &&
uri.host == 'product' &&
uri.pathSegments.isNotEmpty &&
navigator != null) {
unawaited(
navigator.push<void>(
MaterialPageRoute(
builder: (_) => ProductPage(productId: uri.pathSegments.first),
),
),
);
}
}
},
);
runApp(const DemoApp());
}
Future<void> openCampaignUrl(FollowUrlEvent event) async {
final uri = Uri.tryParse(event.url);
if (uri == null || !['https', 'http'].contains(uri.scheme)) return;
try {
if (event.type == FollowUrlType.webview) {
final navigator = navigatorKey.currentState;
if (navigator == null) return;
await navigator.push<void>(
MaterialPageRoute(builder: (_) => CampaignWebView(url: uri)),
);
} else {
final opened = await launchUrl(uri, mode: LaunchMode.externalApplication);
if (!opened) debugPrint('Не удалось открыть ссылку кампании');
}
} catch (error) {
debugPrint('Ошибка открытия ссылки: $error');
}
}
class DemoApp extends StatelessWidget {
const DemoApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
navigatorKey: navigatorKey,
home: const ProductPage(productId: 'sku-123'),
);
}
class ProductPage extends StatefulWidget {
const ProductPage({super.key, required this.productId});
final String productId;
@override
State<ProductPage> createState() => _ProductPageState();
}
class _ProductPageState extends State<ProductPage> {
PageContext get pageContext => PageContext(
type: ContextType.product,
data: [widget.productId],
location: 'app://product/${widget.productId}',
);
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
unawaited(
GravitySDK.instance.trackView(
context: context,
pageContext: pageContext,
),
);
});
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('Товар')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
Text(widget.productId),
const Text('Цена: 1990 ₽'),
FilledButton(
onPressed: () async {
// В реальном приложении событие отправляют после
// успешного изменения корзины.
await GravitySDK.instance.triggerEvent(
context: context,
events: [
AddToCartEvent(
value: 1990,
productId: widget.productId,
quantity: 1,
currency: 'RUB',
),
],
pageContext: pageContext,
);
},
child: const Text('Отправить AddToCartEvent'),
),
GravityInlineWidget(
key: ValueKey(widget.productId),
selector: 'product_recommendations',
height: 280,
pageContext: pageContext,
),
],
),
);
}
class CampaignWebView extends StatefulWidget {
const CampaignWebView({super.key, required this.url});
final Uri url;
@override
State<CampaignWebView> createState() => _CampaignWebViewState();
}
class _CampaignWebViewState extends State<CampaignWebView> {
late final WebViewController controller;
@override
void initState() {
super.initState();
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.disabled)
..loadRequest(widget.url);
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('Кампания')),
body: WebViewWidget(controller: controller),
);
}
Пример отправляет просмотр после первого кадра, показывает inline-блок и позволяет отправить событие добавления товара в корзину. Кнопка демонстрирует трекинг; в вашем приложении AddToCartEvent должен отправляться после успешного изменения корзины.
Встроенный WebView в примере не включает JavaScript. Если он нужен странице кампании, настройте WebView по требованиям вашего приложения. Для собственных диплинков расширьте обработчик маршрутов. SDK уже отправляет tracking для действий встроенного контента: не дублируйте его в callback.
Конфигурация и контекст
initialize() вызывают перед использованием SDK. Настройки запросов, логирование и callback разобраны в Конфигурации, параметры экранов и события — в Контексте и событиях.
Идентификация пользователя
После авторизации передайте LoginEvent с согласованным CUID. Рекомендуемый cuidType — phone_hash. Различия между CUID, серверным UID и setUser() описаны в Идентификации.
resetUser()
await GravitySDK.instance.resetUser() сбрасывает пользователя и сессию, но не очищает офлайн-очередь. Выход и смена аккаунта.
GravityInlineWidget: автоматический рендеринг
Для размещения кампании в вёрстке используйте GravityInlineWidget с selector и PageContext. Параметры и ограничения.
GravityAnchor и tooltip-кампании
GravityAnchor регистрирует якорь; onReady сообщает, что элемент готов к показу подсказки. Пример tooltip.
Правила рекомендаций (rules)
rules передаются в запросы контента и GravityInlineWidget. Названия полей и операций согласуйте с конфигурацией проекта. Пример правила.