Flutter应用状态管理插件app_state的使用

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

Flutter应用状态管理插件app_state的使用

简介

app_state 是一个基于路由API的状态管理解决方案,适用于较大的应用程序。它提供了许多独特的功能,如以状态为中心的设计、编译时类型安全的push-pop操作、从URL恢复堆栈以及在应用程序重启后对话框结果仍然存在等。此外,还提供了一些架构上的优势和灵活性。

核心概念

状态优先 (States First)

与传统的路由或小部件不同,app_state 的核心是状态对象的堆栈(可以是Blocs、ChangeNotifiers或其他任何东西),这些被称为页面状态。导航意图被翻译成对这些状态的操作,然后将这些页面状态包装成小部件以形成导航器的堆栈。这简化了状态管理:无需提供商、有状态的小部件、手动Bloc创建和销毁或 BuildContext

编译时类型安全的Push-Pop操作 (Push-pop Type Safety at Compile Time)

所有状态对象都是类型的,并且只能弹出允许的内容。你推送的页面条目也是类型的,因此你的等待类型也得到了保证。

从URL恢复堆栈 (Stack Recovery from URL)

当输入一个URL时,可以根据定义的规则恢复页面堆栈。例如,如果你导航到 /books/123/rate,你可能会得到一个包含三个页面的堆栈:底部是书籍列表,上面是书籍详情页,顶部是评分对话框,所有这些都可以通过返回按钮弹出。

对话框结果在应用程序重启后仍然存在 (Dialog Awaiting Survives the App Restart)

页面状态监听来自其他页面状态的事件。当最顶部的一个弹出时,其结果会传递给下面的页面状态的 didPopNext 方法。因此,你可以在不等待未来的情况下获得结果。

架构

主要的状态单元是 PageStack,通常作为全局对象创建,或者你可以使用 get_it 提供它。

对于每个页面,有三个主要部分:

  • 页面状态 (Page State): 页面生命周期的核心。
  • 屏幕 (Screen): 大多数情况下是一个只接受页面状态作为单个参数的状态化小部件。
  • 页面 (Page): 这是页面状态和屏幕之间的适配器。

示例代码

最小的应用程序 (The Bare Minimal App)

这是一个只有一个屏幕且没有导航的应用程序,甚至没有页面状态,因为它没有什么需要保存的。

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

final pageStack = PageStack(bottomPage: HomePage());
final _routerDelegate = PageStackRouterDelegate(pageStack);

void main() => runApp(MyApp());

class HomePage extends StatelessMaterialPage {
  HomePage() : super(key: const ValueKey('Home'), child: HomeScreen());
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Hello World with app_state!')),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerDelegate: _routerDelegate,
      routeInformationParser: const PageStackRouteInformationParser(),
    );
  }
}

推送和弹出页面 (Pushing and Popping Pages)

推送页面 (Pushing)

要推送一个屏幕,你需要创建它的 Page 对象并将其推送到 PageStack 中:

final result = await pageStack.push(
  BookDetailsPage(bookId: id),
);

弹出页面 (Programmatic Popping)

要弹出一个页面,调用其状态上的 pop() 方法,可选地带有返回值:

onPressed: () => state.pop(result);

覆盖返回按钮 (Overriding the Back Button)

在你的状态中覆盖 onBackPressed() 方法:

@override
Future<BackPressedResult> onBackPressed() {
  if (!_saved) {
    return Future.value(BackPressedResult.keep);
  }
  return Future.value(BackPressedResult.close);
}

Web架构 (Web Architecture)

为了支持URL,需要解析URL并创建相应的页面路径。

解析URL (Parsing URLs)

创建一个URL解析器,该解析器在启动时由Flutter调用,并在浏览器的回退和前进导航时调用:

class MyRouteInformationParser extends PageStackRouteInformationParser {
  @override
  Future<PagePath> parsePagePath(RouteInformation ri) async {
    return
        BookDetailsPath.tryParse(ri) ??
        const BookListPath(); // 默认页面如果什么都匹配不上。
  }
}

创建页面对象 (Creating the Page Objects)

