klaviyo_flutter_sdk 0.1.0-alpha.1
klaviyo_flutter_sdk: ^0.1.0-alpha.1 copied to clipboard
A Flutter plugin that provides a wrapper around the native Klaviyo SDKs for iOS and Android.
⚠️ STILL WORK IN PROGRESS AND NOT READY FOR PRODUCTION APPs YET ⚠️
Klaviyo Flutter SDK #
A Flutter plugin that provides a wrapper around the native Klaviyo SDKs for iOS and Android. This SDK allows you to integrate Klaviyo's powerful marketing automation features into your Flutter applications.
Features #
- Profile Management: Set user profiles, emails, phone numbers, and custom properties
- Event Tracking: Track custom events and user interactions
- Push Notifications: Register for and handle push notifications (Automatic Token handling)
- In-App Forms: Display and manage in-app forms for lead capture
- Geofencing: Observe geofences for location-based event tracking
Installation #
1. Add the dependency #
Add klaviyo_flutter_sdk to your pubspec.yaml:
dependencies:
klaviyo_flutter_sdk: ^0.1.0-alpha.1
2. Platform Setup #
iOS Setup
1. Install Pods
The required native iOS dependencies are automatically included via the plugin's podspec. Run pod install in the ios directory to install them:
cd ios && pod install
2. Enable Capabilities (Required)
Open your project in Xcode (ios/Runner.xcworkspace), select the Runner target, go to the Signing & Capabilities tab, and add the following capabilities:
- Push Notifications (Required for APNs)
- Background Modes -> Check Remote notifications (Required for silent push updates)
3. AppDelegate Setup (Required)
To ensure push notifications display correctly while the app is in the foreground and to track "Open" events reliably, you need to implement the notification delegate methods in your ios/Runner/AppDelegate.swift.
Please ensure you make the following changes to your existing AppDelegate:
- Import the plugin module:
import klaviyo_flutter_sdk - Set the Notification Delegate: Assign
UNUserNotificationCenter.current().delegate = selfindidFinishLaunching. - Forward Notification Events: Implement (or update) the
userNotificationCentermethods to forward events to the Klaviyo SDK.
Note on Push Token Handling: The plugin automatically intercepts didRegisterForRemoteNotificationsWithDeviceToken to capture the APNs token. If you need to override this method in your AppDelegate (e.g., to also send the token to another push service), make sure to call super:
override func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
// Call super to ensure the Klaviyo plugin receives the token
super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
// Your custom token handling here
}
Here is an example of what the integration looks like:
import UIKit
import Flutter
import klaviyo_flutter_sdk // <--- Add this import
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// <--- Add this line to handle foreground notifications and taps
UNUserNotificationCenter.current().delegate = self
// ... Your existing setup code ...
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// <--- Add or update this method to handle Foreground Notifications
override func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
// Show banner, sound, and badge even when the app is open
completionHandler([.banner, .sound, .badge])
// ... Your custom logic (if any) ...
}
// <--- Add or update this method to forward Tap Events
override func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
// Forward the "Open" event to the Klaviyo SDK
KlaviyoFlutterSdkPlugin.shared.handleNotificationResponse(response)
// ... Your custom logic (if any) ...
// Complete the system callback
completionHandler()
}
// <--- Add this method to handle Silent Push Notifications (Optional)
override func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
// Forward silent push to Klaviyo plugin
KlaviyoFlutterSdkPlugin.shared.handleSilentPush(userInfo: userInfo)
// ... Your custom logic (if any) ...
// You MUST call the completion handler within ~30 seconds.
// Failing to do so will cause iOS to throttle or stop delivering
// silent push notifications to your app.
completionHandler(.newData)
}
}
Android Setup
Requirements:
minSdkVersion23+compileSdkVersion34+- Kotlin 1.8.0+
The Android SDK is automatically included as a dependency by the Flutter plugin. Required permissions are also automatically added via manifest merging.
MainActivity Setup (Required for Push Notification Tracking)
To track when users open push notifications, you need to handle intents in your MainActivity. See the example MainActivity.kt for a complete implementation.
Add the following to your android/app/src/main/kotlin/.../MainActivity.kt:
import android.content.Intent
import android.os.Bundle
import com.klaviyo.analytics.Klaviyo
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Handle notification intent on cold start (app not running)
intent?.let { Klaviyo.handlePush(it) }
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// Handle notification intent on warm start (app already running)
Klaviyo.handlePush(intent)
}
}
Why both methods?
onCreate: Handles notifications when app is launched from scratch (cold start)onNewIntent: Handles notifications when app is already running (warm start)
KlaviyoPushService Declaration (Required for Push Notification Tracking)
To ensure Klaviyo can properly track and handle push notifications, you must declare the KlaviyoPushService in your android/app/src/main/AndroidManifest.xml. This service intercepts FCM messages before Flutter's default FirebaseMessagingService processes them, allowing Klaviyo to track opens, handle rich push, and process notification data.
Add this service declaration inside the <application> tag:
<service
android:name="com.klaviyo.pushFcm.KlaviyoPushService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
Why is this required?
- Without this declaration, Flutter's
firebase_messagingplugin will intercept all FCM messages first - Klaviyo won't be able to track notification opens or handle rich push features
- The service declaration ensures Klaviyo processes the notification before Flutter
See the example AndroidManifest.xml for a complete implementation.
Enabling Geofencing (Optional) #
The Klaviyo Flutter SDK supports geofencing, but the full location module with Play Services is not included by default. The SDK includes lightweight location interfaces (location-core on Android, compile-time checks on iOS) that allow you to call geofencing methods, but they will return errors unless you explicitly enable the full location module.
Android
Add to android/gradle.properties:
klaviyoIncludeLocation=true
This:
- Includes the full
locationmodule with Google Play Services implementation - Adds these permissions to your merged manifest:
android.permission.ACCESS_FINE_LOCATIONandroid.permission.ACCESS_COARSE_LOCATIONandroid.permission.ACCESS_BACKGROUND_LOCATION
iOS
Add to ios/Podfile before flutter_install_all_ios_pods:
ENV['KLAVIYO_INCLUDE_LOCATION'] = 'true'
This includes the KlaviyoLocation pod with full geofencing support. You'll also need location permission descriptions in Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>We use your location for geofencing features.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We need background location for geofence monitoring.</string>
Error Behavior
Without the full location module (default):
- Geofencing methods (
registerGeofencing(),unregisterGeofencing(),getCurrentGeofences()) will return:- Error code:
GEOFENCING_NOT_AVAILABLE - Message: "Geofencing requires the full location module. Add 'klaviyoIncludeLocation=true' to gradle.properties" (Android) or "...to your podfile" (iOS)
- Error code:
With the full location module enabled:
- Full geofencing functionality available
Disabling In-App Forms (Optional) #
In-app forms are enabled by default. To opt out:
Android
Add to android/gradle.properties:
klaviyoIncludeForms=false
iOS
Add to ios/Podfile before flutter_install_all_ios_pods:
ENV['KLAVIYO_INCLUDE_FORMS'] = 'false'
Error Behavior
Without the forms module:
- Forms methods (
registerForInAppForms(),unregisterFromInAppForms()) will log an error and no-op gracefully - Error code:
FORMS_NOT_AVAILABLE - Smaller SDK footprint
Usage #
1. Initialize the SDK #
import 'package:klaviyo_flutter_sdk/klaviyo_flutter_sdk.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await KlaviyoSDK().initialize(
apiKey: 'YOUR_KLAVIYO_PUBLIC_API_KEY',
);
runApp(MyApp());
}
2. Profile Management #
// Set a complete profile
final profile = KlaviyoProfile(
email: 'user@test.com',
firstName: 'John',
lastName: 'Doe',
phoneNumber: '+2125557890',
properties: {
'plan': 'premium',
'signup_date': DateTime.now().toIso8601String(),
},
);
await KlaviyoSDK().setProfile(profile);
// Set individual properties
await KlaviyoSDK().setEmail('user@example.com');
await KlaviyoSDK().setPhoneNumber('+1234567890');
await KlaviyoSDK().setExternalId('user123');
await KlaviyoSDK().setProfileProperties({
'preferences': {'notifications': true},
});
3. Event Tracking #
// Track a simple event using a predefined metric
final openedAppEvent = KlaviyoEvent(
name: EventMetric.openedApp,
properties: {
'source': 'home_screen',
},
);
await KlaviyoSDK().createEvent(openedAppEvent);
// Track a custom event
final customEvent = KlaviyoEvent.custom(
metric: 'User Completed Tutorial',
properties: {
'tutorial_id': 'intro_v2',
'completion_time_seconds': 245,
},
);
await KlaviyoSDK().createEvent(customEvent);
// Track a purchase event with value
final purchaseEvent = KlaviyoEvent.custom(
metric: 'Purchase Completed',
properties: {
'currency': 'USD',
'product_id': 'prod_123',
'product_name': 'Premium Subscription',
},
value: 99.99,
);
await KlaviyoSDK().createEvent(purchaseEvent);
4. Push Notifications #
Prerequisites
Platform-Specific Setup:
- Android: Review Android SDK Push Notifications documentation for Firebase setup requirements
- iOS: Review iOS SDK Push Notifications documentation for APNs setup requirements
Key Requirements:
- Firebase project configured (for both platforms)
google-services.json(Android) andGoogleService-Info.plist(iOS) added to your project- Push notifications configured in your Klaviyo account settings
Requesting Notification Permissions
Requesting user permission to display notifications can be managed from the Flutter code, or from platform-specific native code. Note that either of these approaches is sufficient to inform the Klaviyo SDK of the permission change. If managing permissions from Flutter code, you may use a third-party package (such as firebase_messaging or permission_handler) to handle permissions requests.
Token Collection
The Klaviyo SDK needs to register the device's push token to send notifications. You can choose between two approaches:
Option A: Manual Token Management with Firebase Messaging (Recommended)
If your app already uses Firebase Messaging for other features, or you need more control over token handling, you can manually fetch tokens and pass them to Klaviyo:
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:logging/logging.dart';
final _logger = Logger('MyApp');
// Get token from Firebase and pass to Klaviyo
if (Platform.isIOS) {
String? apnsToken = await FirebaseMessaging.instance.getAPNSToken();
if (apnsToken != null) {
await KlaviyoSDK().setPushToken(apnsToken);
_logger.info('Sent APNs token to Klaviyo');
} else {
_logger.warning('APNs token was null. Waiting for refresh...');
}
} else if (Platform.isAndroid) {
String? fcmToken = await FirebaseMessaging.instance.getToken();
if (fcmToken != null) {
await KlaviyoSDK().setPushToken(fcmToken);
_logger.info('Sent FCM token to Klaviyo');
}
}
// Listen for Token Refreshes (Important for long-running apps)
// Note: On iOS, this stream returns the FCM token, not APNs.
// Native APNs token changes are rare, but for Android this is crucial.
if (Platform.isAndroid) {
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
KlaviyoSDK().setPushToken(newToken);
});
// On iOS, if the APNs token changes, the OS usually relaunches the app
// or triggers distinct native callbacks.
}
When to use this option:
- You're already using
firebase_messagingfor other features (e.g., foreground message handling) - You need to send the same token to multiple push providers
- You want explicit control over token refresh handling
Option B: Using the SDK's Built-in Registration
The simplest approach - the SDK automatically fetches the push token on both platforms without requiring you to add firebase_messaging as a direct dependency:
// Register for push notifications
// iOS: Triggers APNs registration and automatically registers it with Klaviyo
// Android: Fetches the FCM token and automatically registers it with Klaviyo
await KlaviyoSDK().registerForPushNotifications();
// Listen for token events via the push notification stream
KlaviyoSDK().onPushNotification.listen((event) {
switch (event['type']) {
case 'push_token_received':
final token = event['data']['token'];
_logger.info('Token received: $token');
break;
case 'push_token_error':
final error = event['data']['error'];
_logger.warning('Token error: $error');
break;
}
});
How it works:
- iOS: Calls the native APNs registration API and automatically forwards the token to Klaviyo
- Android: Uses the native Firebase SDK to fetch the FCM token and registers it with Klaviyo
- Token updates are emitted through the
onPushNotificationstream - No need to add
firebase_messagingas a direct dependency in your Flutter project
Handling Push Notification Opens
// Listen for notification open events
KlaviyoSDK().onPushNotification.listen((event) {
if (event['type'] == 'push_notification_opened') {
_logger.info('Notification opened: ${event['data']}');
}
});
5. Rich Push #
With Rich Push configured, you can add images and videos (on iOS only) to push notification messages. On iOS, you will need to implement an extension service to attach images to notifications. No additional setup is needed to support rich push on Android.
For iOS: Flutter currently only supports CocoaPods for dependency management, so you'll need to follow the CocoaPods setup steps in the installation section.
6. Badge Count (iOS Only) #
Klaviyo supports setting or incrementing the badge count on iOS when you send a push notification. To enable this functionality, you will need to implement a notification service extension and app group as detailed in the Swift SDK installation instructions. See the badge count documentation for more details and the example app for reference. Android automatically handles badge counts, and no additional setup is needed.
7. In-App Forms #
// Register for in-app forms with default session timeout (1 hour)
await KlaviyoSDK().registerForInAppForms();
// Register with a custom session timeout
final config = InAppFormConfig(
sessionTimeoutDuration: Duration(minutes: 30),
);
await KlaviyoSDK().registerForInAppForms(configuration: config);
// Register with infinite session timeout (no timeout)
final infiniteConfig = InAppFormConfig.infinite();
await KlaviyoSDK().registerForInAppForms(configuration: infiniteConfig);
// Unregister from in-app forms
await KlaviyoSDK().unregisterFromInAppForms();
// Listen for form events
KlaviyoSDK().onFormEvent.listen((event) {
_logger.info('Form event: ${event['type']}');
});
Session Timeout Configuration:
The sessionTimeoutDuration parameter controls how long forms remain eligible to display after app backgrounding. For more details on form behavior and configuration, see the native SDK documentation:
8. Deep Linking #
Klaviyo supports Deep Links for tracking link clicks and navigating to specific content within your app. This works with push notifications, in-app messages, and Klaviyo tracking links.
Prerequisites
-
Set up deep linking in your Flutter app using one of these approaches:
-
Configure platform-specific deep linking:
- iOS: Configure your project settings in Xcode to handle deep links (see below)
- Android: Configure intent filters in your
AndroidManifest.xml(see below)
iOS Deep Linking Configuration
1. Custom URL Schemes (e.g., myapp://product/123)
Follow steps 1 & 2 ("Register the URL scheme" and "Whitelist your URL scheme", respectively) under the Handling URL Schemes section of the Swift SDK's README.
No additional native code is required —
FlutterAppDelegatealready handlesapplication(_:open:options:)and forwards custom URL scheme deep links to the Flutter layer automatically.
2. Klaviyo Universal Tracking Links (e.g., https://trk.yourdomain.com/u/abc123)
Follow steps 1 & 2 ("Configure Universal Links in your Klaviyo account" and "Add the Associated Domains Entitlement", respectively) under the Handling Universal Links section of the Swift SDK's README.
No native AppDelegate override is needed for universal link handling.
FlutterAppDelegateimplementsapplication(_:continue:restorationHandler:)and forwards universal links to the Flutter layer automatically.
Once universal links are arriving in your Flutter app, call handleUniversalTrackingLink from
your router to let the Klaviyo SDK resolve tracking URLs. See the
go_router integration section for an example.
Android Deep Linking Configuration
The example AndroidManifest.xml demonstrates three types of deep links:
1. Custom URL Schemes (e.g., myapp://product/123)
Add to your MainActivity in AndroidManifest.xml:
<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="YOUR_CUSTOM_SCHEME" />
</intent-filter>
Additionally, this can be configured natively on Flutter. Just be sure that the deeplink path you use for actions matches your app package schema.
2. App Links (Universal Links) (e.g., https://yourdomain.com/product/123)
<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" />
<data android:host="yourdomain.com" />
</intent-filter>
3. Klaviyo Universal Tracking Links (e.g., https://trk.yourdomain.com/u/abc123)
<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" />
<data android:host="trk.send.yourdomain.com" />
<data android:pathPrefix="/u/" />
</intent-filter>
Testing Deep Links:
# Test custom URL scheme
adb shell am start -W -a android.intent.action.VIEW -d "myapp://product/123" com.your.package
# Test app link
adb shell am start -W -a android.intent.action.VIEW -d "https://yourdomain.com/product/123" com.your.package
# Test Klaviyo tracking link
adb shell am start -W -a android.intent.action.VIEW -d "https://trk.yourdomain.com/u/abc123" com.your.package
For complete Android App Links setup including domain verification, see the Android SDK Deep Linking Guide.
Integration with go_router
Use go_router's redirect callback to pass URLs to Klaviyo:
import 'package:go_router/go_router.dart';
import 'package:klaviyo_flutter_sdk/klaviyo_flutter_sdk.dart';
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomeScreen(),
routes: [
GoRoute(
path: 'product/:id',
builder: (context, state) => ProductScreen(
productId: state.pathParameters['id']!,
),
),
],
),
],
redirect: (context, state) {
// Fire-and-forget - Klaviyo tracks the link in the background
final klaviyo = KlaviyoSDK();
KlaviyoSDK().handleUniversalTrackingLink(state.uri.toString());
// Continue with normal navigation
return null;
},
);
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await KlaviyoSDK().initialize(apiKey: 'YOUR_API_KEY');
runApp(MaterialApp.router(routerConfig: router));
}
How It Works
- URL arrives via go_router's routing system
redirectcallback fires with the URL- Call
handleUniversalTrackingLink()- validates and returns immediately - Returns
trueif it's a Klaviyo tracking link (format:https://domain/u/...),falseotherwise - Native SDK tracks the click event in the background (fire-and-forget)
- Native SDK resolves the link and your Flutter routing library handles the final destination
Note: handleUniversalTrackingLink() is synchronous - it validates the URL and returns a bool immediately, while the native tracking happens asynchronously in the background.
9. Profile Reset (Logout) #
// Reset profile when user logs out
await KlaviyoSDK().resetProfile();
API Reference #
KlaviyoSDK #
The main SDK class that provides all functionality.
Methods
initialize({required String apiKey})- Initialize the SDKsetProfile(KlaviyoProfile profile)- Set a complete user profilesetEmail(String email)- Set user emailsetPhoneNumber(String phoneNumber)- Set user phone numbersetExternalId(String externalId)- Set external user IDgetEmail()- Get user emailgetPhoneNumber()- Get user phone numbergetExternalId()- Get external user IDsetProfileProperties(Map<String, dynamic> properties)- Set custom profile propertiessetProfileAttribute(String propertyKey, dynamic value)- Set a single profile attributesetLocation(KlaviyoLocation location)- Set profile locationcreateEvent(KlaviyoEvent event)- Create a new event to track a profile's activityregisterForPushNotifications()- Register for push notifications (iOS: triggers APNs registration, Android: fetches FCM token)setPushToken(String token)- Set push notification token (usually handled automatically by native SDKs)getPushToken()- Get current push tokenregisterForInAppForms({InAppFormConfig? configuration})- Register for in-app formsunregisterFromInAppForms()- Unregister from in-app formsregisterGeofencing()- Begin monitoring geofences (requires location permissions)unregisterGeofencing()- Stop monitoring all geofenceshandleUniversalTrackingLink(String url)- Validates and handles Klaviyo universal tracking links, returnsboolresetProfile()- Reset user profilesetBadgeCount(int count)- Set the badge count on the app icon (iOS only)setLogLevel(KlaviyoLogLevel logLevel)- Set logging level (Flutter-side only)dispose()- Clean up resources
Properties
isInitialized- Whether the SDK is initializedapiKey- Current API keyonPushNotification- Stream of push notification events (token received, notification opened, errors)onFormEvent- Stream of in-app form events
Models #
KlaviyoProfile
KlaviyoProfile({
String? email,
String? phoneNumber,
String? externalId,
String? firstName,
String? lastName,
String? organization,
String? title,
String? image,
KlaviyoLocation? location,
Map<String, dynamic>? properties,
})
KlaviyoEvent
KlaviyoEvent({
required EventMetric name,
Map<String, dynamic>? properties,
double? value,
String? uniqueId,
})
// Convenience constructor for custom events
KlaviyoEvent.custom({
required String metric,
Map<String, dynamic>? properties,
double? value,
String? uniqueId,
})
KlaviyoLocation
KlaviyoLocation({
required double latitude,
required double longitude,
String? address1,
String? address2,
String? city,
String? region,
String? country,
String? zip,
})
InAppFormConfig
// Default configuration (1 hour session timeout)
InAppFormConfig({
Duration? sessionTimeoutDuration,
})
// Infinite session timeout (no timeout)
InAppFormConfig.infinite()
Enums #
KlaviyoLogLevel
none- No loggingerror- Error messages onlywarning- Warning and error messagesinfo- Info, warning, and error messagesdebug- All messages including debug
EventMetric
Predefined event metrics:
EventMetric.openedApp- The 'Opened App' eventEventMetric.viewedProduct- The 'Viewed Product' eventEventMetric.addedToCart- The 'Added to Cart' eventEventMetric.startedCheckout- The 'Started Checkout' event
Custom event metrics:
EventMetric.custom(String name)- Create a custom event with any name
Example #
See the example/ directory for a complete working example.
Troubleshooting #
Android Issues #
Push Notifications Not Tracking Opens
Problem: Push notifications arrive but opens are not tracked in Klaviyo.
Solutions:
- Verify MainActivity setup: Ensure
Klaviyo.handlePush(intent)is called in bothonCreateandonNewIntentmethods. See example MainActivity.kt - Check launch mode: In
AndroidManifest.xml, your MainActivity should useandroid:launchMode="singleTop"orsingleTask - Enable debug logging: Add to
AndroidManifest.xmlto see detailed logs:<meta-data android:name="com.klaviyo.core.log_level" android:value="1" /> - Check logcat: Run
adb logcat | grep -i klaviyoto see if push opens are being processed
Push Notifications Not Displaying
Problem: Notifications don't appear in the system tray.
Solutions:
- Check Firebase setup:
- Verify
google-services.jsonis inandroid/app/ - Ensure Firebase plugin is applied in
android/app/build.gradle - Test with Firebase Console first to rule out Klaviyo-specific issues
- Verify
- Check notification permission:
- On Android 13+, runtime permission is required
- Verify permission is granted: Use
permission_handlerto check status
- Check Klaviyo account configuration: Verify FCM server key is configured in Klaviyo settings
Push Tokens Not Being Set
Problem: Tokens are not being registered with Klaviyo.
Solutions:
- Verify Firebase is configured: Ensure Firebase is properly initialized before calling
FirebaseMessaging.instance.getToken() - Check SDK initialization: Call
KlaviyoSDK().initialize()beforeKlaviyoSDK().setPushToken() - Check network connectivity: Token registration requires internet connection
- Verify token is retrieved: Add logging to confirm token is not null:
final token = await FirebaseMessaging.instance.getToken(); _logger.info('FCM Token: $token'); // Should not be null
Deep Links Not Working
Problem: Deep links don't open the app or navigate correctly.
Solutions:
- Verify intent filters: Check
AndroidManifest.xmlhas correct intent filters for your URL schemes. See example AndroidManifest.xml - Test with adb:
adb shell am start -W -a android.intent.action.VIEW -d "yourscheme://path" com.your.package - Check launchMode: Use
singleToporsingleTaskto avoid creating multiple activity instances - Verify App Links (HTTPS links): Run
adb shell pm verify-app-links --re-verify com.your.packageand ensureassetlinks.jsonis accessible
Build Issues
Problem: Build fails with SDK-related errors.
Solutions:
- MinSdk error: Ensure
android/app/build.gradlehasminSdkVersion 23or higher - Duplicate class errors: Remove any direct references to
com.klaviyo:klaviyo-android-sdkfrom your gradle files (the Flutter plugin includes it automatically) - Manifest merger errors: Check for conflicting permissions or activities in your manifest
iOS Issues #
For iOS-specific troubleshooting, refer to the iOS SDK documentation.
Common Issues (Both Platforms) #
Events Not Showing in Klaviyo Dashboard
Problem: Events are sent but don't appear in the dashboard.
Solutions:
- Wait 5-10 minutes: There's a processing delay for events to appear
- Verify API key: Ensure you're using the correct public API key
- Check profile is set: Events require a profile (email, phone, or external_id). Call
KlaviyoSDK().setEmail()orKlaviyoSDK().setExternalId()before tracking events - Check account status: Verify your Klaviyo account is active
SDK Not Initializing
Problem: SDK methods throw "not initialized" errors.
Solutions:
- Call initialize early: Call
KlaviyoSDK().initialize()inmain()beforerunApp():void main() async { WidgetsFlutterBinding.ensureInitialized(); await KlaviyoSDK().initialize(apiKey: 'YOUR_API_KEY'); runApp(MyApp()); } - Check for async issues: Ensure
awaitis used when callinginitialize()
Getting Additional Help #
- Native SDK Documentation:
- Klaviyo Support: https://help.klaviyo.com/
- GitHub Issues: Report bugs at https://github.com/klaviyo/klaviyo-flutter-sdk/issues
When reporting issues, include:
- SDK version
- Flutter version
- Platform (Android/iOS) and OS version
- Steps to reproduce
- Relevant logs (with debug logging enabled)
- Code snippets showing SDK usage
Contributing #
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
License #
This project is licensed under the MIT License - see the LICENSE file for details.
Support #
For support, please contact Klaviyo support or create an issue in this repository.
Native SDK Dependencies #
This Flutter SDK wraps the following native SDKs:
- iOS: Klaviyo Swift SDK
- Android: Klaviyo Android SDK
Make sure to refer to the native SDK documentation for platform-specific features and requirements.