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:

  1. Rating Selected - User taps a rating icon (1-5)

    • Fires immediately when rating is selected
    • Provides rating value only
  2. 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 supported
  • false: disable system material styling

Optional Color Overrides

ApperoColors supports optional overrides:

  • textFieldBackground: custom text field background color
  • cursor: 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 fontFamily name 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 fontFamily value

Troubleshooting Fonts

Font not displaying:

  • Check font is declared in pubspec.yaml
  • Run flutter clean and 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 fontFamily value

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:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests if applicable
  5. Submit a pull request

License

MIT License - See LICENSE for details.

Support

Credits

Developed by Pocketworks Mobile


Made with ❤️ for Flutter developers