Flutter状态管理插件state_flow的使用

Flutter状态管理插件StateFlow的使用

StateFlow 提供了一种简单且高效的管理 Flutter 应用程序状态的方法。本指南将引导你了解 StateFlow 的核心概念及其状态管理功能的使用。

核心概念

StateFlow 的状态管理围绕以下几个关键概念:

  • StateFlowController:用于创建控制器,这些控制器负责管理应用程序的逻辑和状态。
  • take():一个响应式值持有者,当其值发生变化时会通知监听器。
  • StateFlowApp:一个用于设置 StateFlow 环境和依赖注入的小部件。
  • StateValueBuilder:一个小部件,当指定的状态发生变化时会重建自身。

设置

首先,在 pubspec.yaml 文件中添加 StateFlow 的依赖:

dependencies:
  state_flow: ^0.0.8

然后,使用 StateFlowApp 包装你的应用以启用 StateFlow 功能。确保传递你想要使用的控制器到 controllers 参数中:

void main() {
  runApp(StateFlowApp(
    controllers: [
      () => CounterController(),
    ],
    child: MyApp(),
  ));
}

创建控制器

控制器是 StateFlow 状态管理的核心。它们管理应用程序的逻辑和状态,并可用于创建响应式的用户界面。

要创建一个控制器,扩展 StateFlowController 类:

声明变量

class CounterController extends StateFlowController {
  final counter = take(0);

  void increment() {
    counter.value++;
  }
}

使用 StateValueBuilder

你可以使用 StateValueBuilder 来根据状态的变化重新构建小部件:

final counterController = listen(CounterController);

StateValueBuilder(
  value: counterController.counter,
  builder: (value) => Text('Counter: $value'),
),

网络请求

StateFlow 提供了使用 StateFlowClient 进行网络请求的功能。

使用 StateFlowClient

首先,创建一个 StateFlowClient 实例。这将用于向服务器发送请求:

final client = StateFlowClient(baseUrl: 'https://jsonplaceholder.typicode.com');

final response = await client.sendRequest('/todos', HttpMethod.GET);

HttpMethod

你可以使用以下方法向服务器发送请求:

enum HttpMethod {
  GET,
  POST,
  PUT,
  DELETE,
}

创建网络控制器

为了创建一个网络控制器,扩展 StateFlowController 类:

class TodoController extends StateFlowController {
  final todos = take<List<Todo>>([]);
  final StateFlowClient _client = StateFlowClient(baseUrl: 'https://jsonplaceholder.typicode.com');

  Future<List<Todo>> fetchTodos() async {
    todos.setLoading();
    try {
      final response = await _client.sendRequest('/todos', HttpMethod.GET);
      if (response.statusCode == 200) {
        final List<dynamic> jsonData = jsonDecode(response.body);
        final todoList = jsonData.map((json) => Todo.fromJson(json)).toList();
        todos.setLoaded(todoList);
        return todos.value;
      } else {
        todos.setError('Failed to load todos');
        return [];
      }
    } catch (e) {
      todos.setError(e);
      todos.stopLoading();
      rethrow;
    }
  }
}

构建响应式UI

你可以使用 listen 函数来监听控制器的状态。如果要构建一个在状态变化时重建的小部件,可以使用 WidgetStateValueBuilder 小部件:

final todoController = listen(TodoController);

WidgetStateValueBuilder<List<Todo>>(
  state: todoController.todos,
  dataBuilder: (data) {
    if (data.isEmpty) {
      return Text('No todos');
    }
    return Expanded(
      child: ListView.builder(
        itemCount: data.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(data[index].title),
          );
        },
      ),
    );
  },
  errorBuilder: (error) => Text('Error: ${error.toString()}'),
  loadingBuilder: () => CircularProgressIndicator(),
),

你也可以通过 StateValueBuilder 传递多个值:

StateValueBuilder(
  values: [
    counterController.count,
    false,
    'Hello',
    false,
  ],
  builder: (values) {
    var [count, isActive, greeting, otherValue] = values;
    
    return Column(
      children: [
        Text('Counter: $count'),
        Icon(isActive ? Icons.check : Icons.close),
        Text(greeting),
        Text('Other value: $otherValue'),
      ],
    );
  },
),

使用动画控制器

如果你希望不使用 StatefulWidget 来使用动画控制器,可以使用 takeAnimationController 函数:

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    final animationController = takeAnimationController();
    return const Placeholder();
  }
}

使用 StateFlowWidget

如果你想在 initStatedispose 方法被调用时使用,可以使用 StateFlowWidget

