sensors_wave_sdk 0.1.0 copy "sensors_wave_sdk: ^0.1.0" to clipboard
sensors_wave_sdk: ^0.1.0 copied to clipboard

Sensors Wave Flutter SDK for event tracking and AB testing.

Sensors Wave Flutter SDK #

Sensors Wave Sensors Wave Flutter SDK is a mobile analytics tracking and AB testing library for Flutter applications. If you're new to Sensor Wave, check out our product and create an account at sensorswave.com.

SDK Usage #

1. Install via pub.dev #

Add the following to your pubspec.yaml:

dependencies:
  sensors_wave_sdk: ^0.1.0

Then run:

flutter pub get

2. Initialize the SDK #

import 'package:sensors_wave_sdk/sensors_wave_sdk.dart';

// In your main.dart or app initialization
final sdk = SensorsWaveSDK();
sdk.init('your-source-token', config: SensorsConfig.create(
  debug: false,
  apiHost: 'https://your-api-host.com',
  autoCapture: true,
));

// SDK init is synchronous - you can use it immediately
sdk.trackEvent('APP_READY');

3. Integrate Route Observer #

To enable automatic page tracking, add the route observer to your MaterialApp:

MaterialApp(
  navigatorObservers: [
    SensorsWaveSDK().routeObserver ?? [],
  ],
  // ... other properties
);

4. Send Custom Events #

SensorsWaveSDK().trackEvent('ButtonClick', {
  'button_name': 'submit',
  'page': 'home',
});

Configuration Options #

Option Type Default Description
debug bool false Whether to enable debug mode for logging
apiHost String '' API host address for sending events
autoCapture bool true Whether to automatically capture app events (app start, app end, page view, page leave)
enableClickTrack bool false Whether to enable automatic click tracking on supported widgets
enableAB bool false Whether to enable A/B testing feature
abRefreshInterval int 600000 (10 minutes) The interval in milliseconds for refreshing A/B test configuration (minimum 30000ms)
batchSend bool true Whether to use batch sending mode. When true, events are sent in batches (up to 20 events every 5 seconds). When false, events are sent immediately one by one.

API Methods #

Event Tracking #

trackEvent

Manually track a custom event with properties.

Parameters:

  • eventName (String, required): The name of the event to track
  • properties (Map<String, dynamic>, optional): Additional properties to include with the event

Example:

SensorsWaveSDK().trackEvent('ButtonClick', {
  'button_name': 'submit',
  'page': 'home',
  'category': 'user_action',
});

track

Track an event with full control over the event structure. This is an advanced method that allows you to specify all event fields manually.

Parameters:

  • event (SensorsSendEvent, required): Complete event object

Example:

final event = SensorsSendEvent(
  event: 'PurchaseCompleted',
  properties: {
    'product_id': '12345',
    'amount': 99.99,
    'currency': 'USD',
  },
  time: DateTime.now().millisecondsSinceEpoch,
  traceId: UUID(),
  anonId: SensorsWaveSDK().getAnonId(),
  loginId: SensorsWaveSDK().getLoginId(),
  userProperties: {
    'plan': 'premium',
    'signup_date': '2024-01-01',
  },
);

SensorsWaveSDK().track(event);

User Profile #

profileSet

Set user properties. If a property already exists, it will be overwritten.

Parameters:

  • properties (Map<String, dynamic>, required): User properties to set

Example:

await SensorsWaveSDK().profileSet({
  'name': 'John Doe',
  'email': 'john@example.com',
  'age': 30,
  'plan': 'premium',
});

profileSetOnce

Set user properties only if they don't already exist. Existing properties will not be overwritten.

Parameters:

  • properties (Map<String, dynamic>, required): User properties to set once

Example:

await SensorsWaveSDK().profileSetOnce({
  'signup_date': '2024-01-15',
  'initial_referrer': 'google',
  'initial_campaign': 'spring_sale',
});

profileIncrement

Increment numeric user properties by a specified amount. Only supports numeric properties.

Parameters:

  • properties (Map<String, num>, required): Properties to increment with numeric values

Example:

// Increment single property
await SensorsWaveSDK().profileIncrement({
  'login_count': 1,
});

