Tag的作用
依赖注入时使用 tag
在 Get.put() / Get.find() 等依赖注入里使用 tag,其目的是**注册、获取指定的控制器实例**
// 注册两个同类型的控制器,但通过 tag 区分
Get.put(CounterController(), tag: 'c1');
Get.put(CounterController(), tag: 'c2');
// 获取指定 tag 的控制器
final c1 = Get.find<CounterController>(tag: 'c1');
final c2 = Get.find<CounterController>(tag: 'c2');
没有
tag:按类型检索,整个 App 只能同时存在一个CounterController实例。有
tag:同一类型可以注册多个实例,靠tag来唯一标识和区分。类比:
tag就像是给每个控制器实例贴了一个「名字」,否则GetX无法分辨到底要用哪一个。
在UI 绑定组件时使用 tag
在 GetBuilder / GetX / Obx 等 UI 绑定组件里使用 tag,其目的是**绑定到特定 tag 下的控制器实例,避免多个相同类型的控制器互相干扰**
GetBuilder<CounterController>(
tag: 'c1',
builder: (c) => Text('计数器1: ${c.count}'),
);
GetBuilder<CounterController>(
tag: 'c2',
builder: (c) => Text('计数器2: ${c.count}'),
);
GetBuilder会根据type + tag组合找到对应控制器。如果两个
GetBuilder都不写tag,它们会绑定到同一个控制器实例,互相影响。如果使用
init:创建控制器,也可以配合tag使用,确保实例隔离:GetBuilder<CounterController>( init: CounterController(), tag: 'c1', builder: ... )
案例分析
不使用Tag时遇到的问题
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class CounterController extends GetxController {
int count = 0;
void increment() {
count++;
update();
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(home: CounterPage());
}
}
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
// 这里我们想创建两个控制器实例
final c1 = Get.put(CounterController());
final c2 = Get.put(CounterController()); // 实际上会被复用成同一个实例
return Scaffold(
appBar: AppBar(title: const Text('没有 tag')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GetBuilder<CounterController>(
builder: (c) =>
Text('计数器1: ${c.count}', style: const TextStyle(fontSize: 20)),
),
GetBuilder<CounterController>(
builder: (c) =>
Text('计数器2: ${c.count}', style: const TextStyle(fontSize: 20)),
),
ElevatedButton(onPressed: c1.increment, child: const Text('计数器1 +1')),
ElevatedButton(onPressed: c2.increment, child: const Text('计数器2 +1')),
],
),
);
}
}
页面上有两个计数器区块,都使用 GetBuilder<CounterController>,但是控制器实例本想是两个独立的,但因为都没写 tag,最后被当作一个控制器复用,导致两个计数器总是一起变化。
使用Tag成功隔离
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class CounterController extends GetxController {
int count = 0;
void increment() {
count++;
update();
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(home: CounterPage());
}
}
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
// 用 tag 强制创建两个独立实例
final c1 = Get.put(CounterController(), tag: 'c1');
final c2 = Get.put(CounterController(), tag: 'c2');
return Scaffold(
appBar: AppBar(title: const Text('有 tag')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GetBuilder<CounterController>(
tag: 'c1',
builder: (c) =>
Text('计数器1: ${c.count}', style: const TextStyle(fontSize: 20)),
),
GetBuilder<CounterController>(
tag: 'c2',
builder: (c) =>
Text('计数器2: ${c.count}', style: const TextStyle(fontSize: 20)),
),
ElevatedButton(onPressed: c1.increment, child: const Text('计数器1 +1')),
ElevatedButton(onPressed: c2.increment, child: const Text('计数器2 +1')),
],
),
);
}
}
点击「计数器1 +1」 → 只更新计数器1
点击「计数器2 +1」 → 只更新计数器2
二者 互不干扰,问题解决
当需要在同一页面使用相同类型控制器的多个实例时,一定要给 GetBuilder 和 Get.put() 配对加上 tag,否则会出现「明明想要独立,结果共享」的干扰问题。
两个地方都要加Tag
Get.put() 和 GetBuilder 都可以带 tag
只有当 Get.put() 和 GetBuilder 用了 相同的 tag 时,GetBuilder 才能找到对应的那个控制器实例
如果你 Get.put() 时没写 tag,那就只能得到“默认实例”
如果你 GetBuilder 写了 tag,但 Get.put() 没有写相同 tag,是找不到实例的,会报错
Get.put() 是否加 tag |
GetBuilder 是否加 tag |
结果 |
|---|---|---|
| ❌ 无 tag | ❌ 无 tag | 正常:都使用默认实例 |
| ✅ 有 tag | ❌ 无 tag | GetBuilder 找不到实例(报错) |
| ❌ 无 tag | ✅ 有 tag | GetBuilder 找不到实例(报错) |
| ✅ 有 tag | ✅ 有 tag(相同) | 正常:找到对应实例 |
| ✅ 有 tag | ✅ 有 tag(不同) | 各自使用自己的实例,互不干扰 |
final c1 = Get.put(CounterController());
final c2 = Get.put(CounterController());
children: [
GetBuilder<CounterController>(
tag: 'c1',
builder: (c) =>
Text('计数器1: ${c.count}', style: const TextStyle(fontSize: 20)),
),
GetBuilder<CounterController>(
tag: 'c2',
builder: (c) =>
Text('计数器2: ${c.count}', style: const TextStyle(fontSize: 20)),
),
ElevatedButton(onPressed: c1.increment, child: const Text('计数器1 +1')),
ElevatedButton(onPressed: c2.increment, child: const Text('计数器2 +1')),
],
如上面,Get.put()不加tag而GetBuilder 有tag的时候,在创建的时候就会报错:
════════ Exception caught by widgets library ═══════════════════════════════════ The following _TypeError was thrown building KeyedSubtree-[GlobalKey#751ac]: Null check operator used on a null value The relevant error-causing widget was: Scaffold Scaffold:file:///Users/xieshaolin/workpalce/flutterTest/flutter_sophomore/lib/mainGetBuilderTag2.dart:35:12 When the exception was thrown, this was the stack:
#0 GetBuilderState.initState (package:get/get_state_manager/src/simple/get_state.dart:134:40) get_state.dart:134
是用init的方式
案例
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(home: CounterPage());
}
}
class CounterController extends GetxController {
int count = 0;
void increment() {
count++;
update();
}
}
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('有 tag + init')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 左边计数器
GetBuilder<CounterController>(
init: CounterController(), // 创建实例
tag: 'c1', // 指定唯一tag
builder: (c) => Column(
children: [
Text('计数器1: ${c.count}', style: const TextStyle(fontSize: 20)),
ElevatedButton(
onPressed: c.increment,
child: const Text('计数器1 +1'),
),
],
),
),
const SizedBox(height: 40),
// 右边计数器
GetBuilder<CounterController>(
init: CounterController(),
tag: 'c2',
builder: (c) => Column(
children: [
Text('计数器2: ${c.count}', style: const TextStyle(fontSize: 20)),
ElevatedButton(
onPressed: c.increment,
child: const Text('计数器2 +1'),
),
],
),
),
],
),
);
}
}
init做了什么
GetBuilder<CounterController>(
init: CounterController(), // 创建实例
tag: 'c1', // 指定唯一tag
builder: (c) => Column(
children: [
Text('计数器1: ${c.count}', style: const TextStyle(fontSize: 20)),
ElevatedButton(
onPressed: c.increment,
child: const Text('计数器1 +1'),
),
],
),
),
init会在GetBuilder内部自动执行Get.put(init, tag: tag),所以你不需要再外部Get.put一次,GetBuilder会帮你注册- 最后在
GetBuilderdispose 时,自动调用Get.delete<CounterController>()释放实例 - 所以它是局部、生命周期跟随这个 widget 的实例。
和手动 Get.put 对比
| 场景 | 用 Get.put() |
用 init: |
|---|---|---|
| 控制器只用在一个地方 | 可用,但要手动管理删除 | 更推荐,生命周期自动管理 |
| 控制器要跨页面复用 / 全局状态 | 必须用 Get.put() 注册 |
init: 每次都会生成新实例,不能共享 |
| 想用 tag 区分多个实例 | Get.put(..., tag) + GetBuilder(tag) |
init: + tag 也行,效果等价 |