Flutter控制器管理插件simple_controller的使用

发布于 1周前 作者 vueper 来自 Flutter

Flutter控制器管理插件simple_controller的使用

simple_controller 是一个轻量级且高效的 Flutter 应用状态管理解决方案。它提供了简单直观的 API、类型安全的状态管理、最小的样板代码、反应式更新以及易于学习和实现的特点。

特性

  • 🚀 简单直观的 API
  • 🎯 类型安全的状态管理
  • 📦 最小的样板代码
  • 🔄 反应式更新
  • 💡 易于学习和实现

使用方法

关键组件

SimpleController

这是一个扩展 ChangeNotifier 的控制器类,用于管理依赖项并通知监听器。

createState

创建一个反应式状态变量。它初始化状态并提供监听变化的方法。此状态可以在控制器内部管理并高效地更新 UI。

createRefState

创建一个反应式状态变量。它初始化状态并提供监听变化的方法。此状态可以在控制器内部管理并高效地更新 UI。

createCommand

创建一个命令,封装一段逻辑或操作。它可以带有一个参数,并支持如防抖等特性。命令有助于在控制器内部组织和重用逻辑。

SimpleControllerProvider

一个提供单个控制器实例给其子树的 widget。当需要向 widget 子树提供单个控制器时使用。

SimpleControllerProvider.multi

一个允许同时提供多个控制器的 widget。当需要提供多个协同工作的控制器时使用。

context.use<T>()

从 widget 树中访问类型为 T 的控制器。这提供了类型安全的控制器访问方式。

controller.build()

当特定控制器状态变化时高效地重建 widget。它接受一个选择器函数来指定要监听的状态,以及一个构建函数来构造 widget。

基本示例

class CounterController extends SimpleController {
  late final countState = createState(0);

