arcane_framework 2.0.2 copy "arcane_framework: ^2.0.2" to clipboard
arcane_framework: ^2.0.2 copied to clipboard

Agnostic Reusable Component Architecture for New Ecosystems: a modern framework for bootstrapping new applications

example/lib/main.dart

import "dart:async";

import "package:arcane_framework/arcane_framework.dart";
import "package:example/config.dart";
import "package:example/interfaces/debug_auth_interface.dart";
import "package:example/interfaces/debug_print_interface.dart";
import "package:example/services/favorite_color_service.dart";
import "package:example/theme/theme.dart";
import "package:flutter/foundation.dart";
import "package:flutter/material.dart";
import "package:flutter/services.dart";

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

  if (kIsWeb) {
    // Work around a Flutter web debug assertion where legacy raw key messages
    // can arrive before key data and force an incompatible transit mode.
    SystemChannels.keyEvent.setMessageHandler(
      (_) async => <String, dynamic>{"handled": false},
    );
  }

  final DebugPrint debugPrintInterface = DebugPrint();
  final DebugAuthInterface debugAuthInterface = DebugAuthInterface();

  // If any Feature enum items are `enabledAtStartup`, enable them within Arcane.
  for (final Feature feature in Feature.values) {
    if (feature.enabledAtStartup) Arcane.features.enableFeature(feature);
  }

  // Register the logging interface
  await Arcane.logger.registerInterface(
    debugPrintInterface,
    interceptors: [
      LogInterceptor((event, context) {
        if (context.interface is DebugPrint && Feature.logging.disabled) {
          return null;
        }

        return event;
      }),
    ],
  );

  // Register the authentication interface
  await Arcane.auth.registerInterface(debugAuthInterface);

  // Set the light and dark mode themes using our pre-defined ThemeData classes
  Arcane.theme
    ..setLightTheme(lightTheme)
    ..setDarkTheme(darkTheme);

  // Log a message that the app has been initialized
  Arcane.log(
    "Initialization complete.",
    // Set an appropriate log level
    level: Level.info,
    // The `module` and `method` are _often_ automatically determined, but they can be overridden.
    module: "main",
    method: "main",
    // Skip autodetction of the `module`, `method`, and file/line number where logs originated from.
    skipAutodetection: true,
    // Add some optional metadata to be included in this single log message. This is added to the
    // persistent metadata, if any has been set.
    metadata: {
      "ready": "true",
    },
  );

  runApp(
    // The `ArcaneApp` widget is optional but provides Arcane's built-in
    // service, feature flag, environment, and theme integration widgets.
    // It also performs initial platform theme synchronization automatically
    // via ArcaneThemeSwitcher.
    ArcaneApp(
      builder: (context, _) => const MainApp(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: Arcane.theme.light,
      darkTheme: Arcane.theme.dark,
      themeMode: Arcane.theme.currentModeOf(context),
      home: Scaffold(
        appBar: AppBar(
          title: const Text("Arcane Framework Example"),
        ),
        body: Column(
          children: [
            Expanded(
              child: GridView.extent(
                maxCrossAxisExtent: 300,
                padding: const EdgeInsets.all(16),
                children: const [
                  ArcaneThemeExample(),
                  ArcaneAuthExample(),
                  ArcaneFeatureFlagsExample(),
                  ArcaneEnvironmentExample(),
                  ArcaneServicesExample(),
                ],
              ),
            ),
            const ArcaneLoggingExample(),
          ],
        ),
      ),
    );
  }
}

// * Logging
// Arcane's logging system gives developers the power to dynamically add and
// remove logging interfaces on-the-fly: try enabling a debug logging interface
// when the app is running in debug mode, adding a third-party logging interface
// when in production, and waiting until after the user has gone through the
// login process to ask them for permission to track. Include useful metadata,
// including persistent metadata, in your log messages. All of these things, and
// more, are possible when using Arcane's logging system.
class ArcaneLoggingExample extends StatefulWidget {
  const ArcaneLoggingExample({
    super.key,
  });

  @override
  State<ArcaneLoggingExample> createState() => _ArcaneLoggingExampleState();
}

