Mobilev1.2.1

Flutter SDK

Mobile feature flags for Flutter and Dart apps with built-in circuit breaker, caching, and cross-platform support. Pure Dart — works on iOS, Android, Web, and Desktop.

ðŸ›Ąïļ

Circuit Breaker

Auto-recovery

🌍

Cross-Platform

iOS, Android, Web, Desktop

ðŸ’ū

Caching

In-memory cache

ðŸŠķ

Lightweight

13 KB pure Dart

Installation

Add rollgate to your pubspec.yaml:

pubspec.yaml
dependencies:
  rollgate: ^1.2.1

Then run:

flutter pub get
# or
dart pub get

Quick Start

lib/main.dart
import 'package:rollgate/rollgate.dart';

final client = RollgateClient(
  config: RollgateConfig(
    apiKey: 'rg_client_...',
    baseUrl: 'https://api.rollgate.io',
    refreshInterval: Duration(seconds: 30),
  ),
);

await client.init();

// Check flags
final enabled = client.isEnabled('my-feature', false);

// Identify user
await client.identify(UserContext(
  id: 'user-123',
  email: '[email protected]',
  attributes: {'plan': 'pro'},
));

// Cleanup
await client.close();

Configuration

OptionTypeDefaultDescription
apiKeyStringrequiredClient API key (rg_client_...)
baseUrlString'https://api.rollgate.io'API base URL
refreshIntervalDurationDuration(seconds: 30)Polling interval, Duration.zero to disable
timeoutDurationDuration(seconds: 5)HTTP request timeout
maxRetriesint3Maximum retry attempts on failure
cacheTtlDurationDuration(minutes: 5)Cache time-to-live

Note: The Flutter SDK uses polling only (no SSE). This is intentional for mobile environments where persistent connections drain battery and are unreliable on cellular networks.

Methods

init()

Initialize the client and fetch flags from the server. Call this once at app startup.

await client.init();

close()

Stop polling and clean up resources. Call this when the app is disposed.

await client.close();

isEnabled(key, defaultValue)

Check if a boolean flag is enabled. Returns the flag value or the default.

final showFeature = client.isEnabled('new-feature', false);

if (showFeature) {
  // New feature code
}

getValue<T>(key, defaultValue)

Get a flag value of any type. Use for string, number, or JSON flags.

// String flag
final theme = client.getValue<String>('theme', 'light');

// Number flag
final maxRetries = client.getValue<int>('max-retries', 3);

// Dynamic flag
final config = client.getValue<Map<String, dynamic>>(
  'rate-limit-config',
  {'enabled': false, 'limit': 100},
);

getString(key, defaultValue)

Type-safe helper for string flags.

final buttonColor = client.getString('cta-color', 'blue');
final variant = client.getString('checkout-variant', 'control');

getNumber(key, defaultValue)

Type-safe helper for number flags.

final pageSize = client.getNumber('results-per-page', 20);
final timeout = client.getNumber('api-timeout-ms', 5000);

getJson(key, defaultValue)

Type-safe helper for JSON flags. Returns a Map<String, dynamic>.

final config = client.getJson('feature-config', {
  'enabled': false,
  'maxItems': 10,
  'allowedRoles': ['admin'],
});

if (config['enabled'] == true) {
  // Feature logic
}

getAllFlags()

Get all flags as a map.

final flags = client.getAllFlags();
// {'feature-a': true, 'feature-b': false}

identify(userContext)

Set user context for targeting rules. Re-fetches flags with the new context.

await client.identify(UserContext(
  id: 'user-123',
  email: '[email protected]',
  attributes: {
    'plan': 'enterprise',
    'country': 'US',
    'app_version': '2.1.0',
  },
));

refresh()

Force refresh flags from the server.

await client.refresh();

Flutter Widget Integration

Use RollgateClient directly in your Flutter widgets. Initialize in initState and dispose in dispose.

