Klaviyo Flutter Plugin - Integration Guide
Complete guide for integrating the klc_klaviyo_flutter plugin into your Flutter application with support for push notifications on both Android and iOS platforms.
Table of Contents
Prerequisites
Before you begin, ensure you have:
- Flutter SDK installed (version 3.10.0 or higher)
- A Klaviyo account with your Public API Key (Site ID)
- For Android: Firebase project with
google-services.json - For iOS: Apple Developer account with APNs certificate configured in Klaviyo
- Xcode 14+ (for iOS development)
Installation
Run:
flutter pub get klc_klaviyo_flutter
Android Setup
Step 1: Add JitPack Repository
To download the Klaviyo SDK, you need to add JitPack to the repositories section in your Gradle file:
Open android/build.gradle.kts (or android/build.gradle) and add the following line inside the repositories block:
maven { url = uri("https://jitpack.io") }
Example:
allprojects {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") } // Add this line
}
}
Step 2: Add Google Services Plugin
Update settings.gradle.kts
Open android/settings.gradle.kts and add the Google Services plugin at the top:
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("com.google.gms.google-services") version "4.4.2" apply false
}
// ... rest of the file
Update build.gradle.kts
Open android/app/build.gradle.kts and apply the Google Services plugin at the bottom:
plugins {
id("com.google.gms.google-services") // Add this line
}
Open android/app/settings.gradle.kts and apply the Google Services plugin at the bottom:
plugins {
id("com.google.gms.google-services") version "4.4.2" apply false // Add this line
}
Step 2: Add Firebase Configuration File
- Download
google-services.jsonfrom your Firebase Console - Place the file in
android/app/directory
android/
└── app/
├── build.gradle.kts
└── google-services.json ← Place here
iOS Setup
Step 1: Enable Push Notifications Capability
-
Open your iOS project in Xcode:
open ios/Runner.xcworkspace -
Select Runner target in the left sidebar
-
Go to Signing & Capabilities tab
-
Click + Capability button
-
Select Push Notifications
Step 2: Create Notification Service Extension
This extension is required for Rich Push Notifications (images, videos).
2.1 Create the Extension Target
-
In Xcode: File → New → Target
-
Select Notification Service Extension
-
Configure:
- Product Name:
KlaviyoNotificationServiceExtension - Language: Swift
- Project: Runner
- Click Finish
- Product Name:
-
When prompted "Activate scheme?", click Cancel
2.2 Set Deployment Target
-
Select KlaviyoNotificationServiceExtension target
-
Go to Build Settings tab
-
Search for "iOS Deployment Target"
-
Set to 13.0 (or match your app's deployment target)
2.3 Add KlaviyoSwiftExtension Dependency
Open ios/Podfile and add the extension target:
# ... existing code
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
# Add this section
target 'KlaviyoNotificationServiceExtension' do
use_frameworks!
pod 'KlaviyoSwiftExtension', '~> 5.1.1'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
2.4 Update NotificationService.swift
Replace the content of ios/KlaviyoNotificationServiceExtension/NotificationService.swift:
import KlaviyoSwiftExtension
import UserNotifications
import os.log
/// Klaviyo Notification Service Extension
/// Uses official KlaviyoSwiftExtension SDK to handle rich push (images, videos) and badge counts.
class NotificationService: UNNotificationServiceExtension {
private let logger = OSLog(subsystem: "com.abcsoft.sid96604127520.KlaviyoNotificationServiceExtension", category: "RichPush")
var request: UNNotificationRequest!
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
self.request = request
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
let userInfo = request.content.userInfo
if let bestAttemptContent = bestAttemptContent {
KlaviyoExtensionSDK.handleNotificationServiceDidReceivedRequest(
request: self.request,
bestAttemptContent: bestAttemptContent,
contentHandler: contentHandler
)
} else {
contentHandler(request.content)
}
}
override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler,
let bestAttemptContent = bestAttemptContent {
KlaviyoExtensionSDK.handleNotificationServiceExtensionTimeWillExpireRequest(
request: request,
bestAttemptContent: bestAttemptContent,
contentHandler: contentHandler
)
}
}
}
Step 3: Setup App Groups
App Groups allow the extension to share data with your main app.
3.1 Main App Target
-
Select Runner target
-
Go to Signing & Capabilities
-
Click + Capability → App Groups
-
Click + button to create a new App Group
-
Enter:
group.com.yourcompany.yourapp.klaviyo- Replace
yourcompanyandyourappwith your actual values - Example:
group.com.example.myshop.klaviyo
- Replace
-
Check the checkbox to enable it
3.2 Extension Target
-
Select KlaviyoNotificationServiceExtension target
-
Go to Signing & Capabilities
-
Click + Capability → App Groups
-
Select the same App Group you created for the main app
Step 4: Add klaviyo_app_group to Info.plist
4.1 Main App (Runner/Info.plist)
Add this key to ios/Runner/Info.plist:
<dict>
<!-- ... existing keys ... -->
<key>klaviyo_app_group</key>
<string>group.com.yourcompany.yourapp.klaviyo</string>
</dict>
4.2 Extension (KlaviyoNotificationServiceExtension/Info.plist)
Add the same key to ios/KlaviyoNotificationServiceExtension/Info.plist:
<dict>
<!-- ... existing keys ... -->
<key>klaviyo_app_group</key>
<string>group.com.yourcompany.yourapp.klaviyo</string>
</dict>
Step 5: Install Pods
Run pod install:
cd ios
pod install
cd ..
Step 6: Fix Build Phase Ordering
If you encounter a build error about circular dependencies:
-
In Xcode, select Runner target
-
Go to Build Phases tab
-
Find "Embed Foundation Extensions" phase
-
Drag it above the "Thin Binary" phase
-
The correct order should be:
- Dependencies - Compile Sources - Link Binary With Libraries - Embed Foundation Extensions ← Move here - Thin Binary - [CP] Embed Pods Frameworks - Run Script (Flutter Build)
iOS Setup Complete!
Run your app to verify:
flutter run -d ios
Usage
Initialize Klaviyo SDK
In your main.dart:
import 'package:flutter/material.dart';
import 'package:klc_klaviyo_flutter/klc_klaviyo_flutter.dart';
import 'package:klc_klaviyo_flutter/models/index.dart';
import 'package:firebase_core/firebase_core.dart';
import 'dart:io';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase (required for Android)
if (Platform.isAndroid) {
await Firebase.initializeApp();
}
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _status = 'Not initialized';
String? _pushToken;
String? _email;
@override
void initState() {
super.initState();
_setupKlaviyo();
}
Future<void> _setupKlaviyo() async {
// Setup push token callback
KlcKlaviyoFlutter.ic.onPushTokenReceived = (token) {
setState(() => _pushToken = token);
debugPrint('✅ Push token received: $token');
};
// Setup notification tap callback
KlcKlaviyoFlutter.ic.onNotificationTapped = (payload) {
debugPrint('👆 Notification tapped: $payload');
// Handle notification tap
};
try {
// Initialize with your Klaviyo Public API Key
await KlcKlaviyoFlutter.ic.initialize('YOUR_KLAVIYO_PUBLIC_API_KEY');
// Request push notification permission
await KlcKlaviyoFlutter.ic.requestPushPermission();
setState(() => _status = 'Initialized ✅');
} catch (e) {
setState(() => _status = 'Error: $e');
}
}
Future<void> _setProfile() async {
await KlcKlaviyoFlutter.ic.setProfile(
KlaviyoProfile(
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
properties: {'plan': 'premium'},
),
);
final email = await KlcKlaviyoFlutter.ic.getEmail();
setState(() => _email = email);
}
Future<void> _trackEvent() async {
await KlcKlaviyoFlutter.ic.createEvent(
KlaviyoEvent(
name: 'Added to Cart',
properties: {
'product_id': '123',
'product_name': 'Widget',
'price': 29.99
},
value: 29.99,
),
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Klaviyo Integration')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Status: $_status'),
Text('Email: ${_email ?? "Not set"}'),
Text('Token: ${_pushToken?.substring(0, 20) ?? "None"}...'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _setProfile,
child: const Text('Set Profile'),
),
ElevatedButton(
onPressed: _trackEvent,
child: const Text('Track Event'),
),
ElevatedButton(
onPressed: () async {
await KlcKlaviyoFlutter.ic.resetProfile();
setState(() => _email = null);
},
child: const Text('Logout'),
),
],
),
),
),
);
}
}
API Methods
| Method | Description |
|---|---|
initialize(apiKey) |
Initialize SDK with your Klaviyo Public API Key |
setProfile(profile) |
Set user profile information |
setEmail(email) |
Set user email address |
setPhoneNumber(phone) |
Set user phone number (E.164 format) |
setExternalId(id) |
Set external user ID |
resetProfile() |
Reset profile (call on logout) |
getEmail() |
Get current user email |
getPhoneNumber() |
Get current user phone |
getExternalId() |
Get current external ID |
createEvent(event) |
Track custom event |
requestPushPermission() |
Request push notification permission |
getPushPermissionStatus() |
Get current permission status |
getPushToken() |
Get current push token |
setPushToken(token) |
Set push token manually |
registerForInAppForms() |
Enable in-app forms |
unregisterFromInAppForms() |
Disable in-app forms |
Troubleshooting
Android Issues
Push Notifications Not Working
- Verify
google-services.jsonis inandroid/app/directory - Check that Firebase is initialized in your app
- Verify the plugin is applied in
build.gradle.kts:id("com.google.gms.google-services") - Check Logcat for error messages
Build Errors
If you see errors about Google Services:
cd android
./gradlew clean
cd ..
flutter clean
flutter pub get
iOS Issues
Push Token Not Received
-
Simulator Limitation: Push notifications don't work on iOS Simulator. Always test on a real device.
-
Check Push Notifications capability is enabled:
- Xcode → Runner → Signing & Capabilities → Push Notifications
-
Verify provisioning profile includes push notifications
-
Check console logs for errors
Rich Push Images Not Showing
-
App must be in background when receiving the notification
-
Verify App Groups are configured for both targets:
- Runner target → Signing & Capabilities → App Groups
- Extension target → Signing & Capabilities → App Groups
- Both must have the same group ID
-
Check
klaviyo_app_groupis in both Info.plist files -
Verify extension deployment target ≤ app deployment target
Extension Deployment Target Error
Error: The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to...
Solution:
- Select KlaviyoNotificationServiceExtension target
- Build Settings → Search "iOS Deployment Target"
- Set to 13.0 or match your app's target
Pod Install Issues
If pod install fails:
cd ios
rm -rf Pods Podfile.lock
pod install --repo-update
cd ..
General Issues
API Key Invalid
Make sure you're using the Public API Key (Site ID), not the Private API Key:
- Find it in Klaviyo dashboard: Settings → API Keys → Public API Key
Events Not Showing in Klaviyo
- Check that you initialized the SDK before tracking events
- Verify you set a user profile (email or external ID)
- Events may take a few minutes to appear in Klaviyo dashboard
- Check the Metrics section in your Klaviyo account
Resources
Support
If you encounter issues:
- Check this README's troubleshooting section
- Review the example app implementation
- Check Klaviyo's official documentation
- Report bugs on the plugin's GitHub repository
Last Updated: December 2025