class _ArcaneLoggingExampleState extends State<ArcaneLoggingExample> {
  static const String _demoMetadataKey = "demo";
  static const String _demoMetadataValue =
      "This message will be included in all log messages.";

  // Set up a subscriber that we can use to listen to logs in realtime.
  // Note: this is completely optional and does _not_ impact whether logs are
  // sent to any registered logging interfaces.
  late final StreamSubscription<String> _logStreamSubscriber;

  // Used to collect the logs from the stream.
  final List<String> latestLogs = [];
  bool _persistentMetadataEnabled = false;

  @override
  void initState() {
    super.initState();
    // Listens to the Arcane logger stream of logs and adds them to the latestLogs list.
    _logStreamSubscriber = Arcane.logger.logStream.listen((message) {
      // If [Feature.logging] is disabled, we won't add the logs to the list or trigger
      // a rebuild.
      if (Feature.logging.enabled) {
        setState(() {
          latestLogs.insert(0, message);
        });
      }
    });
  }

  @override
  void dispose() {
    // Don't forget to properly dispose of the subscriber
    unawaited(_logStreamSubscriber.cancel());
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
      valueListenable: Arcane.features.notifier,
      builder: (context, enabledFeatures, _) {
        return Padding(
          padding: const EdgeInsets.all(16.0),
          child: Card(
            child: SizedBox(
              height: MediaQuery.sizeOf(context).height / 2,
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      "Logging",
                      style: Theme.of(context).textTheme.headlineSmall,
                    ),
                    Row(
                      spacing: 8,
                      children: [
                        const Text("Include persistent demo metadata"),
                        Switch(
                          value: _persistentMetadataEnabled,
                          onChanged: Feature.logging.disabled
                              ? null
                              : (enabled) {
                                  setState(() {
                                    _persistentMetadataEnabled = enabled;
                                  });

                                  if (enabled) {
                                    Arcane.logger.addPersistentMetadata({
                                      _demoMetadataKey: _demoMetadataValue,
                                    });
                                  } else {
                                    Arcane.logger.removePersistentMetadata(
                                      _demoMetadataKey,
                                    );
                                  }
                                },
                        ),
                      ],
                    ),
                    if (latestLogs.isEmpty)
                      Text(
                        "Log messages will appear here",
                        style: Theme.of(context).textTheme.labelSmall?.copyWith(
                              fontStyle: FontStyle.italic,
                            ),
                      ),
                    if (Feature.logging.disabled)
                      Text(
                        "Logging feature is disabled.",
                        style: Theme.of(context).textTheme.labelSmall?.copyWith(
                              fontWeight: FontWeight.bold,
                            ),
                      ),
                    Expanded(
                      child: ListView.builder(
                        itemCount: latestLogs.length,
                        itemBuilder: (context, index) {
                          return Text(latestLogs[index]);
                        },
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      },
    );
  }
}

// * Authentication
// Arcane's authentication system provides a simple, standard interface
// for common authentication tasks - including registration and account
// management, logging in and out, etc. Authentication status is reflected
// in realtime within the application as changes happen, so you can focus
// on what's most important.
class ArcaneAuthExample extends StatelessWidget {
  const ArcaneAuthExample({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
      valueListenable: Arcane.features.notifier,
      builder: (context, enabledFeatures, _) {
        return Card(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: ValueListenableBuilder(
              valueListenable: Arcane.auth.isSignedIn,
              builder: (context, isSignedIn, _) {
                return Column(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    Text(
                      "Authentication",
                      style: Theme.of(context).textTheme.headlineSmall,
                    ),
                    ElevatedButton(
                      onPressed: Feature.authentication.enabled
                          ? () async {
                              if (isSignedIn) {
                                await Arcane.auth.logOut();
                              } else {
                                await Arcane.auth.login<Credentials>(
                                  input: (
                                    email: "email",
                                    password: "password",
                                  ),
                                );
                              }
                            }
                          : null,
                      child: Text(
                        isSignedIn ? "Sign out" : "Sign in",
                      ),
                    ),
                    Center(
                      child: Text("Status: ${Arcane.auth.status.name}"),
                    ),
                  ],
                );
              },
            ),
          ),
        );
      },
    );
  }
}

// * Theme
// at any time between light mode and dark mode, or set to follow the
// system theme. In addition, themes can be swapped out on-the-fly,
// enabling dynamic customizations and remote theme fetching.
class ArcaneThemeExample extends StatelessWidget {
  const ArcaneThemeExample({
    super.key,
  });

