flutter_screen_controller 0.0.6 copy "flutter_screen_controller: ^0.0.6" to clipboard
flutter_screen_controller: ^0.0.6 copied to clipboard

A view-controller pattern implementation to separate UI from logic

example/lib/main.dart

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

void main() {
  runApp(const MyApp());
}

/// The Widget that configures your application.
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      restorationScopeId: 'app',
      onGenerateRoute: (RouteSettings routeSettings) {
        return MaterialPageRoute<void>(
          settings: routeSettings,
          builder: (BuildContext context) {
            switch (routeSettings.name) {
              case SettingsScreen.routeName:
                return const SettingsScreen();
              case SampleItemDetailsScreen.routeName:
                return SampleItemDetailsScreen(
                  id: routeSettings.arguments as int,
                );
              case SampleItemListScreen.routeName:
              default:
                return const SampleItemListScreen();
            }
          },
        );
      },
    );
  }
}

/// A placeholder class that represents an entity or model.
class SampleItem {
  const SampleItem(this.id);

  final int id;
}

/// Displays detailed information about a SampleItem.
class SampleItemDetailsScreen extends StatelessWidget {
  const SampleItemDetailsScreen({required this.id, super.key});

  final int id;

  static const routeName = '/sample_item';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Item Details'),
      ),
      body: ScreenControllerBuilder(
        create: (state) => _SampleItemDetailsScreenController(state, id),
        builder: (context, ctrl) {
          if (ctrl.isLoading) {
            return const Center(child: CircularProgressIndicator());
          }

          if (ctrl.hasError) {
            return Text(
              'An error occur try again later: ${ctrl.error.toString()}',
            );
          }

          // At this stage, data is not null
          final item = ctrl.data!;

          return Center(
            child: Text(
              'More Information Here About Sample ID: ${item.id}',
            ),
          );
        },
      ),
    );
  }
}

class _SampleItemDetailsScreenController extends ScreenController
    with DataScreenController<SampleItem> {
  _SampleItemDetailsScreenController(super.state, this.id);

  final int id;

  @override
  Future<SampleItem> fetchData() {
    // Use your own logic here to get detail
    return Future.delayed(const Duration(seconds: 2), () => SampleItem(id));
  }
}

/// A class that many Widgets can interact with to read user settings, update
/// user settings, or listen to user settings changes.
///
/// Controllers glue Data Services to Flutter Widgets. The SettingsController
/// uses the SettingsService to store and retrieve user settings.
class SettingsController extends ScreenController {
  SettingsController(super.state);

  // Make SettingsService a private variable so it is not used directly.
  late final SettingsService _settingsService;

  // Make ThemeMode a private variable so it is not updated directly without
  // also persisting the changes with the SettingsService.
  late ThemeMode _themeMode;

  // Allow Widgets to read the user's preferred ThemeMode.
  ThemeMode get themeMode => _themeMode;

  // In this method perform early initializations that do not need UI update
  @override
  void onInit() {
    _settingsService = SettingsService();
  }

  // Perform here initializations that will update the UI
  @override
  void onReady() {
    _loadSettings();
  }

  /// Load the user's settings from the SettingsService. It may load from a
  /// local database or the internet. The controller only knows it can load the
  /// settings from the service.
  Future<void> _loadSettings() async {
    _themeMode = await _settingsService.themeMode();

    // Important! Inform screen a change has occurred.
    refreshUI();
  }

  /// Update and persist the ThemeMode based on the user's selection.
  Future<void> updateThemeMode(ThemeMode? newThemeMode) async {
    if (newThemeMode == null) return;

    // Do not perform any work if new and old ThemeMode are identical
    if (newThemeMode == _themeMode) return;

    // Otherwise, store the new ThemeMode in memory
    _themeMode = newThemeMode;

    // Important! Inform screen a change has occurred.
    refreshUI();

    // Persist the changes to a local database or the internet using the
    // SettingService.
    await _settingsService.updateThemeMode(newThemeMode);
  }
}

