layers_flutter 1.0.0
layers_flutter: ^1.0.0 copied to clipboard
Layers SDK for Flutter. Analytics with ATT, SKAN, and deep link integrations.
Layers Flutter SDK #
Flutter SDK for Layers analytics, built on a Rust core via dart:ffi.
Installation #
dependencies:
layers_flutter: ^0.1.0
Usage #
import 'package:layers_flutter/layers_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Layers.init(LayersConfig(
apiKey: 'your-api-key',
appId: 'your-app-id',
));
runApp(MyApp());
}
Track Events #
Layers.track('button_tapped', properties: {'screen': 'home'});
Screen Views #
Layers.screen('HomeScreen');
Automatic Screen Tracking #
Add LayersNavigatorObserver to your MaterialApp to track screen views automatically when routes change:
MaterialApp(
navigatorObservers: [LayersNavigatorObserver()],
// ...
)
Identify Users #
Layers.identify('user-123');
Set User Properties #
Layers.setUserProperties({'plan': 'premium', 'signup_date': '2025-01-15'});
Consent Management #
Control what data the SDK collects. Set analytics and/or advertising to true or false. Null values mean "not determined".
Layers.setConsent(ConsentSettings(analytics: true, advertising: false));
ATT Integration #
Request App Tracking Transparency authorization on iOS. On Android, all ATT methods return safe defaults.
// Check if ATT is available (iOS 14.5+)
final available = await ATT.isAvailable();
if (available) {
// Check current status before prompting
final status = await ATT.getStatus();
if (status == ATTStatus.notDetermined) {
// Show the system ATT dialog
final result = await ATT.requestAuthorization();
if (result == ATTStatus.authorized) {
// User granted tracking permission
final idfa = await ATT.getAdvertisingId();
}
}
}
SKAN Integration #
Update SKAdNetwork conversion values on iOS. All methods are no-ops on Android.
// SKAN 2.0+: update fine conversion value (0-63)
await SKAN.updateConversionValue(42);
// SKAN 4.0: update with coarse value and optional window lock
await SKAN.updatePostbackConversionValue(
fineValue: 42,
coarseValue: SKANCoarseValue.high,
lockWindow: true,
);
Deep Links #
iOS Setup #
Add your URL scheme to Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
For Universal Links, add the Associated Domains entitlement:
applinks:yourdomain.com
Android Setup #
Add intent filters to AndroidManifest.xml:
<activity ...>
<!-- Deep link URL scheme -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>
<!-- App Links (verified) -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="yourdomain.com" />
</intent-filter>
</activity>
Dart Code #
// Get the deep link that launched the app (cold start)
final initialLink = await DeepLinks.getInitialLink();
if (initialLink != null) {
handleDeepLink(initialLink);
}
// Listen for deep links while the app is running (warm start)
final unsubscribe = DeepLinks.addListener((data) {
// data.url, data.scheme, data.host, data.path, data.queryParams
handleDeepLink(data);
});
// Stop listening when done
unsubscribe();
Commerce Tracking #
Commerce.trackPurchase(
productId: 'pro_monthly',
revenue: 9.99,
currency: 'USD',
transactionId: 'txn_abc123',
store: 'app_store',
quantity: 1,
);
RevenueCat Integration #
Connect your existing RevenueCat Purchases instance to automatically track subscription events and sync user properties. No hard dependency on purchases_flutter -- uses duck typing.
import 'package:purchases_flutter/purchases_flutter.dart';
// Connect after RevenueCat is configured
RevenueCat.connect(Purchases.instance);
// Optionally track individual package purchases
final offerings = await Purchases.instance.getOfferings();
final package = offerings.current?.availablePackages.first;
if (package != null) {
RevenueCat.trackPurchase(package);
}
Superwall Integration #
Track paywall events from your existing Superwall setup. No hard dependency on superwall_flutter -- uses duck typing.
// Track paywall presentation
Superwall.instance.setPaywallPresentationHandler(
PaywallPresentationHandler()
..onPresent((info) {
LayersSuperwall.trackPresentation(info);
})
..onDismiss((info) {
LayersSuperwall.trackDismiss(info);
})
..onSkip((reason) {
LayersSuperwall.trackSkip(null, reason.toString());
}),
);
Error Handling #
Register an onError callback to receive SDK errors. Errors are always logged via dart:developer regardless of this callback.
Layers.onError = (error) {
// Forward to your crash reporter, logging service, etc.
print(error);
};
await Layers.init(LayersConfig(
apiKey: 'your-api-key',
appId: 'your-app-id',
));
The SDK also provides typed exceptions:
import 'package:layers_flutter/layers_flutter.dart';
// LayersException — base type with message and optional code
// LayersNotInitializedException — thrown when calling methods before init
// LayersQueueFullException — thrown when the event queue is at capacity
Debug Mode #
Enable debug logging to see SDK activity in the console and dart:developer logs:
await Layers.init(LayersConfig(
apiKey: 'your-api-key',
appId: 'your-app-id',
enableDebug: true,
));
When enabled, the SDK logs track, screen, and identify calls with their arguments.
Print the full SDK state at any time:
Layers.debugPrintState();
// [Layers] === SDK Status ===
// [Layers] Initialized: true
// [Layers] Session ID: abc-123
// [Layers] Queue Depth: 5
// [Layers] User ID: user-42
// [Layers] Consent: analytics=true, advertising=unset
// [Layers] Debug Mode: true
// [Layers] Environment: production
// [Layers] App ID: your-app-id
// [Layers] Base URL: (default)
// [Layers] === End Status ===
Testing #
Use enableTestMode to inject a mock bindings implementation so tests run without the native Rust library:
import 'package:layers_flutter/layers_flutter.dart';
void main() {
late MockLayersBindings mock;
setUp(() {
mock = MockLayersBindings();
Layers.enableTestMode(mock);
});
tearDown(() {
Layers.resetForTesting();
});
test('tracks events', () {
Layers.track('button_tap', properties: {'screen': 'home'});
expect(mock.trackedEvents, hasLength(1));
expect(mock.trackedEvents.first['event'], 'button_tap');
});
}