Flutter控制器管理插件simple_controller的使用
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