创建一个全局方法来创建页面对象:

AbstractPage? createPage(
  String factoryKey,
  Map<String, dynamic> state,
) {
  switch (factoryKey) {
    case BookDetailsPage.classFactoryKey: return BookDetailsPage(bookId: state['bookId']);
    case BookListPage.classFactoryKey: return BookListPage();
  }

  return null;
}

更新地址栏 (Updating the Address Bar)

更新地址栏内容的方式取决于页面是否有状态。

没有状态的页面 (Page Without State)

对于没有状态的页面,你可以在构造函数中硬编码一个 PagePath

super(
  // ...
  path: BookDetailsPath(bookId: bookId),
);

有状态的页面 (Page With State)

对于有状态的页面,委托给状态进行更高灵活性的更新:

@override
BookListPath get path => const BookListPath();

不影响URL的页面 (Page Without URL)

对于不影响地址栏和浏览器历史记录的次要对话框,只需不在页面路径类中引入任何路径类。

程序化更新URL (Updating the URL Programmatically)

想象一个树形浏览器,动态更新地址栏中的内容:

state.emitPathChanged();

重定向URL (Redirecting a URL)

可以通过以下两种方式之一实现:

单个类 (Option 1. Single Class)

BookListPath.tryParse() 中允许 //books,并在 location getter 中返回 /books

多个类 (Option 2. Multiple Classes)

可以有许多 PagePath 类,它们都返回 BookListPath,并且它在 tryParse 方法中返回 /books

浏览器回退和前进按钮 (Browser Back and Forward Buttons)

回退和前进按钮在浏览器中自动工作。它们遍历浏览器的历史记录。

恢复未保存的输入 (Recovering Unsaved Input on Page Refresh and Navigation)

此包允许你在许多情况下恢复状态,而不会丢失状态。

多个选项卡具有独立的堆栈 (Multiple Tabs with Independent Stacks)

每个选项卡必须有自己的导航堆栈。Android返回按钮和Scaffold的返回按钮应该只弹出当前堆栈上的页面。选项卡切换不应影响返回按钮历史记录。

高级返回结果的方法 (Advanced Ways to Return Result)

包括编译时类型安全的Push-Pop操作,以及接收对话框结果后应用程序重启。

技术支持聊天 (Tech Support Chat)

如果你有任何问题,欢迎加入 Telegram Support Chat

需要帮助 (Help is Wanted)

如果你喜欢这个包,请帮忙传播、报告问题或参与开发。

更多示例项目请参阅 这里

希望这篇文档对你有所帮助!如果你有任何问题或需要进一步的帮助,请随时提问。


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

1 回复

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


当然,app_state 并不是一个广为人知的 Flutter 状态管理插件,但我可以基于 Flutter 中常见的状态管理插件(如 providerriverpod)给出一个类似的示例代码,因为这些插件在 Flutter 社区中非常流行且功能强大。为了贴近你的要求,我会假设 app_state 提供了类似的功能,并展示如何使用一个类似的状态管理插件来实现应用状态管理。

以下是一个使用 provider 插件管理应用状态的示例代码:

1. 添加依赖

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

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.0  # 请确保使用最新版本

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

2. 创建一个状态管理类

接下来,创建一个管理应用状态的类。例如,我们可以创建一个简单的计数器状态:

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

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

3. 包装应用

main.dart 中,使用 MultiProvider 包装你的应用,并提供 Counter 实例:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter.dart'; // 假设你的 Counter 类在这个文件中

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

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

4. 在UI中使用状态

现在,在你的 MyHomePage 中,你可以使用 ConsumerSelector 来访问和监听 Counter 状态的变化:

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

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext 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(
              '${Provider.of<Counter>(context).count}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of<Counter>(context, listen: false).increment();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

总结

虽然这个示例使用的是 provider 插件而不是假设的 app_state 插件,但它展示了如何在 Flutter 应用中实现状态管理。你可以根据 app_state 的具体 API 文档调整上述代码以适应你的需求。如果 app_state 插件有类似的 ChangeNotifierProvider 机制,代码结构应该是非常相似的。

回到顶部