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

Remote config for Flutter backed by Cloudflare Workers KV: in-app defaults, fetch and activate, typed getters and an offline cache.

example/lib/main.dart

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

// Pass your deployed Worker URL at build time:
//   flutter run --dart-define=CF_CONFIG_ENDPOINT=https://<worker>.<subdomain>.workers.dev \
//               --dart-define=CF_CLIENT_KEY=optional-key
const endpoint = String.fromEnvironment('CF_CONFIG_ENDPOINT');
const clientKey = String.fromEnvironment('CF_CLIENT_KEY');

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  if (endpoint.isEmpty) {
    runApp(const MissingEndpointApp());
    return;
  }

  final remoteConfig = await CloudflareRemoteConfig.initialize(
    endpoint: Uri.parse(endpoint),
    clientKey: clientKey.isEmpty ? null : clientKey,
  );
  await remoteConfig.setConfigSettings(
    RemoteConfigSettings(
      fetchTimeout: const Duration(seconds: 10),
      // Use a long interval (e.g. 1 hour) in production.
      minimumFetchInterval: Duration.zero,
    ),
  );
  await remoteConfig.setDefaults(const {
    'welcome_message': 'Hello from defaults',
    'max_items': 5,
    'discount_ratio': 0.0,
    'new_checkout_enabled': false,
  });

  runApp(const ExampleApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'cloudflare_worker_kv example',
      theme: ThemeData(colorSchemeSeed: const Color(0xFFF38020)),
      home: const ConfigPage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Padding(
            padding: EdgeInsets.all(24),
            child: SelectableText(
              'Run the example with your Worker URL:\n\n'
              'flutter run --dart-define=CF_CONFIG_ENDPOINT='
              'https://<worker>.<subdomain>.workers.dev',
              textAlign: TextAlign.center,
            ),
          ),
        ),
      ),
    );
  }
}

class ConfigPage extends StatefulWidget {
  const ConfigPage({super.key});

  @override
  State<ConfigPage> createState() => _ConfigPageState();
}

class _ConfigPageState extends State<ConfigPage> {
  final _remoteConfig = CloudflareRemoteConfig.instance;
  bool _loading = false;

  @override
  void initState() {
    super.initState();
    _refresh();
  }

  Future<void> _refresh() async {
    setState(() => _loading = true);
    String message;
    try {
      final activated = await _remoteConfig.fetchAndActivate();
      message = activated ? 'New config activated' : 'Config is up to date';
    } on RemoteConfigException catch (e) {
      message = 'Fetch failed: ${e.code}';
    }
    if (!mounted) return;
    setState(() => _loading = false);
    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(SnackBar(content: Text(message)));
  }

  @override
  Widget build(BuildContext context) {
    final values = _remoteConfig.getAll().entries.toList()
      ..sort((a, b) => a.key.compareTo(b.key));
    final checkoutEnabled = _remoteConfig.getBool('new_checkout_enabled');

    return Scaffold(
      appBar: AppBar(title: const Text('Cloudflare Remote Config')),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: _loading ? null : _refresh,
        icon: _loading
            ? const SizedBox.square(
                dimension: 18,
                child: CircularProgressIndicator(strokeWidth: 2),
              )
            : const Icon(Icons.refresh),
        label: const Text('Fetch & activate'),
      ),
      body: ListView(
        padding: const EdgeInsets.fromLTRB(16, 16, 16, 96),
        children: [
          Text(
            _remoteConfig.getString('welcome_message'),
            style: Theme.of(context).textTheme.headlineSmall,
          ),
          const SizedBox(height: 8),
          Text(
            'Max items: ${_remoteConfig.getInt('max_items')} · '
            'Discount: ${(_remoteConfig.getDouble('discount_ratio') * 100).toStringAsFixed(0)}%',
          ),
          const SizedBox(height: 8),
          Chip(
            avatar: Icon(checkoutEnabled ? Icons.check_circle : Icons.cancel),
            label: Text(
              'New checkout ${checkoutEnabled ? 'enabled' : 'disabled'}',
            ),
          ),
          const Divider(height: 32),
          Text('Status: ${_remoteConfig.lastFetchStatus.name}'),
          Text(
            'Last fetch: ${_remoteConfig.lastFetchTime.millisecondsSinceEpoch == 0 ? 'never' : _remoteConfig.lastFetchTime.toLocal()}',
          ),
          Text('Endpoint: $endpoint'),
          const Divider(height: 32),
          for (final entry in values)
            ListTile(
              dense: true,
              contentPadding: EdgeInsets.zero,
              title: Text(entry.key),
              subtitle: Text(entry.value.asString()),
              trailing: Text(entry.value.source.name),
            ),
        ],
      ),
    );
  }
}
0
likes
160
points
--
downloads
screenshot

Documentation

API reference

Publisher

verified publisherhuynq.dev

Weekly Downloads

Remote config for Flutter backed by Cloudflare Workers KV: in-app defaults, fetch and activate, typed getters and an offline cache.

Repository (GitHub)
View/report issues

Topics

#remote-config #cloudflare #feature-flags #configuration

License

MIT (license)

Dependencies

flutter, http, meta, shared_preferences

More

Packages that depend on cloudflare_worker_kv