Flutter子树管理插件subtree的使用

Flutter子树管理插件subtree的使用

简介

Subtree 是一个状态管理器,适用于喜欢 BLoC 但不喜欢其冗长性的开发者。它将状态注入与状态观察机制分离,并提供了一种紧凑的状态观察机制,而不是冗长的 Builder 概念。事件(动作)不是独立的对象,而是简单的函数调用,大大减少了样板代码。

BLoC 差异

  • Subtree 将状态注入与状态观察机制分离。
  • 提供了紧凑的状态观察机制,而不是冗长的 Builder 概念。
  • 事件(动作)是简单的函数调用,而非独立对象,减少了样板代码。
  • 不尝试进入所有应用层,仅限于表现层。

基本示例

class ExamplePageState {
  final title = ValueNotifier("...");
}

abstract class ExamplePageActions {
  void buttonClicked();
}

void build(BuildContext context) {
  final state = context.subtreeGet<ExamplePageState>(); // 获取子树状态
  final actions = context.subtreeGet<ExamplePageActions>(); // 获取子树动作

  // 监听 state.title 的变化
  return Obx((ref) =>
      TextButton(
          onPressed: actions.buttonClicked,
          child: Text(ref.watch(state.title))
      ));
}

在这个例子中,状态是一个简单的类,你可以向其中添加任何响应式原语。Obxref.watch 用于减少 ValueListenableBuilder 的冗长性。你可以监听任何实现了 ValueListenable 接口的原语。

如果需要,你也可以使用其他响应式原语和构建器,例如 StreamBuilder

class ExamplePageState {
  final title = BehaviorSubject<String>();
}

void build(BuildContext context) {
  final state = context.subtreeGet<ExamplePageState>();

  return StreamBuilder<String>(
      stream: state.title,
      builder: (BuildContext context, AsyncSnapshot<String> titleSnapshot) {
        // 构建 UI
      });
}

计数器示例

通常,使用计数器示例来演示状态管理的使用。但是,这离实际用例有些远。让我们稍微复杂一些,假设计数器状态位于后端。

CounterAPI 类

abstract class CounterAPI {
  Future<int> getCounterValue();

  Future<int> incCounterValue();

  Future<int> decCounterValue();
}

CounterState 类

class CounterState {
  final counter = Rx<int>(0);
  final loaded = Rx<bool>(false);
  final blocked = Rx<bool>(false);
}

CounterActions 抽象类

abstract class CounterActions {
  void incCounter();

  void decCounter();
}

CounterController 类

class CounterController extends SubtreeController implements CounterActions {
  final state = CounterState();

  @protected
  final CounterAPI counterAPI;

  CounterController({required this.counterAPI}) {
    subtree.put(state);
    subtree.put<CounterActions>(this);
    loadData();
  }

  void loadData() async {
    final counter = await counterAPI.getCounterValue();
    state.counter.value = counter;
    state.loaded.value = true;
  }

  @override
  void incCounter() async {
    state.blocked.value = true;

    final newCounterValue = await counterAPI.incCounterValue();
    state.counter.value = newCounterValue;

    state.blocked.value = false;
  }

  @override
  void decCounter() async {
    state.blocked.value = true;

    final newCounterValue = await counterAPI.decCounterValue();
    state.counter.value = newCounterValue;

    state.blocked.value = false;
  }
}

CounterScreen 类

class CounterScreen extends StatelessWidget {
  const CounterScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final state = context.get<CounterState>();
    final actions = context.get<CounterActions>();

    return Scaffold(
        appBar: AppBar(title: const Text('Counter')),
        body: Obx((ref) {
          if (!ref.watch(state.loaded)) {
            return const Center(child: CircularProgressIndicator());
          }
          return Stack(children: [
            Center(
              child: Column(children: [
                Text('Counter: ${ref.watch(state.counter)}'),
                MaterialButton(
                  onPressed: actions.incCounter,
                  child: const Text('+'),
                ),
                MaterialButton(
                  onPressed: actions.decCounter,
                  child: const Text('-'),
                )
              ]),
            ),
            if (ref.watch(state.blocked)) ...[
              const Opacity(
                opacity: 0.2,
                child: ModalBarrier(dismissible: false, color: Colors.black),
              ),
              const Center(child: CircularProgressIndicator())
            ]
          ]);
        }));
  }
}

绑定所有组件

ControlledSubtree(
  subtree: const CounterScreen(),
  controller: () => CounterController(counterAPI: services.counterAPI),
);

由于子树小部件和控制器之间不直接依赖,它们可以独立测试。你还可以为不同的平台提供不同的小部件,或者为演示模式提供具有预设数据的模拟控制器。

ControlledSubtree(
  subtree: isDesktop ? const CounterScreenDesktop() : const CounterScreen(),
  controller: () => isDemo ? MockCounterController() : CounterController(counterAPI: services.counterAPI),
);

更多关于Flutter子树管理插件subtree的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

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


在Flutter中,子树管理通常涉及对特定Widget树的创建、更新和销毁的精细控制。虽然Flutter本身并没有一个官方名为“subtree”的插件,但我们可以通过一些常用的状态管理和布局控制技巧来实现子树的管理。

一个常见的场景是,你可能想要动态地显示或隐藏某个子树(一组Widgets)。这可以通过使用StatefulWidget和条件渲染来实现。以下是一个简单的代码示例,展示了如何根据状态来控制子树的显示和隐藏:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Subtree Management Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SubtreeManagementExample(),
    );
  }
}

class SubtreeManagementExample extends StatefulWidget {
  @override
  _SubtreeManagementExampleState createState() => _SubtreeManagementExampleState();
}

class _SubtreeManagementExampleState extends State<SubtreeManagementExample> {
  bool isSubtreeVisible = false;

  void toggleSubtreeVisibility() {
    setState(() {
      isSubtreeVisible = !isSubtreeVisible;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Subtree Management Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Toggle Subtree:',
              style: TextStyle(fontSize: 20),
            ),
            ElevatedButton(
              onPressed: toggleSubtreeVisibility,
              child: Text(isSubtreeVisible ? 'Hide' : 'Show'),
            ),
            SizedBox(height: 20),
            // Conditionally render the subtree
            if (isSubtreeVisible) {
              SubtreeWidget(),
            }
          ],
        ),
      ),
    );
  }
}

class SubtreeWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text('This is part of the subtree.'),
        Text('Another widget in the subtree.'),
        // Add more widgets as needed
      ],
    );
  }
}

在这个示例中:

  1. SubtreeManagementExample是一个StatefulWidget,它维护了一个布尔状态isSubtreeVisible,用于控制子树的显示和隐藏。
  2. toggleSubtreeVisibility方法用于切换isSubtreeVisible的值,并通过调用setState来触发UI的重新构建。
  3. build方法中,我们使用了一个条件渲染语句if (isSubtreeVisible)来决定是否显示SubtreeWidget

这种方法允许你根据应用的状态动态地管理子树的显示和隐藏。如果你需要更复杂的子树管理(例如,在多个屏幕或组件之间共享状态),可能需要考虑使用更高级的状态管理解决方案,如ProviderRiverpodBloc等。

回到顶部