/// Displays the various settings that can be customized by the user.
///
/// When a user changes a setting, the SettingsController is updated and
/// Widgets that listen to the SettingsController are rebuilt.
class SettingsScreen extends StatelessWidget {
  const SettingsScreen({super.key});

  static const routeName = '/settings';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Settings')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        // Glue the SettingsController to the theme selection DropdownButton.
        //
        // When a user selects a theme from the dropdown list, the
        // SettingsController is updated, which rebuilds the portion of screen wrapped by the controller.
        child: ScreenControllerBuilder(
          create: SettingsController.new,
          builder: (context, controller) => DropdownButton<ThemeMode>(
            // Read the selected themeMode from the controller
            value: controller.themeMode,
            // Call the updateThemeMode method any time the user selects a theme.
            onChanged: controller.updateThemeMode,
            items: const [
              DropdownMenuItem(
                value: ThemeMode.system,
                child: Text('System Theme'),
              ),
              DropdownMenuItem(
                value: ThemeMode.light,
                child: Text('Light Theme'),
              ),
              DropdownMenuItem(
                value: ThemeMode.dark,
                child: Text('Dark Theme'),
              )
            ],
          ),
        ),
      ),
    );
  }
}

/// A service that stores and retrieves user settings.
///
/// By default, this class does not persist user settings. If you'd like to
/// persist the user settings locally, use the shared_preferences package. If
/// you'd like to store settings on a web server, use the http package.
class SettingsService {
  /// Loads the User's preferred ThemeMode from local or remote storage.
  Future<ThemeMode> themeMode() async => ThemeMode.system;

  /// Persists the user's preferred ThemeMode to local or remote storage.
  Future<void> updateThemeMode(ThemeMode theme) async {
    // Use the shared_preferences package to persist settings locally or the
    // http package to persist settings over the network.
  }
}

/// You can also use prepared controller for common use case
/// Another ways is to make your controller private using dart part and part of
class _SampleItemListScreenController
    extends DataListScreenController<SampleItem> {
  _SampleItemListScreenController(super.state);

  @override
  Future<List<SampleItem>> fetchData() {
    return Future.delayed(const Duration(seconds: 3), () {
      return const [SampleItem(1), SampleItem(2), SampleItem(3)];
    });
  }
}

/// Displays a list of SampleItems.
class SampleItemListScreen extends StatelessWidget {
  const SampleItemListScreen({super.key});

  static const routeName = '/';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample Items'),
        actions: [
          IconButton(
            icon: const Icon(Icons.settings),
            onPressed: () {
              Navigator.restorablePushNamed(context, SettingsScreen.routeName);
            },
          ),
        ],
      ),
      // Wrap your screen with ScreenControllerBuilder
      // and pass your controller
      body: ScreenControllerBuilder(
        create: _SampleItemListScreenController.new,
        builder: (context, ctrl) {
          if (ctrl.isLoading) {
            return const Center(child: CircularProgressIndicator());
          }

          if (ctrl.hasError) {
            return Text(
              'An error occur try again later: ${ctrl.error.toString()}',
            );
          }

          // There is no error and isLoading is false, then we have data
          return ListView.builder(
            restorationId: 'sampleItemListSampleItemListScreen',
            itemCount: ctrl.data.length,
            itemBuilder: (BuildContext context, int index) => ListTile(
              title: Text('SampleItem ${ctrl.data[index].id}'),
              leading: const CircleAvatar(
                foregroundImage: AssetImage('assets/images/flutter_logo.png'),
              ),
              onTap: () {
                Navigator.restorablePushNamed(
                  context,
                  SampleItemDetailsScreen.routeName,
                  arguments: ctrl.data[index].id,
                );
              },
            ),
          );
        },
      ),
    );
  }
}
1
likes
150
points
78
downloads

Publisher

unverified uploader

Weekly Downloads

A view-controller pattern implementation to separate UI from logic

Repository (GitHub)

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_screen_controller