bzengage_flutter_sdk 0.7.2
bzengage_flutter_sdk: ^0.7.2 copied to clipboard
Official BzEngage Flutter SDK. A thin Dart layer that forwards calls to the native BzEngage Android and iOS SDKs (in-app messages, zones, push, events, consent, commerce).
example/lib/main.dart
// BzEngage Flutter SDK example.
//
// Run with your own keys:
// flutter run --dart-define=BZ_BASE_URL=https://your-host/bzengage-api --dart-define=BZ_API_KEY=cpk_...
// (optional) --dart-define=BZ_USER=member-123
//
// Android push needs android/app/google-services.json (your Firebase project). This example also
// uses firebase_messaging to show how to forward BzEngage pushes when another package owns FCM
// (README §11.6). Apps without firebase_messaging need none of the Firebase code below.
import 'package:bzengage_flutter_sdk/bzengage_flutter_sdk.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
const String kBaseUrl = String.fromEnvironment('BZ_BASE_URL');
const String kApiKey = String.fromEnvironment('BZ_API_KEY');
/// Identifier used by the "Sign in" button (your member ID or email).
const String kUserId = String.fromEnvironment('BZ_USER', defaultValue: 'demo-user');
/// Deep link scheme of this app (registered in AndroidManifest.xml / Info.plist).
const String kAppScheme = 'bzexample';
/// Android only: when firebase_messaging owns FCM, BzEngage pushes are forwarded.
/// Set in main() once Firebase has started (it needs android/app/google-services.json).
bool _usesFirebaseMessaging = false;
/// Runs in a background isolate when a push arrives while the app is closed or in the background.
@pragma('vm:entry-point')
Future<void> firebaseBackgroundHandler(RemoteMessage message) async {
if (BzEngageSDK.isBzEngagePush(message.data)) {
await BzEngageSDK.handlePushMessage(message.data);
}
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
if (kBaseUrl.isEmpty || kApiKey.isEmpty) {
runApp(const MaterialApp(
home: Scaffold(
body: Center(
child: Padding(
padding: EdgeInsets.all(24),
child: Text('Run with --dart-define=BZ_BASE_URL=... --dart-define=BZ_API_KEY=cpk_... '
'(see example/README.md).'),
),
),
),
));
return;
}
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
try {
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(firebaseBackgroundHandler);
_usesFirebaseMessaging = true;
} catch (e) {
debugPrint('Firebase not configured - push forwarding is off ($e)');
}
}
final sdk = await BzEngageSDK.initialize(
baseUrl: kBaseUrl,
apiKey: kApiKey,
inAppAllowedSchemes: const [kAppScheme],
);
// Push — on every launch, right after initialize().
final openedFromPush = await sdk.initialPushDeeplink();
await sdk.initPush();
if (_usesFirebaseMessaging) {
FirebaseMessaging.onMessage.listen((message) {
if (BzEngageSDK.isBzEngagePush(message.data)) {
BzEngageSDK.handlePushMessage(message.data);
}
});
FirebaseMessaging.instance.onTokenRefresh.listen((_) => sdk.reRegisterPushToken());
}
runApp(ExampleApp(sdk: sdk, openedFromPush: openedFromPush));
}
/// Reports every screen change to BzEngage and shows the in-app messages for it.
class BzEngageNavigatorObserver extends NavigatorObserver {
BzEngageNavigatorObserver(this.sdk);
final BzEngageSDK sdk;
void _report(Route<dynamic>? route) {
final name = route?.settings.name;
if (name == null) return;
sdk.setScreen(name);
sdk.showMessages().catchError((Object _) => <int>[]);
}
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) => _report(route);
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) => _report(previousRoute);
@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) => _report(newRoute);
}
class ExampleApp extends StatefulWidget {
const ExampleApp({super.key, required this.sdk, this.openedFromPush});
final BzEngageSDK sdk;
final String? openedFromPush;
@override
State<ExampleApp> createState() => _ExampleAppState();
}
class _ExampleAppState extends State<ExampleApp> {
final _navigatorKey = GlobalKey<NavigatorState>();
final _log = ValueNotifier<List<String>>(const []);
void _add(String line) => _log.value = [line, ..._log.value].take(30).toList();
@override
void initState() {
super.initState();
final sdk = widget.sdk;
sdk.onInAppDeeplink.listen((url) => _add('in-app deeplink: $url'));
sdk.onInAppUrlOpen.listen((url) => _add('in-app url: $url'));
sdk.onInAppCustomAction.listen((value) => _add('in-app custom action: $value'));
sdk.onPushDeeplink.listen(_openPushLink);
final initial = widget.openedFromPush;
if (initial != null) {
WidgetsBinding.instance.addPostFrameCallback((_) => _openPushLink(initial));
}
}
void _openPushLink(String url) {
_add('push deeplink: $url');
if (Uri.tryParse(url)?.host == 'cart') _navigatorKey.currentState?.pushNamed('/cart');
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'BzEngage example',
navigatorKey: _navigatorKey,
navigatorObservers: [BzEngageNavigatorObserver(widget.sdk)],
initialRoute: '/home',
routes: {
'/home': (_) => HomeScreen(sdk: widget.sdk, log: _log, onLog: _add),
'/cart': (_) => CartScreen(sdk: widget.sdk),
},
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key, required this.sdk, required this.log, required this.onLog});
final BzEngageSDK sdk;
final ValueNotifier<List<String>> log;
final ValueChanged<String> onLog;
Future<void> _run(String label, Future<Object?> Function() action) async {
try {
final result = await action();
onLog('$label${result == null ? '' : ' → $result'}');
} on BzEngageError catch (e) {
onLog('$label failed: ${e.code}');
}
}
@override
Widget build(BuildContext context) {
final item = {'item_id': 'A1', 'item_name': 'T-shirt', 'price': 9900, 'quantity': 1};
return Scaffold(
appBar: AppBar(title: const Text('BzEngage example')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// Content of the zone "home_top" from the BzEngage editor.
const BzenZone(name: 'home_top', padding: EdgeInsets.only(bottom: 16)),
Wrap(spacing: 8, runSpacing: 8, children: [
FilledButton(
onPressed: () => _run('identify', () => sdk.identify(kUserId, properties: {'tier': 'gold'})),
child: const Text('Sign in'),
),
OutlinedButton(onPressed: () => _run('logout', sdk.logout), child: const Text('Sign out')),
OutlinedButton(
onPressed: () => _run('person id', sdk.currentPersonId),
child: const Text('Person ID'),
),
OutlinedButton(
onPressed: () => _run('event', () async {
await sdk.trackEvent('example_button', {'source': 'home'});
return sdk.showMessages();
}),
child: const Text('Send event'),
),
OutlinedButton(
onPressed: () => _run('add to cart', () => sdk.commerce.addToCart(items: [item], value: 9900)),
child: const Text('Add to cart'),
),
OutlinedButton(
onPressed: () => _run('cart', sdk.commerce.cartSnapshot),
child: const Text('Cart state'),
),
OutlinedButton(
onPressed: () => _run('push consent', () => sdk.setPushOptin(true)),
child: const Text('Allow marketing push'),
),
OutlinedButton(
onPressed: () => _run('notification permission', () async {
// Ask with the tool your app already uses; firebase_messaging is used here.
if (_usesFirebaseMessaging) await FirebaseMessaging.instance.requestPermission();
await sdk.reRegisterPushToken();
return null;
}),
child: const Text('Ask notification permission'),
),
OutlinedButton(
onPressed: () => Navigator.of(context).pushNamed('/cart'),
child: const Text('Open cart'),
),
]),
const SizedBox(height: 16),
ValueListenableBuilder<List<String>>(
valueListenable: log,
builder: (_, lines, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [for (final l in lines) Text(l, style: const TextStyle(fontSize: 12))],
),
),
],
),
);
}
}
class CartScreen extends StatelessWidget {
const CartScreen({super.key, required this.sdk});
final BzEngageSDK sdk;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Cart')),
body: Center(
child: FilledButton(
onPressed: () => sdk.commerce.purchase(
transactionId: 'T-${DateTime.now().millisecondsSinceEpoch}',
items: [
{'item_id': 'A1', 'item_name': 'T-shirt', 'price': 9900, 'quantity': 1},
],
value: 9900,
),
child: const Text('Purchase'),
),
),
);
}
}