appdna_sdk 1.0.16
appdna_sdk: ^1.0.16 copied to clipboard
AppDNA SDK for Flutter — analytics, experiments, paywalls, surveys, and more.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:appdna_sdk/appdna_sdk.dart';
void main() {
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AppDNA SDK Example',
theme: ThemeData(colorSchemeSeed: const Color(0xFF6366f1), useMaterial3: true),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// Surface targets injected at build time via --dart-define (no customer IDs
// committed). Fall back to placeholders for source readers.
static const _onboardingId =
String.fromEnvironment('APPDNA_ONBOARDING_ID', defaultValue: 'default');
static const _paywallId =
String.fromEnvironment('APPDNA_PAYWALL_ID', defaultValue: 'default');
static const _paywallId2 =
String.fromEnvironment('APPDNA_PAYWALL_ID_2', defaultValue: 'default');
static const _surveyId =
String.fromEnvironment('APPDNA_SURVEY_ID', defaultValue: 'default');
static const _messageEvent =
String.fromEnvironment('APPDNA_MESSAGE_EVENT', defaultValue: 'session_start');
String _status = 'Not configured';
String? _webEntitlement;
String? _deepLink;
@override
void initState() {
super.initState();
_initSdk();
}
Future<void> _initSdk() async {
// 1. Configure SDK. The API key is injected at build time via
// --dart-define=APPDNA_API_KEY=... (D12: no key committed to the example);
// falls back to a placeholder for source readers.
const apiKey =
String.fromEnvironment('APPDNA_API_KEY', defaultValue: 'YOUR_API_KEY');
await AppDNA.configure(apiKey: apiKey);
setState(() => _status = 'Configured');
// 2. Identify user. The user id can be overridden at build time via
// --dart-define=APPDNA_USER_ID=... (used to exercise per-user-frequency
// surfaces like in-app messages with a fresh identity).
const userId =
String.fromEnvironment('APPDNA_USER_ID', defaultValue: 'user_123');
await AppDNA.identify(userId, traits: {'email': 'demo@example.com'});
setState(() => _status = 'Identified');
// 3. Check for deferred deep link (first launch)
final link = await AppDNA.checkDeferredDeepLink();
if (link != null) {
setState(() => _deepLink = '${link.screen} (${link.params})');
}
// 4. Listen for web entitlement changes
AppDNA.onWebEntitlementChanged.listen((entitlement) {
setState(() {
_webEntitlement = entitlement != null
? '${entitlement.planName} (${entitlement.status})'
: 'None';
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('AppDNA SDK Example')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_infoCard('SDK Status', _status),
_infoCard('Web Entitlement', _webEntitlement ?? 'Not loaded'),
_infoCard('Deferred Deep Link', _deepLink ?? 'None'),
const SizedBox(height: 24),
// Track Event
FilledButton.icon(
onPressed: () => AppDNA.track('button_tapped', properties: {'button': 'demo'}),
icon: const Icon(Icons.analytics),
label: const Text('Track Event'),
),
const SizedBox(height: 12),
// Present Paywall
FilledButton.icon(
onPressed: () => AppDNA.presentPaywall(_paywallId),
icon: const Icon(Icons.shopping_cart),
label: const Text('Present Paywall'),
),
const SizedBox(height: 12),
// Present a second paywall (e.g. a winback variant).
FilledButton.icon(
onPressed: () => AppDNA.presentPaywall(_paywallId2),
icon: const Icon(Icons.card_giftcard),
label: const Text('Present Paywall 2'),
),
const SizedBox(height: 12),
// Present Onboarding
FilledButton.icon(
onPressed: () => AppDNA.presentOnboarding(_onboardingId),
icon: const Icon(Icons.rocket_launch),
label: const Text('Present Onboarding'),
),
const SizedBox(height: 12),
// Present Survey
FilledButton.icon(
onPressed: () => AppDNA.showSurvey(_surveyId),
icon: const Icon(Icons.assignment),
label: const Text('Present Survey'),
),
const SizedBox(height: 12),
// Trigger In-App Message (fires the native trigger evaluation)
FilledButton.icon(
onPressed: () => AppDNA.track(_messageEvent),
icon: const Icon(Icons.campaign),
label: const Text('Trigger In-App Message'),
),
const SizedBox(height: 12),
// Remote Config
FilledButton.icon(
onPressed: () async {
final value = await AppDNA.getRemoteConfig('welcome_message');
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Remote config: $value')),
);
}
},
icon: const Icon(Icons.settings_remote),
label: const Text('Get Remote Config'),
),
const SizedBox(height: 12),
// Experiment Variant
FilledButton.icon(
onPressed: () async {
final variant = await AppDNA.getExperimentVariant('onboarding_test');
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Variant: $variant')),
);
}
},
icon: const Icon(Icons.science),
label: const Text('Get Experiment Variant'),
),
const SizedBox(height: 12),
// Feature Flag
FilledButton.icon(
onPressed: () async {
final enabled = await AppDNA.isFeatureEnabled('dark_mode');
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Feature enabled: $enabled')),
);
}
},
icon: const Icon(Icons.flag),
label: const Text('Check Feature Flag'),
),
const SizedBox(height: 12),
// Run Diagnose — returns the SDK health report String on BOTH platforms.
FilledButton.icon(
onPressed: () async {
final report = await AppDNA.diagnose();
if (context.mounted) {
showDialog<void>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('SDK Diagnose'),
content: SingleChildScrollView(
child: Text(report ?? '(null)',
style: const TextStyle(fontFamily: 'monospace', fontSize: 11)),
),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('Close'),
),
],
),
);
}
},
icon: const Icon(Icons.health_and_safety),
label: const Text('Run Diagnose'),
),
],
),
);
}
Widget _infoCard(String title, String value) {
return Card(
child: ListTile(
title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(value),
),
);
}
}