  static final Listenable themeListenable =
      Listenable.merge([Arcane.theme.darkTheme, Arcane.theme.lightTheme]);

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(
              "Theme",
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            Column(
              children: [
                Switch(
                  value: context.isDarkMode,
                  thumbIcon: WidgetStateProperty.resolveWith((states) {
                    if (states.contains(WidgetState.selected)) {
                      return const Icon(Icons.dark_mode);
                    }
                    return const Icon(Icons.light_mode);
                  }),
                  onChanged: (_) {
                    // Always disable system mode and flip to the opposite of the effective mode
                    Arcane.theme.switchTheme(
                      themeMode:
                          context.isDarkMode ? ThemeMode.light : ThemeMode.dark,
                    );
                    Arcane.log(
                      "Switching theme",
                      metadata: {
                        "followingSystemTheme":
                            "${Arcane.theme.isFollowingSystemTheme}",
                        "newMode": Arcane.theme.currentThemeMode.name,
                      },
                    );
                  },
                ),
                Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Checkbox(
                      value: Arcane.theme.isFollowingSystemTheme,
                      onChanged: (value) {
                        if (value == true) {
                          Arcane.theme.followSystemTheme(context);
                        } else {
                          // When unchecking, set mode to the effective mode (not system)
                          final ThemeMode effective =
                              Theme.of(context).brightness == Brightness.dark
                                  ? ThemeMode.dark
                                  : ThemeMode.light;
                          Arcane.theme.switchTheme(themeMode: effective);
                        }
                        Arcane.log(
                          "Switching theme",
                          metadata: {
                            "followingSystemTheme":
                                "${Arcane.theme.isFollowingSystemTheme}",
                            "newMode": Arcane.theme.currentThemeMode.name,
                          },
                        );
                      },
                    ),
                    const Text("Follow system"),
                  ],
                ),
              ],
            ),
            Text(
              "The current theme mode is ${Arcane.theme.currentModeOf(context).name} and "
              "is ${Arcane.theme.isFollowingSystemTheme ? "" : "not "}"
              "following the system theme.",
            ),
          ],
        ),
      ),
    );
  }
}

