Clix Flutter SDK is a powerful tool for managing push notifications and user events in your Flutter application. It provides a simple and intuitive interface for user engagement and analytics.
Add this to your package's pubspec.yaml file:
dependencies:
clix_flutter: ^0.0.3Then run:
flutter pub get- Flutter 3.33.0 or later (required for iOS debug mode on iOS 26+)
- Dart 2.17.0 or later
- iOS 14.0+ / Android API 23+
- Firebase Cloud Messaging
Initialize the SDK with a ClixConfig object. The config is required and contains your project settings.
import 'package:clix_flutter/clix_flutter.dart';
import 'package:firebase_core/firebase_core.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase first
await Firebase.initializeApp();
// Initialize Clix SDK
await Clix.initialize(const ClixConfig(
projectId: 'YOUR_PROJECT_ID',
apiKey: 'YOUR_API_KEY',
endpoint: 'https://api.clix.so', // Optional: default is https://api.clix.so
logLevel: ClixLogLevel.debug, // Optional: set log level
));
// Configure notifications (optional)
await Clix.Notification.configure(
autoRequestPermission: true, // Request permission immediately
autoHandleLandingURL: true, // Auto-open landing URLs on tap
);
runApp(MyApp());
}// Set user ID
await Clix.setUserId('user123');
// Set user properties
await Clix.setUserProperty('name', 'John Doe');
await Clix.setUserProperties({
'age': 25,
'premium': true,
});
// Remove user properties
await Clix.removeUserProperty('name');
await Clix.removeUserProperties(['age', 'premium']);
// Remove user ID
await Clix.removeUserId();// Track an event with properties
await Clix.trackEvent(
'signup_completed',
properties: {
'method': 'email',
'discount_applied': true,
'trial_days': 14,
'completed_at': DateTime.now(),
},
);// Get device ID
final deviceId = await Clix.getDeviceId();
// Get push token
final pushToken = await Clix.Notification.getToken();Clix.setLogLevel(ClixLogLevel.debug);
// Available log levels:
// - ClixLogLevel.none: No logs
// - ClixLogLevel.error: Error logs only
// - ClixLogLevel.warning: Warning logs
// - ClixLogLevel.info: Info logs
// - ClixLogLevel.debug: Debug logs
// - ClixLogLevel.verbose: All logsThe Clix Flutter SDK automatically handles push notification integration through Firebase Cloud Messaging.
iOS:
- Add your
GoogleService-Info.plistto the iOS project in Xcode - Enable Push Notifications capability in your iOS project
- Add Background Modes capability and check "Remote notifications"
Android:
- Add your
google-services.jsontoandroid/app/ - Firebase configuration is handled automatically by FlutterFire
Register handlers for push notification events:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await Clix.initialize(const ClixConfig(
projectId: 'YOUR_PROJECT_ID',
apiKey: 'YOUR_API_KEY',
));
// Register notification handlers (all receive full RemoteMessage)
Clix.Notification.onMessage((message) async {
// Called when message received in foreground
print('Foreground message: ${message.messageId}');
return true; // Return true to display, false to suppress
});
Clix.Notification.onBackgroundMessage((message) async {
// Called when message received in background
print('Background message: ${message.messageId}');
});
Clix.Notification.onNotificationOpened((message) {
// Called when user taps notification (app was in background)
print('Notification tapped: ${message.messageId}');
final clixData = message.data['clix'];
// Handle custom routing based on notification data
});
Clix.Notification.onFcmTokenError((error) {
print('FCM token error: $error');
});
runApp(MyApp());
}Important: All Clix.Notification methods must be called after Clix.initialize().
All handlers receive the full RemoteMessage object for Firebase compatibility:
| Handler | Signature | Description |
|---|---|---|
onMessage |
Future<bool> Function(RemoteMessage) |
Foreground messages. Return true to display, false to suppress |
onBackgroundMessage |
Future<void> Function(RemoteMessage) |
Background messages. Matches Firebase's BackgroundMessageHandler |
onNotificationOpened |
void Function(RemoteMessage) |
All notification taps (FCM + local). Matches Firebase's onNotificationOpened |
Access Clix metadata via message.data['clix'].
If your app already uses firebase_messaging with custom handlers, you can migrate to Clix SDK while preserving your existing logic.
Why migrate? The Clix SDK internally registers Firebase Messaging handlers. If you register your own handlers separately, they may conflict or be overwritten. By passing your handlers to Clix, both SDK tracking and your custom logic work together.
The onBackgroundMessage handler signature matches Firebase's BackgroundMessageHandler exactly, making migration straightforward:
Before (Firebase direct):
@pragma('vm:entry-point')
Future<void> myBackgroundHandler(RemoteMessage message) async {
print('Background message: ${message.messageId}');
await saveToLocalDB(message.data);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Direct Firebase registration
FirebaseMessaging.onBackgroundMessage(myBackgroundHandler);
runApp(MyApp());
}After (via Clix SDK):
@pragma('vm:entry-point')
Future<void> myBackgroundHandler(RemoteMessage message) async {
// Same handler code - no changes needed
print('Background message: ${message.messageId}');
await saveToLocalDB(message.data);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Pass handler to Clix instead of Firebase directly
Clix.Notification.onBackgroundMessage(myBackgroundHandler);
await Clix.initialize(const ClixConfig(
projectId: 'YOUR_PROJECT_ID',
apiKey: 'YOUR_API_KEY',
));
runApp(MyApp());
}When a background message arrives, the execution order is:
1. Firebase receives RemoteMessage
↓
2. Clix SDK internal handler runs (logging, setup)
↓
3. Your handler executes (await myBackgroundHandler(message))
↓
4. Clix SDK completes processing (event tracking, notification display)
This ensures your custom logic runs with full RemoteMessage access while Clix handles analytics and notification display automatically.
The onMessage handler also receives full RemoteMessage:
Before (Firebase direct):
FirebaseMessaging.onMessage.listen((message) {
print('Foreground message: ${message.messageId}');
showLocalNotification(message);
});After (via Clix SDK):
Clix.Notification.onMessage((message) async {
print('Foreground message: ${message.messageId}');
// Return true to let Clix display notification, false to suppress
return true;
});Before (Firebase direct):
FirebaseMessaging.onNotificationOpened.listen((message) {
handleNotificationTap(message);
});After (via Clix SDK):
Clix.Notification.onNotificationOpened((message) {
handleNotificationTap(message);
});// Get current FCM token
final token = await Clix.Notification.getToken();
// Delete FCM token
await Clix.Notification.deleteToken();By default, the SDK does not automatically request notification permissions. You can request permission at the right moment in your app's UX:
await Clix.initialize(const ClixConfig(
projectId: 'YOUR_PROJECT_ID',
apiKey: 'YOUR_API_KEY',
));
// Option 1: Request immediately via configure()
await Clix.Notification.configure(autoRequestPermission: true);
// Option 2: Request at a specific point (e.g., after onboarding)
final status = await Clix.Notification.requestPermission();
if (status == AuthorizationStatus.authorized) {
print('Notifications enabled!');
}
// Check current permission status
final currentStatus = await Clix.Notification.getPermissionStatus();If you've disabled automatic permission requests (default is false), you must manually notify Clix when users grant or deny push permissions.
After requesting push permissions in your app, call Clix.Notification.setPermissionGranted():
final settings = await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
sound: true,
);
final isGranted = settings.authorizationStatus == AuthorizationStatus.authorized ||
settings.authorizationStatus == AuthorizationStatus.provisional;
// Notify Clix SDK about permission status
await Clix.Notification.setPermissionGranted(isGranted);
if (isGranted) {
print('Push notifications enabled!');
}This ensures Clix can accurately track permission status for your users and target campaigns appropriately.
If your app already uses flutter_local_notifications with a custom callback, migrate to onNotificationOpened:
Before (flutter_local_notifications direct):
await flutterLocalNotificationsPlugin.initialize(
initSettings,
onDidReceiveNotificationResponse: (response) {
if (response.payload != null) {
final data = jsonDecode(response.payload!);
handleMyNotificationTap(data);
}
},
);After (via Clix SDK):
// Remove your flutter_local_notifications initialization code
// The SDK handles it internally
// Use onNotificationOpened for ALL notification taps (FCM + local)
Clix.Notification.onNotificationOpened((message) {
handleMyNotificationTap(message.data); // Your existing logic
});Why migrate? The flutter_local_notifications plugin can only have one onDidReceiveNotificationResponse callback. The SDK handles initialization internally and calls your onNotificationOpened handler for all notification taps (both FCM and local notifications).
All SDK operations can throw ClixError. Always handle potential errors:
try {
await Clix.setUserId('user123');
} catch (error) {
print('Failed to set user ID: $error');
}The SDK is thread-safe and all operations can be called from any isolate. Async operations will automatically wait for SDK initialization to complete.
If push notifications aren't working, verify:
- ✅ Firebase is initialized before Clix SDK
- ✅
google-services.json(Android) andGoogleService-Info.plist(iOS) are added - ✅ Push Notifications capability is enabled (iOS)
- ✅ Testing on a real device (push notifications don't work on iOS simulator)
- ✅ Debug logs show FCM token registration messages
- ✅
Clix.Notification.setPermissionGranted()is called after requesting permissions (when not using auto-request)
If you're experiencing FCM token registration failures, use the error handler:
Clix.Notification.onFcmTokenError((error) {
print('FCM token error: $error');
// Common causes:
// - Missing or invalid google-services.json/GoogleService-Info.plist
// - Network connectivity issues
// - Firebase service errors
});If you experience crashes with the error Unable to flip between RX and RW memory protection on pages when running in debug mode on iOS physical devices:
Cause: iOS 26 introduced stricter memory protection policies that affect Flutter's JIT (Just-In-Time) compilation in debug mode.
Solution: Upgrade Flutter to version 3.33.0 or later:
flutter upgrade
flutter clean
cd ios && rm -rf Pods Podfile.lock && pod install --repo-updateWorkaround (if upgrade is not possible):
- Use Profile or Release mode for physical device testing:
flutter run --profile - Use iOS Simulator for debug mode testing
For more details, see Flutter Issue #163984.
If you continue to experience issues:
- Enable debug logging (
ClixLogLevel.debug) - Check console for Clix log messages
- Verify your device appears in the Clix console Users page
- Check if
push_tokenfield is populated for your device - Create an issue on GitHub with logs and configuration details
A comprehensive sample app is provided in the samples/basic_app directory. The sample demonstrates:
- Basic Clix SDK integration
- Push notification handling with Firebase
- User property management
- Event tracking
- Device information display
To run the sample:
- Navigate to
samples/basic_app - Follow the Firebase setup instructions in
FIREBASE_SETUP.md - Update
lib/clix_configuration.dartwith your project details - Run the app:
flutter run
This project is licensed under the MIT License with Custom Restrictions. See the LICENSE file for details.
See the full release history and changes in the CHANGELOG.md file.
We welcome contributions! Please read the CONTRIBUTING.md guide before submitting issues or pull requests.