Skip to main content

ctrlapp 0.1.4 copy "ctrlapp: ^0.1.4" to clipboard
ctrlapp: ^0.1.4 copied to clipboard

Remote kill-switch, feature flags, forced update, forms and inbox for Flutter, plus diagnostics, device-integrity checks, push permission and hosted login.

example/lib/main.dart

// A small, complete example of the `ctrlapp` package.
//
// It connects to CtrlApp, then shows the things you will use most:
//   * the kill-switch (block the whole app from the dashboard)
//   * a feature flag, remote config and remote text
//   * an experiment (A/B test)
//   * event tracking
// and it updates live when you change something in the dashboard.
//
// Create an app at https://ctrlapp.dev, copy its API key, then run:
//
//   flutter run --dart-define=CTRLAPP_API_KEY=ck_sdk_YOUR_KEY

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

const _apiKey = String.fromEnvironment('CTRLAPP_API_KEY');

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // One call at startup. After this, CtrlApp.instance works anywhere.
  if (_apiKey.isNotEmpty) {
    await CtrlApp.init(apiKey: _apiKey);
  }

  runApp(const ExampleApp());
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'CtrlApp example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        useMaterial3: true,
      ),
      home: _apiKey.isEmpty ? const MissingKeyScreen() : const KillSwitchGate(),
    );
  }
}

/// Shown until you run the example with your own API key.
class MissingKeyScreen extends StatelessWidget {
  const MissingKeyScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Padding(
          padding: EdgeInsets.all(24),
          child: Text(
            'Add your API key to try this example:\n\n'
            'flutter run --dart-define=CTRLAPP_API_KEY=ck_sdk_...\n\n'
            'Create an app and copy its key at https://ctrlapp.dev',
            textAlign: TextAlign.center,
          ),
        ),
      ),
    );
  }
}

/// The kill-switch. When you block the app in the dashboard, every running
/// copy of your app switches to this screen within about a minute, and it
/// keeps working offline because the decision is cached on the device.
class KillSwitchGate extends StatelessWidget {
  const KillSwitchGate({super.key});

  @override
  Widget build(BuildContext context) {
    final ctrl = CtrlApp.instance;

    return StreamBuilder<CtrlAppBlock>(
      stream: ctrl.blockChanges,
      builder: (context, _) {
        if (ctrl.isBlocked) {
          return BlockedScreen(reason: ctrl.blockReason());
        }
        return const HomeScreen();
      },
    );
  }
}

class BlockedScreen extends StatelessWidget {
  const BlockedScreen({super.key, this.reason});

  final String? reason;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const Icon(Icons.construction, size: 64),
              const SizedBox(height: 16),
              Text(
                'Temporarily unavailable',
                style: Theme.of(context).textTheme.headlineSmall,
              ),
              const SizedBox(height: 8),
              Text(reason ?? 'Please try again later.', textAlign: TextAlign.center),
            ],
          ),
        ),
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final ctrl = CtrlApp.instance;

    // Rebuild whenever a control changes in the dashboard.
    return StreamBuilder<Map<String, dynamic>>(
      stream: ctrl.controlChanges,
      builder: (context, _) {
        // Remote text, with a default used until the dashboard sets one.
        final title = ctrl.text('home.title', fallback: 'Hello from CtrlApp');

        // Remote config: a number you can change without a release.
        final itemCount = ctrl.configValue<int>('home.items', defaultValue: 3);

        return Scaffold(
          appBar: AppBar(title: Text(title)),
          body: ListView(
            padding: const EdgeInsets.all(16),
            children: [
              // Feature flag: show or hide this card from the dashboard.
              if (ctrl.boolFlag('home.banner'))
                const Card(
                  child: ListTile(
                    leading: Icon(Icons.campaign),
                    title: Text('New feature!'),
                    subtitle: Text('Switched on remotely with a feature flag.'),
                  ),
                ),
              for (var i = 1; i <= itemCount; i++)
                ListTile(
                  leading: const Icon(Icons.check_circle_outline),
                  title: Text('Item $i'),
                  subtitle: const Text('The number of items is remote config.'),
                ),
              const SizedBox(height: 24),
              const ExperimentButton(),
            ],
          ),
        );
      },
    );
  }
}

/// An experiment: CtrlApp decides which variant this user sees, and you
/// compare results in the dashboard.
class ExperimentButton extends StatelessWidget {
  const ExperimentButton({super.key});

  @override
  Widget build(BuildContext context) {
    final ctrl = CtrlApp.instance;

    return FutureBuilder<String>(
      future: ctrl.treatment('home.cta', defaultValue: 'control'),
      builder: (context, snapshot) {
        final variant = snapshot.data ?? 'control';
        final label = variant == 'b' ? 'Get started now' : 'Get started';

        return FilledButton(
          onPressed: () {
            // Record what people do so you can see it in the dashboard.
            ctrl.track('cta_tapped', props: {'variant': variant});
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Tracked "cta_tapped" (variant: $variant)')),
            );
          },
          child: Text(label),
        );
      },
    );
  }
}
1
likes
130
points
448
downloads

Documentation

Documentation
API reference

Publisher

verified publisherctrlapp.dev

Weekly Downloads

Remote kill-switch, feature flags, forced update, forms and inbox for Flutter, plus diagnostics, device-integrity checks, push permission and hosted login.

Homepage

Topics

#feature-flags #kill-switch #remote-config #forced-update #screen-capture

License

MIT (license)

Dependencies

device_info_plus, flutter, http, package_info_plus, shared_preferences, url_launcher, video_player

More

Packages that depend on ctrlapp

Packages that implement ctrlapp