// * Feature flags
// Arcane's feature flag system is extremely simple and flexible to use.
// By registering _any_ enum (or even multiple enums!), features can be
// toggled on and off at any point. The feature flag system even offers
// a notifier, stream, and app-level scope so you can react to changes as
// they happen. Fetch your remote config and use it to dynamically enable
// and disable features with ease!
class ArcaneFeatureFlagsExample extends StatelessWidget {
  const ArcaneFeatureFlagsExample({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final ArcaneFeatureFlagProvider flags = context.featureFlags;

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(
              "Feature Flags",
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            Expanded(
              child: ListView.builder(
                itemCount: Feature.values.length,
                itemBuilder: (context, i) {
                  final Feature feature = Feature.values[i];
                  return Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Text(feature.name),
                      Switch(
                        value: flags.isEnabled(feature),
                        onChanged: (_) {
                          flags.isEnabled(feature)
                              ? flags.disableFeature(feature)
                              : flags.enableFeature(feature);
                        },
                      ),
                    ],
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// * Environment
// Quickly and easily toggle between a "normal" and "debug" environment
// within your application. This is particularly useful during development
// when you may want to change the behavior of the application under
// certain conditions.
class ArcaneEnvironmentExample extends StatelessWidget {
  static const Environment stagingEnvironment = Environment("staging");

  const ArcaneEnvironmentExample({
    super.key,
  });

  Environment _nextEnvironment(Environment current) {
    if (current == Environment.normal) return Environment.debug;
    if (current == Environment.debug) return stagingEnvironment;
    return Environment.normal;
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(
              "Environment",
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            ElevatedButton(
              onPressed: () {
                final ArcaneEnvironmentService environment = Arcane.environment;
                final Environment previousEnvironment = environment.current;
                final Environment nextEnvironment = _nextEnvironment(
                  previousEnvironment,
                );

                environment.setEnvironment(nextEnvironment);

                Arcane.log(
                  "Environment changed.",
                  metadata: {
                    "previous": previousEnvironment.name,
                    "current": nextEnvironment.name,
                  },
                );
              },
              child: const Text("Cycle environment"),
            ),
            ValueListenableBuilder<Environment>(
              valueListenable: Arcane.environment.notifier,
              builder: (context, environment, _) {
                return Text(
                  "Environment: ${environment.name}",
                  textAlign: TextAlign.center,
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

/// * Services
/// Arcane's services system is flexible and minimal, leaving the power
/// and control in developers' hands. This system powers much of Arcane
/// internally, so you know it's reliable.
class ArcaneServicesExample extends StatelessWidget {
  const ArcaneServicesExample({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final ArcaneServiceProvider serviceProvider = ArcaneServiceProvider.of(
      context,
    );

    return ValueListenableBuilder<List<ArcaneService>>(
      valueListenable: serviceProvider.notifier!,
      builder: (context, _, __) {
        final FavoriteColorService? service =
            ArcaneServiceProvider.serviceOfType<FavoriteColorService>(context);

        Widget buildCard(MaterialColor? color) {
          return Card(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Text(
                    "Services",
                    style: Theme.of(context).textTheme.headlineSmall,
                  ),
                  Text(
                    color != null ? "Favorite color: ${color.name}" : "",
                  ),
                  ElevatedButton(
                    onPressed: () {
                      if (service == null) {
                        final FavoriteColorService nextService =
                            FavoriteColorService()
                              ..syncFromCurrentTheme(colors);

                        serviceProvider.addService(
                          nextService,
                        );

                        Arcane.log(
                          "Service registered.",
                          metadata: {"service": "FavoriteColorService"},
                        );
                      } else {
                        serviceProvider.removeService<FavoriteColorService>();

                        Arcane.log(
                          "Service removed.",
                          metadata: {"service": "FavoriteColorService"},
                        );
                      }
                    },
                    child: Text(
                      '${service == null ? 'Register' : 'Remove'} service',
                    ),
                  ),
                  SizedBox(
                    height: 20,
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      spacing: 8,
                      children: [
                        const Text("Color"),
                        Expanded(
                          child: ListView.separated(
                            itemCount: colors.length,
                            scrollDirection: Axis.horizontal,
                            separatorBuilder: (_, __) =>
                                const SizedBox(width: 4),
                            itemBuilder: (context, index) {
                              return Opacity(
                                opacity: service == null ? 0.4 : 1,
                                child: InkWell(
                                  onTap: service == null
                                      ? null
                                      : () {
                                          service.setMyFavoriteColor(
                                            colors[index],
                                          );
                                          Arcane.log(
                                            "Set a color in FavoriteColorService",
                                            metadata: {
                                              "color": colors[index].name ??
                                                  "Unknown",
                                            },
                                          );
                                        },
                                  child: Container(
                                    decoration: BoxDecoration(
                                      color: colors[index],
                                      border: color == colors[index]
                                          ? Border.all(width: 2)
                                          : null,
                                    ),
                                    width: 20,
                                    height: 20,
                                  ),
                                ),
                              );
                            },
                          ),
                        ),
                      ],
                    ),
                  ),
                  Text(
                    "Service is ${service != null ? "" : "not "}registered",
                  ),
                ],
              ),
            ),
          );
        }

        if (service == null) return buildCard(null);

        return ValueListenableBuilder<MaterialColor?>(
          valueListenable: service.notifier,
          builder: (context, color, _) => buildCard(color),
        );
      },
    );
  }
}
1
likes
0
points
663
downloads

Publisher

verified publisherhanskokx.com

Weekly Downloads

Agnostic Reusable Component Architecture for New Ecosystems: a modern framework for bootstrapping new applications

Repository (GitHub)
View/report issues

Topics

#arcane-framework

License

unknown (license)

Dependencies

arcane_helper_utils, collection, flutter, result_monad

More

Packages that depend on arcane_framework