// Increment multiple properties
await SensorsWaveSDK().profileIncrement({
  'login_count': 1,
  'points_earned': 100,
  'purchases_count': 1,
});

profileAppend

Append new values to list-type user properties without deduplication.

Parameters:

  • properties (Map<String, dynamic>, required): Properties with list values to append

Example:

await SensorsWaveSDK().profileAppend({
  'categories_viewed': ['electronics', 'mobile_phones'],
  'tags': ['new_customer', 'q1_2024'],
});

profileUnion

Append new values to list-type user properties with deduplication (avoids duplicate values).

Parameters:

  • properties (Map<String, List>, required): Properties with list values to append with deduplication

Example:

await SensorsWaveSDK().profileUnion({
  'interests': ['technology', 'gaming'],
  'newsletter_subscriptions': ['tech_news'],
});

profileUnset

Set specific user properties to null (effectively removing them).

Parameters:

  • keys (String or List

Example:

// Unset single property
await SensorsWaveSDK().profileUnset('temporary_campaign');

// Unset multiple properties
await SensorsWaveSDK().profileUnset(['old_plan', 'expired_flag', 'temp_id']);

profileDelete

Delete all user profile data for the current user. This operation cannot be undone.

Example:

await SensorsWaveSDK().profileDelete();

User Identification #

identify

Set the login ID for the current user and send a $Identify binding event to the backend.

Parameters:

  • loginId (String or num, required): Unique identifier for the user (e.g., email, user ID, username)

Example:

SensorsWaveSDK().identify('user@example.com');

setLoginId

Set the login ID for the current user without sending a binding event.

Difference from identify:

  • identify: Sets login ID AND sends a $Identify event to backend
  • setLoginId: Only sets login ID locally, no event sent

Parameters:

  • loginId (String or num, required): Unique identifier for the user

Example:

// Set login ID without triggering event
SensorsWaveSDK().setLoginId('user@example.com');

// Also supports numeric types (auto-converted to String)
SensorsWaveSDK().setLoginId(12345);

getLoginId

Get the current login ID.

Returns: String?

Example:

final loginId = SensorsWaveSDK().getLoginId();

getAnonId

Get the anonymous ID for the current user.

Returns: String?

Example:

final anonId = SensorsWaveSDK().getAnonId();

Common Properties #

registerCommonProperties

Register static common properties that will be included with all events. This is useful for including global context like app version, environment, or user-specific data.

Parameters:

  • properties (Map<String, dynamic>, required): Properties to register

Example:

SensorsWaveSDK().registerCommonProperties({
  'app_version': '1.0.0',
  'environment': 'production',
  'user_tier': getUserTier(),
});

clearCommonProperties

Remove specific registered common properties.

Parameters:

  • keys (List

Example:

SensorsWaveSDK().clearCommonProperties(['app_version', 'user_session_id']);

A/B Testing #

checkFeatureGate

Check if a feature gate (feature flag) is enabled for the current user.

Parameters:

  • key (String, required): The feature gate key to check

Returns: Future

Example:

// Check if a feature is enabled
final isEnabled = await SensorsWaveSDK().checkFeatureGate('new_checkout_flow');
if (isEnabled) {
  // Show new feature
  showNewCheckout();
} else {
  // Show old feature
  showOldCheckout();
}

getExperiment

Get experiment variant data for the current user.

Parameters:

  • key (String, required): The experiment key to retrieve

Returns: Future<Map<String, dynamic>>

Example:

// Get experiment configuration
final experiment = await SensorsWaveSDK().getExperiment('homepage_layout');
if (experiment.isNotEmpty) {
  // Apply experiment configuration
  applyLayout(experiment['layout_type']);
}

Other Methods #

destroy

Clean up and release all SDK resources. Call this when the app is terminating.

Example:

await SensorsWaveSDK().destroy();

Best Practices #

1. Initialization Timing #

Initialize the SDK as early as possible in your app's lifecycle:

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize SDK
  final sdk = SensorsWaveSDK();
  sdk.init('your-source-token');

  runApp(MyApp());
}

2. User Identity Management #

Use identify() when a user logs in to track their identity across sessions:

// When user logs in
sdk.identify('user@example.com');

// When you need to update login ID without sending event
sdk.setLoginId('new.email@example.com');

3. Event Naming Convention #

  • Use descriptive event names: ButtonClick, PurchaseCompleted
  • Avoid using $ prefix for custom events (it's reserved for preset events)

4. Property Design #

  • Use snake case for property keys: user_id, product_name, is_premium
  • Include context in properties: { 'button_name': 'submit', 'page': 'checkout' }
  • Use consistent data types for the same property across events

5. Common Properties #

Register app-level properties once instead of including them in every event:

// Set once at app startup
sdk.registerCommonProperties({
  'app_version': '1.0.0',
  'user_tier': getUserTier(),
  'environment': 'production',
});

// These will be automatically included in all events
sdk.trackEvent('any_event'); // Properties above are included

6. Error Handling #

The SDK handles errors internally and logs them when debug mode is enabled:

sdk.init('your-source-token', config: SensorsConfig.create(
  debug: true, // Enable debug logs during development
));

7. Memory Management #

The SDK manages resources automatically. Call destroy() only when:

  • User logs out and you want to clear all data
  • App is shutting down permanently
  • You need to reinitialize the SDK with different configuration
await sdk.destroy();

FAQ #

Q: What's the difference between identify() and setLoginId()? #

A:

  • identify(): Sets the login ID AND sends a $Identify binding event to the backend. Use this when a user logs in.
  • setLoginId(): Only sets the login ID locally without sending an event. Use this when you need to update the ID but don't want to trigger the binding event.

Q: Is init() synchronous or asynchronous? #

A: The init() method is synchronous and returns immediately. Actual initialization (loading data from storage, collecting device info, initializing plugins) happens in the background. You can call other SDK methods right after init() returns.

Q: When should I disable autoCapture? #

A: Disable autoCapture if:

  • You want full control over what events are tracked
  • You only want to track specific custom events
  • You need to implement custom page view tracking logic
  • You want to minimize performance impact

Q: What's the difference between batchSend: true and batchSend: false? #

A:

  • batchSend: true (default): Events are queued and sent in batches (up to 20 events every 5 seconds). Better for performance and network efficiency.
  • batchSend: false: Each event is sent immediately. Better for real-time analytics but uses more network requests.

Q: How do I track events that need to be sent immediately? #

A: The SDK automatically handles critical events (like $AppEnd) with immediate sending. For custom events, use batchSend: false or call flush() after tracking.

Q: Can I use the SDK in the background? #

A: Yes, but note that some features like automatic page tracking won't work in the background since there are no route changes. Event tracking and user profile methods work normally.

Q: How do I test the SDK during development? #

A:

  1. Enable debug mode: sdk.init('your-token', config: SensorsConfig.create(debug: true))
  2. Check console logs for event tracking details
  3. Verify events are sent to your analytics dashboard
  4. Use a test source token separate from production

Q: What happens if I call init() multiple times? #

A: The SDK ignores subsequent init() calls after the first initialization. The first call's configuration is used throughout the SDK lifecycle.

Q: How do I clear user data? #

A: Use the appropriate method based on your needs:

  • Clear user properties: sdk.profileDelete()
  • Clear login ID: sdk.setLoginId(null) or sdk.setLoginId('')
  • Clear all data: await sdk.destroy() then reinitialize

Q: Does the SDK work offline? #

A: Yes! Events are queued in local storage using SharedPreferences. When the device comes back online, queued events are automatically sent.

Supported Event Types #

The SDK automatically captures the following event types when autoCapture is enabled:

  • $AppInstall: Triggered on first app launch
  • $AppStart: Triggered when the app enters the foreground
  • $AppEnd: Triggered when the app enters the background
  • $AppPageView: Triggered when a user navigates to a new page/route
  • $AppPageLeave: Triggered when a user leaves a page/route
  • $AppClick: Triggered on element interactions (only when enableClickTrack is true)

AppClick - Supported Elements #

When enableClickTrack is enabled, the SDK automatically tracks clicks on the following Flutter widget types:

Widget Type Description
ElevatedButton Material Design elevated button
TextButton Material Design text button (flat button)
OutlinedButton Material Design outlined button
FilledButton Material Design filled button
IconButton Icon button with an icon
ListTile Single row in a list with optional leading/trailing widgets
GestureDetector Custom gesture detector widget
InkWell Material ink splash widget (also covers widgets that use InkWell internally)

How It Works

  1. Enable click tracking in the SDK config:
sdk.init('your-source-token', config: SensorsConfig.create(
  enableClickTrack: true,
));
  1. Use any of the supported widgets normally - no wrapper components needed:
// These will be tracked automatically
ElevatedButton(
  onPressed: () => doSomething(),
  child: Text('Click Me'),
)

TextButton(
  onPressed: () => doSomething(),
  child: Text('Text Button'),
)

IconButton(
  icon: Icon(Icons.favorite),
  onPressed: () => doSomething(),
)

ListTile(
  title: Text('Title'),
  onTap: () => doSomething(),
)

Event Properties

Each $AppClick event includes the following properties:

Property Description
$element_type Type of the clicked element (e.g., "ElevatedButton", "TextButton")
$element_content Text content of the clicked element (if available)
$screen_name Current screen/page name when the click occurred
$event_duration Time in seconds since the page was viewed
$element_selector CSS-style selector for the element (e.g., "[ElevatedButton]")
$element_path Full path to the element (e.g., "/HomePage/ElevatedButton")

Manual Click Tracking

Custom events can be tracked using the trackEvent() or track() methods.

Complete Example #

Here's a complete example showing how to integrate the SDK into a Flutter app:

import 'package:flutter/material.dart';
import 'package:sensors_wave_sdk/sensors_wave_sdk.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize SDK (synchronous, returns immediately)
  final sdk = SensorsWaveSDK();
  sdk.init('your-source-token', config: SensorsConfig.create(
    debug: false,
    autoCapture: true,
    enableAB: true,
    enableClickTrack: true,
    batchSend: true,
  ));

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final sdk = SensorsWaveSDK();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My App',
      // Add route observer for automatic page tracking
      navigatorObservers: sdk.routeObserver != null ? [sdk.routeObserver!] : [],
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  final sdk = SensorsWaveSDK();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Column(
        children: [
          ElevatedButton(
            onPressed: () {
              // Track custom event
              sdk.trackEvent('ButtonClick', {
                'button_name': 'get_started',
                'page': 'home',
              });
            },
            child: Text('Get Started'),
          ),
          ElevatedButton(
            onPressed: () async {
              // Check feature gate
              final showNewFeature = await sdk.checkFeatureGate('new_dashboard');
              if (showNewFeature) {
                Navigator.push(context, MaterialPageRoute(
                  builder: (_) => NewDashboardPage(),
                ));
              } else {
                Navigator.push(context, MaterialPageRoute(
                  builder: (_) => OldDashboardPage(),
                ));
              }
            },
            child: Text('Go to Dashboard'),
          ),
        ],
      ),
    );
  }
}

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final sdk = SensorsWaveSDK();

  void handleLogin(String email, String password) {
    // Your login logic here
    // ...

    // After successful login, identify the user
    sdk.identify(email);
  }

  @override
  Widget build(BuildContext context) {
    // Your login UI here
    return Container();
  }
}

class ProductPage extends StatelessWidget {
  final sdk = SensorsWaveSDK();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Product')),
      body: ListView(
        children: [
          ElevatedButton(
            onPressed: () {
              // Increment purchase count
              sdk.profileIncrement({'purchase_count': 1});

              // Track purchase event
              sdk.trackEvent('product_purchase', {
                'product_id': '12345',
                'product_name': 'Premium Plan',
                'price': 99.99,
              });
            },
            child: Text('Buy Now'),
          ),
        ],
      ),
    );
  }
}

class SettingsPage extends StatefulWidget {
  @override
  _SettingsPageState createState() => _SettingsPageState();
}

class _SettingsPageState extends State<SettingsPage> {
  final sdk = SensorsWaveSDK();

  void handleLogout() {
    // Track logout event
    sdk.trackEvent('UserLogout');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Settings')),
      body: ListView(
        children: [
          ListTile(
            title: Text('Logout'),
            onTap: handleLogout,
          ),
        ],
      ),
    );
  }
}

License #

Apache-2.0