StatefulWidget pattern
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late RollgateClient _rollgate;

  @override
  void initState() {
    super.initState();
    _rollgate = RollgateClient(
      config: RollgateConfig(apiKey: 'rg_client_...'),
    );
    _rollgate.init();
  }

  @override
  void dispose() {
    _rollgate.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: _rollgate.isEnabled('new-ui', false)
            ? NewUI()
            : LegacyUI(),
      ),
    );
  }
}

FutureBuilder Pattern

Wait for initialization before rendering flag-dependent UI:

FutureBuilder
class FeatureScreen extends StatelessWidget {
  final RollgateClient rollgate;

  const FeatureScreen({required this.rollgate});

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: rollgate.init(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const CircularProgressIndicator();
        }

        if (snapshot.hasError) {
          // Rollgate unavailable - show default UI
          return const DefaultFeatureUI();
        }

        final showNewFeature = rollgate.isEnabled('new-feature', false);
        return showNewFeature
            ? const NewFeatureUI()
            : const DefaultFeatureUI();
      },
    );
  }
}

StreamBuilder Pattern

React to flag changes from polling updates using a stream:

StreamBuilder
class LiveFeature extends StatelessWidget {
  final RollgateClient rollgate;

  const LiveFeature({required this.rollgate});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<Map<String, dynamic>>(
      stream: rollgate.flagsStream,
      builder: (context, snapshot) {
        final flags = snapshot.data ?? {};
        final enabled = flags['premium-feature'] == true;

        return AnimatedSwitcher(
          duration: const Duration(milliseconds: 300),
          child: enabled
              ? const PremiumWidget(key: ValueKey('premium'))
              : const StandardWidget(key: ValueKey('standard')),
        );
      },
    );
  }
}

User Targeting

User context enables server-side targeting rule evaluation. Set the user after authentication to receive personalized flag values.

User targeting in Flutter
// After login
Future<void> onUserLogin(User user) async {
  await rollgate.identify(UserContext(
    id: user.id,
    email: user.email,
    attributes: {
      'plan': user.subscription.plan,
      'country': user.country,
      'app_version': packageInfo.version,
      'platform': Platform.operatingSystem,
    },
  ));
}

// After logout - reset to anonymous
Future<void> onUserLogout() async {
  await rollgate.identify(UserContext(id: 'anonymous'));
}

Available operators: equals, not_equals, contains, starts_with, ends_with, in, not_in, gt, gte, lt, lte, regex, is_set, is_not_set, semver_gt, semver_lt, semver_eq

Error Handling & Graceful Degradation

The SDK is designed to never throw exceptions during flag evaluation. If Rollgate is unavailable, flags return their default values. The circuit breaker automatically switches to cached values when the API is unreachable.

Graceful degradation
// Default values ensure your app works even without Rollgate
final showNewFeature = client.isEnabled('new-feature', false);
// Returns false if: flag doesn't exist, API down, circuit open

// Check circuit state for monitoring
final state = client.getCircuitState();
// 'closed' | 'open' | 'half_open'

// Safe initialization with error handling
Future<void> initFlags() async {
  try {
    await client.init();
    debugPrint('Rollgate initialized with ${client.getAllFlags().length} flags');
  } catch (e) {
    // Log but don't crash - SDK will use defaults
    debugPrint('Rollgate init failed: $e');
    debugPrint('Continuing with default flag values');
  }
}

Retry & Circuit Breaker

The SDK includes automatic retry with exponential backoff and a circuit breaker for resilience on unreliable mobile networks:

// Circuit breaker states:
// - closed:    Normal operation, requests go through
// - open:      API unreachable, using cached flags
// - half_open: Testing if API is back

// The SDK handles this automatically:
// 1. Request fails -> retry with backoff (up to maxRetries)
// 2. Consecutive failures -> circuit opens
// 3. After cooldown -> circuit half-opens, tries one request
// 4. Success -> circuit closes, normal operation resumes