envified 2.0.4 copy "envified: ^2.0.4" to clipboard
envified: ^2.0.4 copied to clipboard

Runtime environment switching for Flutter. Load .env files, switch dev/staging/prod/custom at runtime, override base URLs, and lock production config — no rebuild required.

example/lib/main.dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:envified/envified.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialise envified before runApp. This loads all .env* asset files,
  // restores the previously selected environment from storage, and sets up
  // the production lock.
  await EnvConfigService.instance.init(
    defaultEnv: Env.dev,
    persistSelection: true,
    allowProdSwitch: false, // prod is locked by default
  );

  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'envified Example',
      debugShowCheckedModeBanner: false,
      // Use builder to inject the debug panel across all routes.
      builder: (context, child) => EnvifiedOverlay(
        service: EnvConfigService.instance,
        gate: const EnvGate(pin: '1234'),
        trigger: const EnvTrigger.tap(count: 2),
        enabled: kDebugMode, // remove the panel in release builds
        child: child ?? const SizedBox.shrink(),
      ),
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF1E88E5),
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      home: const _HomePage(),
    );
  }
}

class _HomePage extends StatelessWidget {
  const _HomePage();

  @override
  Widget build(BuildContext context) {
    final EnvConfigService service = EnvConfigService.instance;

    return Scaffold(
      appBar: AppBar(
        title: const Text('envified Example'),
        centerTitle: true,
      ),
      body: ValueListenableBuilder<EnvConfig>(
        valueListenable: service.current,
        builder: (context, config, _) {
          return SingleChildScrollView(
            padding: const EdgeInsets.all(24),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // ── Active env badge ─────────────────────────────────────
                const _SectionTitle('Active Environment'),
                const SizedBox(height: 8),
                _EnvBadge(env: config.env),

                const SizedBox(height: 24),

                // ── Base URL ─────────────────────────────────────────────
                const _SectionTitle('Base URL'),
                const SizedBox(height: 8),
                _InfoRow(
                  label: 'Current',
                  value: config.baseUrl,
                  highlight: config.isBaseUrlOverridden,
                ),
                if (config.isBaseUrlOverridden)
                  _InfoRow(
                    label: 'From .env',
                    value: config.values['BASE_URL'] ?? '(not set)',
                  ),

                const SizedBox(height: 24),

                // ── Values ───────────────────────────────────────────────
                _SectionTitle('All env values (${config.values.length})'),
                const SizedBox(height: 8),
                ...config.values.entries.map(
                  (e) => _InfoRow(label: e.key, value: e.value),
                ),

                const SizedBox(height: 24),

                // ── Quick switch buttons ──────────────────────────────────
                const _SectionTitle('Quick Switch'),
                const SizedBox(height: 12),
                _EnvSwitcher(service: service),

                const SizedBox(height: 32),

                // ── Tip ──────────────────────────────────────────────────
                Container(
                  padding: const EdgeInsets.all(16),
                  decoration: BoxDecoration(
                    color: Colors.blueGrey.shade900,
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(
                      color: Colors.blueGrey.shade700,
                    ),
                  ),
                  child: const Text(
                    '💡 Double-tap anywhere to open the debug panel.\n'
                    '🔐 PIN is 1234 — or tap 🌿 in the bottom-right corner.',
                    style: TextStyle(fontSize: 13, height: 1.6),
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// Helper widgets
// ─────────────────────────────────────────────────────────────────────────────

class _SectionTitle extends StatelessWidget {
  final String text;
  const _SectionTitle(this.text);

  @override
  Widget build(BuildContext context) {
    return Text(
      text,
      style: Theme.of(context).textTheme.titleSmall?.copyWith(
            color: Colors.blueGrey.shade400,
            fontWeight: FontWeight.w600,
            letterSpacing: 0.6,
          ),
    );
  }
}

class _EnvBadge extends StatelessWidget {
  final Env env;
  const _EnvBadge({required this.env});

  Color _color() {
    switch (env) {
      case Env.dev:
        return Colors.blue.shade400;
      case Env.staging:
        return Colors.orange.shade400;
      case Env.prod:
        return Colors.red.shade400;
      case Env.custom:
        return Colors.purple.shade400;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      decoration: BoxDecoration(
        // ignore: deprecated_member_use
        color: _color().withOpacity(0.15),
        borderRadius: BorderRadius.circular(24),
        border: Border.all(color: _color(), width: 1.5),
      ),
      child: Text(
        env.label,
        style: TextStyle(
          color: _color(),
          fontWeight: FontWeight.bold,
          fontSize: 16,
        ),
      ),
    );
  }
}

class _InfoRow extends StatelessWidget {
  final String label;
  final String value;
  final bool highlight;
  const _InfoRow({
    required this.label,
    required this.value,
    this.highlight = false,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 3),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            width: 120,
            child: Text(
              label,
              style: const TextStyle(
                fontSize: 12,
                fontWeight: FontWeight.w600,
                fontFamily: 'monospace',
                color: Colors.blueGrey,
              ),
            ),
          ),
          Expanded(
            child: Text(
              value,
              style: TextStyle(
                fontSize: 12,
                fontFamily: 'monospace',
                color: highlight ? Colors.amber.shade400 : null,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _EnvSwitcher extends StatelessWidget {
  final EnvConfigService service;
  const _EnvSwitcher({required this.service});

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<EnvConfig>(
      valueListenable: service.current,
      builder: (context, config, _) {
        final bool locked = service.isProdLocked;

        return Wrap(
          spacing: 8,
          runSpacing: 8,
          children: Env.values.map((env) {
            final bool isActive = config.env == env;
            return Tooltip(
              message: locked && env != Env.prod ? 'Locked in production' : '',
              child: FilledButton(
                onPressed: (locked && env != Env.prod)
                    ? null
                    : () async {
                        try {
                          await service.switchTo(env);
                        } on EnvifiedLockException catch (e) {
                          if (context.mounted) {
                            ScaffoldMessenger.of(context).showSnackBar(
                              SnackBar(content: Text(e.message)),
                            );
                          }
                        }
                      },
                style: FilledButton.styleFrom(
                  backgroundColor: isActive ? null : Colors.blueGrey.shade800,
                ),
                child: Text(env.label),
              ),
            );
          }).toList(),
        );
      },
    );
  }
}
0
likes
0
points
0
downloads

Publisher

verified publisherappamania.in

Weekly Downloads

Runtime environment switching for Flutter. Load .env files, switch dev/staging/prod/custom at runtime, override base URLs, and lock production config — no rebuild required.

Homepage
Repository (GitHub)
View/report issues

Topics

#environment #configuration #env #debugging

Funding

Consider supporting this project:

paywithchai.in

License

unknown (license)

Dependencies

crypto, flutter, flutter_secure_storage, local_auth, sensors_plus

More

Packages that depend on envified