  late final increment = createCommand((_) {
    countState.value++;
  });
}

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

  @override
  Widget build(BuildContext context) {
    return SimpleControllerProvider(
      create: (context) => CounterController(),
      child: const CounterWidget(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final controller = context.use<CounterController>();
    return controller.build(
      select: (value) => value.countState.value,
      builder: (context, value, child) => Text(value.toString()),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final controller = context.use<CounterController>();
    return TextButton(
      onPressed: () {
        controller.increment.execute(null);
      },
      child: const Text('Increment'),
    );
  }
}

高级用法

对于更复杂的状态管理场景,可以:

class SettingController extends SimpleController {
  late final localeState = createState(Locale('en', 'US'));
  late final themeModeState = createState(ThemeMode.system);
  late final envState = createState(Env.dev);
}

class MainAppController extends SimpleController {
  MainAppController({
    required SettingController settingController,
  }) : _settingController = settingController;

  late final SettingController _settingController;

  late final countLengthState = createRefState((ref) {
    final env = ref.watchState(_settingController.envState);
    return switch (env) {
      Env.dev => 3,
      Env.uat => 2,
      Env.stg => 1,
      Env.prod => 0,
    };
  });

  final int maxCountLength = 3;

  bool get isMaxCountLength => countLengthState.value >= maxCountLength;

  bool get isMinCountLength => countLengthState.value <= 0;

  late final incrementCountLength = createCommand((_) {
    if (isMaxCountLength) {
      return;
    }
    countLengthState.value++;
  });

  late final decrementCountLength = createCommand((_) {
    if (isMinCountLength) {
      return;
    }
    countLengthState.value--;
  });
}

class HomePageController extends SimpleController {
  HomePageController({
    required MainAppController mainAppController,
  }) : _mainAppController = mainAppController;

  late final MainAppController _mainAppController;

  late final countsState = createRefState((ref) {
    final countLength = ref.watchState(_mainAppController.countLengthState);
    return List.generate(countLength, (i) => 0);
  });

  late final increment = createCommand(
    (int index) {
      countsState.value[index]++;
    },
    debounce: const Duration(milliseconds: 100),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return SimpleControllerProvider.multi(
      providers: [
        SimpleControllerProvider<SettingController>(
          create: (context) => SettingController(),
        ),
        SimpleControllerProvider<MainAppController>(
          create: (context) => MainAppController(
            settingController: context.use(),
          ),
        ),
      ],
      child: Builder(
        builder: (context) {
          final settingController = context.use<SettingController>();
          return settingController.build(
            select: (value) => (
              locale: value.localeState.value,
              themeMode: value.themeModeState.value,
            ),
            builder: (context, value, child) => MaterialApp(
              locale: value.locale,
              themeMode: value.themeMode,
              theme: ThemeData.light(),
              darkTheme: ThemeData.dark(),
              home: child,
            ),
            child: SimpleControllerProvider(
              create: (context) => HomePageController(
                mainAppController: context.use(),
              ),
              child: HomePage(),
            ),
          );
        },
      ),
    );
  }
}

示例代码

import 'dart:developer' as developer;

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:simple_controller/simple_controller.dart';

enum Env {
  dev,
  prod,
}

class SettingController extends SimpleController {
  final supportedLocales = [
    const Locale('en', 'US'),
    const Locale('fr', 'FR'),
    const Locale('de', 'DE'),
    const Locale('es', 'ES'),
    const Locale('it', 'IT'),
  ];

  late final envState = createState(Env.dev);

  late final themeModeState = createState(ThemeMode.system);

  late final localeState = createState(supportedLocales.first);

  late final counterToggleState = createState(false);

  late final devInitialCountState = createState(999);

  late final prodInitialCountState = createState(0);

  late final initialCountState = createRefState(
    (ref) {
      final env = ref.watchState(envState);
      if (env == Env.dev) {
        return ref.watchState(devInitialCountState);
      }
      return ref.watchState(prodInitialCountState);
    },
  );
}

class CounterController extends SimpleController {
  CounterController({
    required SettingController settingController,
  }) : _settingController = settingController {
    addDependency(
      controller: settingController,
      select: (value) => value.initialCountState.value,
      listen: (prev, next) {
        final prevText = initialCountTextControllerState.value.text;
        final nextText = '$next';
        if (prevText != nextText) {
          initialCountTextControllerState.value.text = nextText;
        }
      },
    );
  }

  final SettingController _settingController;

  late final initialCountTextControllerState = createState(
    TextEditingController(
      text: '${_settingController.initialCountState.value}',
    ),
    onDispose: (value) {
      value.dispose();
    },
  );

  late final counterState = createRefState(
    (ref) {
      final initialCount = ref.watchState(_settingController.initialCountState);
      return initialCount;
    },
  );

  late final increment = createCommand((_) {
    counterState.value++;
  });
}

void main() {
  SimpleController.showLog = true;
  SimpleController.showLogDetail = false;
  SimpleController.log = (message) {
    developer.log(
      message,
      name: 'simple_controller',
    );
  };
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return SimpleControllerProvider.multi(
      providers: [
        SimpleControllerProvider<SettingController>(
          create: (context) => SettingController(),
        ),
      ],
      child: Builder(
        builder: (context) {
          final settingController = context.use<SettingController>();
          return settingController.build(
            select: (settingController) => (
              themeMode: settingController.themeModeState.value,
              locale: settingController.localeState.value,
              supportedLocales: settingController.supportedLocales,
            ),
            builder: (context, states, child) {
              return MaterialApp(
                themeMode: states.themeMode,
                theme: ThemeData.light(),
                darkTheme: ThemeData.dark(),
                locale: states.locale,
                supportedLocales: states.supportedLocales,
                localizationsDelegates: const [
                  GlobalMaterialLocalizations.delegate,
                  GlobalWidgetsLocalizations.delegate,
                  GlobalCupertinoLocalizations.delegate,
                ],
                home: child,
              );
            },
            child: const HomePage(),
          );
        },
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final settingController = context.use<SettingController>();
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const EnvWidget(),
              const ThemeModeWidget(),
              const LocaleWidget(),
              const EnvInitialCountWidget(),
              const CounterToggleWidget(),
              settingController.build(
                select: (value) => value.counterToggleState.value,
                builder: (context, value, child) {
                  if (value) {
                    return SimpleControllerProvider(
                      create: (context) => CounterController(
                        settingController: context.use(),
                      ),
                      child: const Column(
                        children: [
                          InitialCountWidget(),
                          CounterWidget(),
                        ],
                      ),
                    );
                  }
                  return const SizedBox.shrink();
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final settingController = context.use<SettingController>();
    return settingController.build(
      select: (value) => value.envState.value,
      builder: (context, value, child) {
        return DropdownMenu(
          initialSelection: value,
          dropdownMenuEntries: [
            for (final env in Env.values)
              DropdownMenuEntry(
                value: env,
                label: '$env',
              ),
          ],
          onSelected: (env) {
            if (env != null) {
              settingController.envState.value = env;
            }
          },
        );
      },
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final settingController = context.use<SettingController>();
    return settingController.build(
      select: (value) => value.themeModeState.value,
      builder: (context, value, child) {
        return DropdownMenu(
          initialSelection: value,
          dropdownMenuEntries: [
            for (final themeMode in ThemeMode.values)
              DropdownMenuEntry(
                value: themeMode,
                label: '$themeMode',
              ),
          ],
          onSelected: (themeMode) {
            if (themeMode != null) {
              settingController.themeModeState.value = themeMode;
            }
          },
        );
      },
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final settingController = context.use<SettingController>();
    return settingController.build(
      select: (value) => (
        locale: value.localeState.value,
        supportedLocales: value.supportedLocales,
      ),
      builder: (context, states, child) {
        return DropdownMenu(
          initialSelection: states.locale,
          dropdownMenuEntries: [
            for (final locale in states.supportedLocales)
              DropdownMenuEntry(
                value: locale,
                label: '$locale',
              ),
          ],
          onSelected: (locale) {
            if (locale != null) {
              settingController.localeState.value = locale;
            }
          },
        );
      },
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final settingController = context.use<SettingController>();

    return Column(
      children: [
        settingController.build(
          select: (value) => (
            prodInitialCount: value.prodInitialCountState.value,
            devInitialCount: value.devInitialCountState.value,
          ),
          builder: (context, value, child) {
            return Text(
              'Initial Count\n'
              '${Env.prod}: ${value.prodInitialCount}\n'
              '${Env.dev}: ${value.devInitialCount}',
              textAlign: TextAlign.center,
            );
          },
        ),
        FilledButton.icon(
          onPressed: () {
            settingController.prodInitialCountState.value++;
          },
          icon: const Icon(Icons.add),
          label: const Text('Increment Prod'),
        ),
        FilledButton.icon(
          onPressed: () {
            settingController.prodInitialCountState.value--;
          },
          icon: const Icon(Icons.remove),
          label: const Text('Decrement Prod'),
        ),
        FilledButton.icon(
          onPressed: () {
            settingController.devInitialCountState.value++;
          },
          icon: const Icon(Icons.add),
          label: const Text('Increment Dev'),
        ),
        FilledButton.icon(
          onPressed: () {
            settingController.devInitialCountState.value--;
          },
          icon: const Icon(Icons.remove),
          label: const Text('Decrement Dev'),
        ),
      ],
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final settingController = context.use<SettingController>();
    return ConstrainedBox(
      constraints: const BoxConstraints(
        maxWidth: 300,
      ),
      child: settingController.build(
        select: (value) => value.counterToggleState.value,
        builder: (context, value, child) {
          return SwitchListTile(
            value: value,
            onChanged: (value) {
              settingController.counterToggleState.value = value;
            },
            title: const Text('Counter Toggle'),
          );
        },
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final counterController = context.use<CounterController>();
    return counterController.build(
      select: (value) => value.initialCountTextControllerState.value,
      builder: (context, value, child) {
        return TextFormField(
          controller: value,
          onChanged: (value) {
            final parsedValue = int.tryParse(value);
            if (parsedValue != null) {
              final settingController = context.use<SettingController>();
              settingController.initialCountState.value = parsedValue;
            }
          },
          keyboardType: TextInputType.number,
          decoration: const InputDecoration(
            labelText: 'Initial Count',
            border: OutlineInputBorder(),
            constraints: BoxConstraints(
              maxWidth: 132,
            ),
          ),
          inputFormatters: [
            FilteringTextInputFormatter.digitsOnly,
          ],
        );
      },
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final counterController = context.use<CounterController>();
    return Column(
      children: [
        const Text(
          'You have pushed the button this many times:',
          textAlign: TextAlign.center,
        ),
        counterController.build(
          select: (value) => value.counterState.value,
          builder: (context, value, child) {
            return Text('$value');
          },
        ),
        FilledButton.icon(
          onPressed: () {
            counterController.increment.execute(null);
          },
          icon: const Icon(Icons.add),
          label: const Text('Increment'),
        ),
      ],
    );
  }
}

以上代码展示了如何使用 simple_controller 插件进行状态管理。通过这些示例,你可以看到 simple_controller 如何简化状态管理的复杂性,并提供高效、类型安全的解决方案。


更多关于Flutter控制器管理插件simple_controller的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter控制器管理插件simple_controller的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用simple_controller插件进行控制器管理的代码案例。请注意,simple_controller这个包名在Flutter社区中可能不是非常常见,但基于你的要求,我将假设它提供了一些基本的控制器管理功能。如果没有这样一个具体的包,以下示例将展示一个类似功能的实现,即如何管理应用状态和控制逻辑。

首先,你需要确保在pubspec.yaml文件中添加了必要的依赖(如果simple_controller确实存在):

dependencies:
  flutter:
    sdk: flutter
  simple_controller: ^x.y.z  # 假设版本号为x.y.z

然后运行flutter pub get来获取依赖。

然而,由于simple_controller可能不是一个真实存在的包,我将展示如何使用Provider或GetX等流行的状态管理库来模拟控制器管理的功能。这里以Provider为例:

使用Provider进行控制器管理

  1. 创建一个Controller类
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class MyController with ChangeNotifier {
  int _counter = 0;

  int get counter => _counter;

  void increment() {
    _counter++;
    notifyListeners();
  }
}
  1. 在应用顶层提供Controller
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'my_controller.dart'; // 假设Controller类在这个文件中

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => MyController()),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}
  1. 在UI中使用Controller
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyController controller = Provider.of<MyController>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${controller.counter}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

总结

上面的代码示例展示了如何使用Provider在Flutter中进行状态管理,这类似于使用控制器管理插件。如果你有一个特定的simple_controller插件,并且它有不同的API,请参考该插件的文档进行相应调整。不过,大多数状态管理库的基本思想是相似的:创建一个持有状态的类,通过某种方式(如Provider、GetX等)在应用中提供这个类的实例,并在UI中消费这个状态。

回到顶部