class TestApp extends StateFlowWidget {
  TestApp({super.key}) : super();

  [@override](/user/override)
  void onInit(context) {
    print('TestApp onInit');
  }

  [@override](/user/override)
  void onDispose() {
    print('TestApp onDispose');
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}

StateFlow 导航

为了在应用中启用导航,可以在 MaterialApp 中使用 navigatorKey: StateFlow.navigatorKey

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'StateFlow Demo',
      navigatorKey: StateFlow.navigatorKey,
      theme: ThemeData(primarySwatch: Colors.blue),
    );
  }
}

使用 StateFlow.to 方法导航到新的屏幕:

StateFlow.to(DetailScreen());

对于返回上一个屏幕,可以使用 StateFlow.back 方法:

StateFlow.back();

还可以使用其他导航方法,如 StateFlow.offStateFlow.offAllStateFlow.maybePopStateFlow.currentRouteStateFlow.getHistory

创建你的应用路由

你可以这样创建你的应用路由:

class AppRoutes {
  static const String home = '/';
  static const String second = '/second';

  static final routes = {
    home: (context) => HomeScreen(),
    second: (context) => SecondScreen(),
  };
}

修改 MaterialApp 如下:

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'StateFlow Demo',
      navigatorKey: StateFlow.navigatorKey,
      onGenerateRoute: (settings) => StateFlow.onGenerateRoute(settings),
      theme: ThemeData(primarySwatch: Colors.blue),
      routes: AppRoutes.routes,
    );
  }
}

现在你可以使用这些路由:

onPressed: () => StateFlow.to('/second')
onPressed: () => StateFlow.to(AppRoutes.second)

完整示例

下面是一个完整的示例代码:

import 'package:flutter/material.dart';
import 'package:state_flow/state_flow.dart';
import 'dart:convert';

void main() {
  runApp(StateFlowApp(
    controllers: [
      () => TodoController(),
      () => CounterController(),
    ],
    child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'StateFlow Demo',
      navigatorKey: StateFlow.navigatorKey,
      onGenerateRoute: (settings) => StateFlow.onGenerateRoute(settings),
      theme: ThemeData(primarySwatch: Colors.blue),
      routes: AppRoutes.routes,
    );
  }
}

class AppRoutes {
  static const String home = '/';
  static const String second = '/second';

  static final routes = {
    home: (context) => TestApp(),
    second: (context) => SecondScreen(),
  };
}

class HomeScreen extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
                child: Text('Go to Second Screen'),
                onPressed: () => StateFlow.to('/second')),
            ElevatedButton(
              child: Text('Replace with Third Screen'),
              onPressed: () => StateFlow.off('/third'),
            ),
            ElevatedButton(
              child: Text('Go to Fourth Screen and clear history'),
              onPressed: () => StateFlow.offAll('/fourth'),
            ),
            ElevatedButton(
              child: Text('Go to Fifth Screen and clear history'),
              onPressed: () => StateFlow.offAll('/fifth'),
            ),
            ElevatedButton(
              child: Text('Print Current Route'),
              onPressed: () => print(StateFlow.currentRoute()),
            ),
            ElevatedButton(
              child: Text('Print Navigation History'),
              onPressed: () => print(StateFlow.getHistory()),
            ),
          ],
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Second Screen')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              child: Text('Go Back'),
              onPressed: () => StateFlow.back(),
            ),
            ElevatedButton(
              child: Text('Can Pop?'),
              onPressed: () => print('Can pop: ${StateFlow.canPop()}'),
            ),
            ElevatedButton(
              child: Text('Maybe Pop'),
              onPressed: () async {
                bool didPop = await StateFlow.maybePop();
                print('Did pop: $didPop');
              },
            ),
          ],
        ),
      ),
    );
  }
}

class Hi extends StateFlowWidget {
  const Hi({super.key}) : super();
  [@override](/user/override)
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}

// THIRD, FOURTH, FIFTH SCREENS
class ThirdScreen extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Third Screen')),
      body: Center(child: Text('Third Screen')),
    );
  }
}

class FourthScreen extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Fourth Screen')),
      body: Center(child: Text('Fourth Screen')),
    );
  }
}

class FifthScreen extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Fifth Screen')),
      body: Center(child: Text('Fifth Screen')),
    );
  }
}

class CounterController extends StateFlowController {
  late final StateValue<int> count;
  late final StateValue<String> status;
  int count23 = 2;

  add() {
    count23++;
  }

  [@override](/user/override)
  void onInit() {
    count = take(2);
    print('CounterController initialized with count: ${count.value}');
    status = take('Zero');
    print('CounterController status initialized: ${status.value}');
  }

