Appero SDK for Flutter
The in-app feedback widget that drives organic growth.
Appero helps you capture user feedback at the right moments in your app journey. Built natively for iOS and Android with a Flutter-friendly API.
Features
✅ Automatic Feedback Prompts - Smart triggers based on user experience ✅ Native UI Components - High-performance native dialogs on both platforms ✅ Offline Support - Queues experiences when offline, syncs automatically ✅ Customizable Themes - Full control over colors, typography, and rating icons ✅ Custom Fonts - Support for brand-consistent fonts on iOS and Android ✅ Analytics Integration - Easy integration with Firebase, GA4, and custom platforms ✅ Cross-Platform - Single Dart API for iOS (16.0+) and Android (7.0+)
Installation
Add to your pubspec.yaml:
dependencies:
appero_flutter: ^0.1.1
Then run:
flutter pub get
Quick Start
1. Initialize the SDK
Initialize in your main() function before running your app:
import 'package:flutter/material.dart';
import 'package:appero_flutter/appero.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Appero.instance.initialize(
apiKey: 'your_api_key',
userId: 'user_123', // Optional but recommended
debug: true, // Enable debug logging
);
runApp(MyApp());
}
2. Log User Experiences
Track user sentiment at key moments in your app:
// After successful action
await Appero.instance.log(
rating: ExperienceRating.positive,
context: 'Purchase completed successfully',
);
// After error
await Appero.instance.log(
rating: ExperienceRating.negative,
context: 'Checkout failed',
);
3. That's it!
The SDK automatically handles feedback prompts based on logged experiences.
Core Concepts
Experience Logging
The SDK tracks user experiences and automatically determines when to show a feedback prompt based on the Experience Threshold configured in the Appero Dashboard.
Experience Ratings
Five levels of sentiment tracking:
| Rating | Value | Emoji | Use Case |
|---|---|---|---|
ExperienceRating.strongPositive |
5 | 😄 | Very satisfied - Feature delights user |
ExperienceRating.positive |
4 | 🙂 | Satisfied - Smooth completion |
ExperienceRating.neutral |
3 | 😐 | Neutral - Completed but suboptimal |
ExperienceRating.negative |
2 | 🙁 | Dissatisfied - Failed operations |
ExperienceRating.strongNegative |
1 | 😡 | Very dissatisfied - Critical failures |
Feedback Flows
Three distinct flows based on user sentiment:
- Positive Flow (😄 / 🙂): Thank you message, optional text feedback
- Neutral Flow (😐): Asks what could be improved
- Negative Flow (😡 / 🙁): Apologizes, asks what went wrong
When to Log Experiences
Positive Experiences (😄 / 🙂):
- Successful completion of user flows
- Feature usage that delights users
- Smooth transactions or purchases
- Positive responses to content
Negative Experiences (😡 / 🙁):
- Failed operations or error states
- Server errors or timeouts
- Abandoned flows
- User frustration indicators
Neutral Experiences (😐):
- Completed but suboptimal flows
- Feature discovery without engagement
- Canceled operations
⚠️ Important: Avoid logging sensitive information (addresses, phone numbers, emails, payment details) in the context field.
API Reference
Initialization
await Appero.instance.initialize({
required String apiKey, // Your Appero API key
String? userId, // Optional user identifier
bool debug = false, // Enable debug logging
});
Logging Experiences
await Appero.instance.log({
required ExperienceRating rating, // User sentiment level
String? context, // Optional context description
});
Manual Feedback Prompt
// Show feedback UI manually
await Appero.instance.showFeedbackPrompt();
// Dismiss feedback UI
await Appero.instance.dismissPrompt();
Posting Feedback
final success = await Appero.instance.postFeedback({
required int rating, // 1-5
String? feedback, // Optional text feedback
});
Theme Customization
await Appero.instance.setTheme(ApperoTheme theme);
// Reset to system default (auto light/dark)
await Appero.instance.setTheme(null);
Reset SDK Data
// Clear all locally stored data
await Appero.instance.reset();
Analytics Integration
Track user feedback interactions in your analytics platform (Firebase Analytics, Mixpanel, GA4, etc.).
Setup Analytics Delegate
import 'package:appero_flutter/appero.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
class MyAnalyticsDelegate implements ApperoAnalyticsDelegate {
final FirebaseAnalytics analytics = FirebaseAnalytics.instance;
@override
void onRatingSelected(int rating) {
// Called when user selects a rating (1-5)
analytics.logEvent(
name: 'appero_rating_selected',
parameters: {'rating': rating},
);
}
@override
void onFeedbackSubmitted(int rating, String? feedback) {
// Called when user submits feedback
analytics.logEvent(
name: 'appero_feedback_submitted',
parameters: {
'rating': rating,
'has_feedback': feedback != null,
'feedback_length': feedback?.length ?? 0,
},
);
}
}
// Set the delegate (typically in main())
Appero.instance.setAnalyticsDelegate(MyAnalyticsDelegate());
// Remove the delegate
Appero.instance.setAnalyticsDelegate(null);
Analytics Events
The delegate receives two types of events:
-
Rating Selected - User taps a rating icon (1-5)
- Fires immediately when rating is selected
- Provides rating value only
-
Feedback Submitted - User submits feedback form
- Fires when user completes and submits feedback
- Provides rating and optional text feedback
Theme Customization
Using Predefined Themes
import 'package:appero_flutter/appero.dart';
// System theme (auto light/dark mode)
await Appero.instance.setTheme(systemTheme);
// Custom theme 1 (purple/lime yellow)
await Appero.instance.setTheme(customTheme1);
// Custom theme 2 (rose/purple with custom font)
await Appero.instance.setTheme(customTheme2);
// Reset to default
await Appero.instance.setTheme(null);
Creating Custom Themes
const myTheme = ApperoTheme(
colors: ApperoColors(
surface: Color(0xFFFFFFFF), // Background
onSurface: Color(0xFF000000), // Primary text
onSurfaceVariant: Color(0xFF666666), // Secondary text
primary: Color(0xFF6200EE), // Accent color
onPrimary: Color(0xFFFFFFFF), // Text on accent
textFieldBackground: Color(0xFFF5F5F5), // Optional text field background override
cursor: Color(0xFF6200EE), // Optional cursor color override
),
typography: ApperoTypography(
titleLargeFontSize: 22.0,
titleLargeFontWeight: FontWeight.bold,
bodyMediumFontSize: 16.0,
bodyMediumFontWeight: FontWeight.normal,
labelLargeFontSize: 16.0,
labelLargeFontWeight: FontWeight.w500,
bodySmallFontSize: 14.0,
bodySmallFontWeight: FontWeight.normal,
fontFamily: null, // Use system font
),
usesSystemMaterial: false, // Disable frosted glass/system material effect
);
await Appero.instance.setTheme(myTheme);
Material Effect Control
ApperoTheme includes a usesSystemMaterial flag:
true(default): enable system material styling where supportedfalse: disable system material styling
Optional Color Overrides
ApperoColors supports optional overrides:
textFieldBackground: custom text field background colorcursor: custom text cursor color
If omitted, native defaults are used.
Platform support:
- iOS: supported
- Android: currently ignored by the native bridge until SDK parity is added
Custom Rating Images
const myRatingImages = ApperoRatingImages(
strongNegative: 'assets/rating_1.png',
negative: 'assets/rating_2.png',
neutral: 'assets/rating_3.png',
positive: 'assets/rating_4.png',
strongPositive: 'assets/rating_5.png',
);
const myTheme = ApperoTheme(
colors: ApperoColors(...),
typography: ApperoTypography(...),
ratingImages: myRatingImages,
);
await Appero.instance.setTheme(myTheme);
Custom Fonts
The SDK supports custom fonts on both iOS and Android.
Step 1: Add Font Files
Create a fonts directory and add your font files:
your_app/
fonts/
YourFont-Regular.ttf
YourFont-Bold.ttf
Step 2: Register in pubspec.yaml
flutter:
fonts:
- family: your_font_name
fonts:
- asset: fonts/YourFont-Regular.ttf
weight: 400
- asset: fonts/YourFont-Bold.ttf
weight: 700
Step 3: Setup for Android
Copy the font to Android resources with lowercase, underscore-separated naming:
mkdir -p android/app/src/main/res/font
cp fonts/YourFont-Regular.ttf android/app/src/main/res/font/your_font_name.ttf
Android naming rules:
- Must be lowercase
- Use underscores instead of hyphens or spaces
- Should match the
fontFamilyname in your theme
Step 4: Use in Theme
const myTheme = ApperoTheme(
colors: ApperoColors(...),
typography: ApperoTypography(
fontFamily: 'your_font_name', // Must match pubspec.yaml family name
),
);
await Appero.instance.setTheme(myTheme);
Platform Details
iOS:
- ✅ Fonts loaded automatically from Flutter assets
- ✅ PostScript name extracted and registered dynamically
- ✅ No Info.plist configuration needed
- ✅ Supports TrueType (.ttf) and OpenType (.otf)
Android:
- ✅ Fonts must exist in
android/app/src/main/res/font/ - ✅ Filename must be lowercase with underscores
- ✅ Filename should match
fontFamilyvalue
Troubleshooting Fonts
Font not displaying:
- Check font is declared in
pubspec.yaml - Run
flutter cleanand rebuild - Verify font file is not corrupted
- Check platform-specific logs for errors
Android font issues:
- Verify file exists in
android/app/src/main/res/font/ - Ensure lowercase filename with underscores
- Confirm filename matches
fontFamilyvalue
Example App
The included example demonstrates all SDK features:
cd example
flutter run
Features demonstrated:
- SDK initialization with debug mode
- Analytics delegate integration
- Theme switching (System, Theme 1, Theme 2)
- Experience logging with all rating levels
- Manual feedback prompt triggering
- Error handling with SnackBar notifications
Architecture
Flutter Layer
- Language: Dart 3.0+
- State Management: Streams and StreamSubscription
- Platform Channels: MethodChannel & EventChannel
iOS Native
- Language: Swift
- UI Framework: SwiftUI
- Reactive: Combine framework
- Minimum Version: iOS 16.0+
Android Native
- Language: Kotlin
- UI Framework: Jetpack Compose
- Coroutines: kotlinx.coroutines
- Minimum SDK: 24 (Android 7.0)
Requirements
- Flutter: >=3.24.0
- Dart: >=3.0.0 <4.0.0
- iOS: >=16.0
- Android: SDK 24+ (Android 7.0 Nougat)
Offline Support
The SDK handles offline scenarios gracefully:
- ✅ Experiences logged while offline are queued locally
- ✅ Automatic retry when connection is restored
- ✅ Network state monitoring
- ✅ JSON file storage in secure app storage
Best Practices
1. Initialize Early
Initialize in main() before running your app to ensure the SDK is ready.
2. Use Meaningful Context
Provide descriptive context when logging experiences to help identify patterns:
// Good ✅
await Appero.instance.log(
rating: ExperienceRating.positive,
context: 'Checkout completed - 3 items',
);
// Avoid ❌
await Appero.instance.log(
rating: ExperienceRating.positive,
context: 'success',
);
3. Set User ID
Provide a user ID during initialization to track feedback across sessions:
await Appero.instance.initialize(
apiKey: 'your_key',
userId: userAuthId, // From your auth system
);
4. Analytics Integration
Set up analytics delegate to track engagement metrics:
Appero.instance.setAnalyticsDelegate(MyAnalyticsDelegate());
5. Handle Errors Gracefully
Wrap SDK calls in try-catch blocks:
try {
await Appero.instance.log(rating: rating, context: context);
} catch (e) {
// Handle error appropriately
debugPrint('Appero error: $e');
}
6. Theme Consistency
Apply your theme once during initialization:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Appero.instance.initialize(apiKey: 'key');
await Appero.instance.setTheme(myCustomTheme);
runApp(MyApp());
}
7. Don't Log PII
Never log personally identifiable information in the context field:
// Never do this ❌
await Appero.instance.log(
rating: rating,
context: 'User email: ${user.email}', // ❌ Don't log PII
);
// Do this instead ✅
await Appero.instance.log(
rating: rating,
context: 'User profile updated', // ✅ Generic context
);
Sample Use Cases
E-commerce App
class CheckoutPage extends StatelessWidget {
Future<void> _completeCheckout() async {
try {
await processPayment();
// Log positive experience
await Appero.instance.log(
rating: ExperienceRating.strongPositive,
context: 'Checkout completed',
);
navigateToConfirmation();
} catch (e) {
// Log negative experience
await Appero.instance.log(
rating: ExperienceRating.negative,
context: 'Checkout failed',
);
showError(e);
}
}
}
Content App
class VideoPlayer extends StatefulWidget {
void _onVideoComplete() {
// User watched entire video - positive signal
Appero.instance.log(
rating: ExperienceRating.positive,
context: 'Video watched to completion',
);
}
void _onVideoError(String error) {
// Playback failed - negative signal
Appero.instance.log(
rating: ExperienceRating.strongNegative,
context: 'Video playback error',
);
}
}
Social App
class PostCreation extends StatelessWidget {
Future<void> _publishPost() async {
final result = await api.createPost(content);
if (result.success) {
Appero.instance.log(
rating: ExperienceRating.strongPositive,
context: 'Post published successfully',
);
} else {
Appero.instance.log(
rating: ExperienceRating.negative,
context: 'Post publication failed',
);
}
}
}
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
License
MIT License - See LICENSE for details.
Support
- Issues: GitHub Issues
- Email: support@appero.co.uk
- Documentation: This README
Credits
Developed by Pocketworks Mobile
Made with ❤️ for Flutter developers