什么是 StateMixin
?
StateMixin<T>
是 GetX 提供的一个状态管理混入类(mixin),它的作用是 简化异步请求的状态管理和 UI 更新。
核心思想:
- 异步请求通常有四种状态:加载中 / 成功 / 空 / 错误
StateMixin
提供了统一的状态管理机制,让 UI 自动响应状态变化,无需手动管理多个变量(如isLoading
、isError
、data
)。
优势
- 统一管理异步状态:loading / success / empty / error 一目了然。
- 减少冗余代码:不需要单独定义
isLoading
、isError
、data
等变量。 - UI 自动刷新:
obx
根据状态自动渲染,不用手动刷新界面。 - 可与 GetConnect、Dio 等请求工具结合使用。
注意事项
- 不是必须的:如果你不想用
StateMixin
,也可以用普通Rx
+Obx
管理状态。 - 适合页面初始化或单次请求:当状态频繁变化或者有复杂逻辑时,可以选择更灵活的 Rx 变量方案。
StateMixin
的核心方法
方法 | 作用 |
---|---|
change(T? data, {RxStatus status}) |
更新数据和状态,通常在 Controller 中调用 |
RxStatus.loading() |
表示加载中状态 |
RxStatus.success() |
表示请求成功状态 |
RxStatus.empty() |
表示请求成功但数据为空 |
RxStatus.error(String message) |
表示请求失败或出错 |
change()
是用于:
- 更新当前的数据 (
data
) - 更新当前的状态 (
RxStatus
)
它是 状态管理和数据绑定的桥梁,用它可以让 UI 根据状态自动刷新,而不需要手动 setState()
或管理多个 Rx 变量。
StateMixin
的示例
import 'package:flutter/material.dart';
import 'package:get/get.dart';
// ------------------ 1. 创建 GetConnect API --------------------
class PostApi extends GetConnect {
/// 初始化
/// class GetConnect extends GetConnectInterface {
/// abstract class GetConnectInterface with GetLifeCycleBase {
/// onInit() → 实例创建时触发
@override
void onInit() {
// 设置基础 URL
httpClient.baseUrl = "https://jsonplaceholder.typicode.com";
super.onInit();
}
/// get("/posts"):①发送Get请求 ②制定请求路径:baseUrl+/posts
/// ③返回一个Future<Response>对象
/// https://jsonplaceholder.typicode.com/posts
Future<Response> getPosts() => get("/posts");
}
// -------------------2. 创建 Controller ---------------------------------
// with StateMixin<List<dynamic>> : 混入 StateMixin<List<dynamic>>
// StateMixin 会提供 change() 方法和 RxStatus 状态,让 UI 自动刷新。
// StateMixin<T> 中的泛型 T 决定 state(response的body的类型) 的类型。
class PostController extends GetxController with StateMixin<List<dynamic>> {
final api = Get.find<PostApi>();
void fetchPosts() async {
// data 这里传 null,因为还没有获取到数据。
// status: RxStatus.loading() 表示 当前正在加载数据,UI 会显示 loading 指示器(在页面里通过 obx 渲染)。
change(null, status: RxStatus.loading()); // ✅ 设置加载状态
try {
final response = await api.getPosts();
if (response.statusCode == 200) {
// 请求成功,获得数据
final data = response.body;
if (data.isEmpty) {
// 数据为空
// UI 会显示 “暂无数据” 的占位界面。
change([], status: RxStatus.empty()); // 数据为空
} else {
// UI 会显示列表内容,obx 自动监听 change() 并刷新界面。
change(data, status: RxStatus.success()); // 请求成功
}
} else {
// 请求失败,显示错误信息
// RxStatus.error() 会让 UI 显示错误提示。
change(
null,
status: RxStatus.error("请求失败: ${response.statusCode}"),
); // 请求失败
}
} catch (e) {
// 捕获请求异常(如网络超时、解析失败等)。
// 将状态设置为 error,UI 会显示错误信息。
change(null, status: RxStatus.error(e.toString())); // 异常
}
}
}
// -----------------------3. 创建 Binding --------------------------------
/// 延迟注入的核心好处就是 按需创建对象,避免顺序依赖和不必要的资源占用。
/// 通过 Get.lazyPut,我们可以在需要的时候再创建实例,而不是在应用启动时就创建所有实例。
class PostBinding extends Bindings {
@override
void dependencies() {
// ✅ 延迟注入 PostApi
Get.lazyPut<PostApi>(() => PostApi());
// ✅ 延迟注入 PostController
Get.lazyPut<PostController>(() => PostController());
}
}
// -----------------------4.创建页面 ------------------------------------
class PostPage extends StatelessWidget {
PostPage({super.key});
final PostController controller = Get.find<PostController>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("GetConnect + StateMixin 示例")),
body: Column(
children: [
ElevatedButton(
onPressed: () {
controller.fetchPosts(); // 点击按钮触发请求
},
child: Text("加载数据"),
),
Expanded(
// 页面使用 controller.obx(...) 监听状态,
// 在Controller中调用 change() 会自动触发 UI 更新。
child: controller.obx(
// 这个 state 对应的是请求的结果数据
// List<dynamic>? state :这个是由StateMixin<List<dynamic>>决定的
(state) => ListView.builder(
itemCount: state!.length,
itemBuilder: (context, index) {
final post = state[index];
return ListTile(
title: Text(post["title"]),
subtitle: Text(post["body"]),
);
},
),
onLoading: Center(
child: CircularProgressIndicator(),
), // loading 状态
onEmpty: Center(child: Text("暂无数据")), // empty 状态
onError: (err) => Center(child: Text("错误: $err")), // error 状态
),
),
],
),
);
}
}
// -----------------------4.路由配置(将 Binding 和页面绑定)---------------------
void main() {
runApp(
GetMaterialApp(
initialRoute: '/posts',
getPages: [
GetPage(
name: '/posts',
page: () => PostPage(),
binding: PostBinding(), // ✅ 页面进入时自动注入依赖
),
],
),
);
}
步骤
- Controller混入StateMixin
- Controller里面:数据或者状态变化的时候调用
change(T? data, {RxStatus status})
- 页面:
controller.obx
监听状态的变化 - 页面:不同状态,不同的UI
注意StateMixin<T>
泛型的选择
StateMixin<T>
中的泛型 T
决定 state
的类型。
state
的类型可以是:
List<dynamic>
→ 列表Map<String, dynamic>
→ JSON 对象String
→ 文本- 甚至是自定义模型类
MyModel
StateMixin<List<dynamic>>
,所以 state
是一个列表。
如果返回值是Json
假设接口返回一个单独的 JSON 对象:
{
"id": 1,
"title": "Hello",
"body": "World"
}
Controller 可以定义为:
class PostController extends GetxController with StateMixin<Map<String, dynamic>> {
// ......
final response = await api.getSinglePost();
if (response.statusCode == 200) {
final data = response.body; // Map<String, dynamic>
change(data, status: RxStatus.success());
// ......
}
页面使用:
controller.obx(
(state) => Column(
children: [
Text("ID: ${state!['id']}"),
Text("Title: ${state['title']}"),
Text("Body: ${state['body']}"),
],
),
onLoading: Center(child: CircularProgressIndicator()),
onError: (err) => Center(child: Text("错误: $err")),
);
如果返回值是字符串
如果接口直接返回一个字符串,比如 "Hello World"
:
class StringController extends GetxController with StateMixin<String> {
void fetchMessage() async {
change(null, status: RxStatus.loading());
await Future.delayed(Duration(seconds: 1));
change("Hello World", status: RxStatus.success());
}
}
页面使用:
controller.obx(
(state) => Center(child: Text(state!)), // state 就是字符串
onLoading: Center(child: CircularProgressIndicator()),
onError: (err) => Center(child: Text("错误: $err")),
);