  void increment() {
    count.value++;
    updateStatus();
  }

  void decrement() {
    count.value--;
    updateStatus();
  }

  void updateStatus() {
    status.value = count.value == 0 ? 'Zero' : 'Non-zero';
  }

  [@override](/user/override)
  void onDispose() {
    print('Disposing CounterController');
    print('Final count: ${count.value}');
    print('Final status: ${status.value}');
    super.onDispose();
  }
}

class TodoController extends StateFlowController {
  [@override](/user/override)
  void onInit() {}
  final todos = take<List<Todo>>([]);
  final StateFlowClient _client = StateFlowClient(baseUrl: 'https://jsonplaceholder.typicode.com');

  Future<List<Todo>> fetchTodos() async {
    todos.setLoading();
    try {
      final response = await _client.sendRequest('/todos', HttpMethod.GET);
      if (response.statusCode == 200) {
        final List<dynamic> jsonData = jsonDecode(response.body);
        final todoList = jsonData.map((json) => Todo.fromJson(json)).toList();
        todos.setLoaded(todoList);
        return todos.value;
      } else {
        todos.setError('Failed to load todos');
        return [];
      }
    } catch (e) {
      todos.setError(e);
      todos.stopLoading();
      rethrow;
    }
  }
}

class Todo {
  final int id;
  final String title;
  final bool completed;

  Todo.fromJson(Map<String, dynamic> json)
      : id = json['id'],
        title = json['title'],
        completed = json['completed'];
}

class TestApp extends StateFlowWidget {
  const TestApp({super.key}) : super();
  [@override](/user/override)
  void onInit(context) {
    print('TestApp onInit');
  }

  [@override](/user/override)
  void onDispose() {
    print('TestApp onDispose');
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    final todoController = listen(TodoController);
    final CounterController counterController = listen(CounterController);

    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counterController.add();
          print('Pressed');
        },
        child: Icon(Icons.refresh),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            WidgetStateValueBuilder<List<Todo>>(
              state: todoController.todos,
              dataBuilder: (data) {
                if (data.isEmpty) {
                  return Text('No todos');
                }
                return Expanded(
                  child: ListView.builder(
                    itemCount: data.length,
                    itemBuilder: (context, index) {
                      return ListTile(
                        title: Text(data[index].title),
                      );
                    },
                  ),
                );
              },
              errorBuilder: (error) => Text('Error: ${error.toString()}'),
              loadingBuilder: () => CircularProgressIndicator(),
            ),
            SizedBox(height: 20),
            StateValueBuilder(
              value: counterController.count,
              builder: (count) {
                return Text(count.toString());
              },
            ),
            SizedBox(height: 20),
          ],
        ),
      ),
    );
  }
}

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    final animationController = takeAnimationController();
    return const Placeholder();
  }
}

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

1 回复

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


state_flow 是 Flutter 中一个轻量级的状态管理插件,它基于 StreamStreamController 来实现状态管理。state_flow 的核心思想是通过流(Stream)来管理应用的状态,并在状态变化时通知监听者进行更新。

安装 state_flow

首先,你需要在 pubspec.yaml 中添加 state_flow 依赖:

dependencies:
  flutter:
    sdk: flutter
  state_flow: ^0.1.0

然后运行 flutter pub get 来安装依赖。

使用 state_flow

1. 创建状态管理类

首先,你需要创建一个状态管理类,并继承自 StateFlow。在这个类中,你可以定义需要管理的状态以及相关的业务逻辑。

import 'package:state_flow/state_flow.dart';

class CounterState extends StateFlow<int> {
  CounterState() : super(0); // 初始化状态为 0

  void increment() {
    state = state + 1; // 更新状态
  }

  void decrement() {
    state = state - 1; // 更新状态
  }
}

2. 在 UI 中使用状态管理类

在 UI 中,你可以通过 StateFlowBuilder 来监听状态的变化,并在状态变化时更新 UI。

import 'package:flutter/material.dart';
import 'counter_state.dart'; // 导入刚才创建的状态管理类

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

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterScreen(),
    );
  }
}

class CounterScreen extends StatelessWidget {
  final CounterState counterState = CounterState(); // 创建状态管理实例

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Counter App'),
      ),
      body: Center(
        child: StateFlowBuilder<int>(
          flow: counterState,
          builder: (context, state) {
            return Text(
              'Count: $state',
              style: TextStyle(fontSize: 24),
            );
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: counterState.increment,
            child: Icon(Icons.add),
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            onPressed: counterState.decrement,
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}
回到顶部