Flutter - getx 控制器在数据更改时未更新
Flutter - getx controller not updated when data changed
我正在开发一个具有五个页面的 bottomnavitaionbar 的应用程序。我用getx。在第一页,我列出了数据。我的问题是,当我从数据库手动更改数据(bottomnavigationbar 中的第一页)然后我传递页面时,回到第一页我看不到更改。
控制器;
class ExploreController extends GetxController {
var isLoading = true.obs;
var articleList = List<ExploreModel>().obs;
@override
void onInit() {
fetchArticles();
super.onInit();
}
void fetchArticles() async {
try {
isLoading(true);
var articles = await ApiService.fetchArticles();
if (articles != null) {
//articleList.clear();
articleList.assignAll(articles);
}
} finally {
isLoading(false);
}
update();
}
}
和我的 UI;
body: SafeArea(
child: Column(
children: <Widget>[
Header(),
Expanded(
child: GetX<ExploreController>(builder: (exploreController) {
if (exploreController.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),
);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {
GetX< ExploreController >(builder: (controller) {
if (controller.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: controller.articleList.length,
separatorBuilder: (BuildContext context, int index) {});
});
您在这里不需要 GetBuilder,因为它不适用于可观察变量。您也不需要在 fetchArticles 函数中调用 update(),因为它仅用于 GetBuilder 和不可观察变量。
所以您有 2 个小部件用于更新 UI(GetBuilder 和 Obx),它们都遵循相同的控制器,您只需要 OBX。所以 Rahuls 的答案有效,或者您可以将 Obx 留在原地,摆脱 GetBuilder 并在构建方法的开头声明和初始化控制器。
final exploreController = Get.put(ExploreController());
然后在您的 OBX 小部件中使用该初始化控制器作为您的 Expanded 的子项。
Obx(() => exploreController.isLoading.value
? Center(
child:
SpinKitChasingDots(color: Colors.deepPurple[600], size: 40),
)
: ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {},
),
)
如果更改数据库中的值'manually',则需要一个 STREAM 来监听数据库中的更改。
你不能这样做:
var articles = await ApiService.fetchArticles();
你需要做这样的事情:
var articles = await ApiService.listenToArticlesSnapshot();
你解释的方式就像你需要在导航到另一个页面并单击按钮后刷新数据,然后导航到第一页 (GetBuilder) 或自动从第一页 (Obx) 添加数据.但是您的情况很简单,只需检索文章 SNAPSHOT,然后在控制器 onInit 中,使用 bindStream 方法订阅快照,并最终使用函数 ever() 对可观察文章列表中的任何更改做出反应。
像这样:
GetX 不知道/看不到数据库数据何时更改/更新。
您需要告诉 GetX 在适当的时候重建。
如果您将 GetX observables
与 GetX
或 Obx
小部件一起使用,那么您只需为 observable
字段分配一个新值。 obs
值更改时将进行重建。
如果您将 GetX 与 GetBuilder<MyController>
一起使用,则需要在 MyController
中调用 update()
方法来重建 GetBuilder<MyController>
个小部件。
下面的解决方案使用 GetX 控制器(即 TabX
)来:
保持应用程序状态:
- 所有标签列表(
tabPages
)
- 哪个选项卡处于活动状态 (
selectedIndex
)
公开更改 active/visible 选项卡的方法 (onItemTapped()
)
OnItemTapped()
此方法在 TabX
、GetXController 中。
调用时,它将:
- 设置哪个选项卡可见
- 将查看的选项卡保存到数据库中(
FakeDB
)
- 使用
update()
重建任何 GetBuilder 小部件
void onItemTapped(int index) {
selectedIndex = index;
db.insertViewedPage(index); // simulate database update while tabs change
update(); // ← rebuilds any GetBuilder<TabX> widget
}
完整示例
Copy/paste 将整个代码放入您应用程序的飞镖页面中,以查看有效的 BottomNavigationBar 页面。
此选项卡式/BottomNavigationBar 示例取自
https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html
但已编辑为使用 GetX。
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyTabHomePage(),
);
}
}
class FakeDB {
List<int> viewedPages = [0];
void insertViewedPage(int page) {
viewedPages.add(page);
}
}
/// BottomNavigationBar page converted to GetX. Original StatefulWidget version:
/// https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html
class TabX extends GetxController {
TabX({this.db});
final FakeDB db;
int selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
List<Widget> tabPages;
@override
void onInit() {
super.onInit();
tabPages = <Widget>[
ListViewTab(db),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
}
/// INTERESTING PART HERE ↓ ************************************
void onItemTapped(int index) {
selectedIndex = index;
db.insertViewedPage(index); // simulate database update while tabs change
update(); // ← rebuilds any GetBuilder<TabX> widget
// ↑ update() is like setState() to anything inside a GetBuilder using *this*
// controller, i.e. GetBuilder<TabX>
// Other GetX controllers are not affected. e.g. GetBuilder<BlahX>, not affected
// by this update()
// Use async/await above if data writes are slow & must complete before updating widget.
// This example does not.
}
}
/// REBUILT when Tab Page changes, rebuilt by GetBuilder in MyTabHomePage
class ListViewTab extends StatelessWidget {
final FakeDB db;
ListViewTab(this.db);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: db.viewedPages.length,
itemBuilder: (context, index) =>
ListTile(
title: Text('Page Viewed: ${db.viewedPages[index]}'),
),
);
}
}
class MyTabHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
Get.put(TabX(db: FakeDB()));
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
/// ↓ Tab Page currently visible - rebuilt by GetBuilder when
/// ↓ TabX.onItemTapped() called
child: GetBuilder<TabX>(
builder: (tx) => tx.tabPages.elementAt(tx.selectedIndex)
),
),
/// ↓ BottomNavBar's highlighted/active item, rebuilt by GetBuilder when
/// ↓ TabX.onItemTapped() called
bottomNavigationBar: GetBuilder<TabX>(
builder: (tx) => BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: tx.selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: tx.onItemTapped,
),
),
);
}
}
感谢@Baker 的正确答案。但是,如果您有一个列表并且在 viewModel 中并且想要更新该列表,只需在列表更新时使用 list.refresh()
RxList<Models> myList = <Models>[].obs;
当添加或插入数据时像这样:
myList.add(newItem);
myList.refresh();
创建
final exploreController = Get.put(ExploreController());
添加
初始化:ExploreController();
body: SafeArea(
child: Column(
children: <Widget>[
Header(),
Expanded(
child: GetX<ExploreController>(builder: (exploreController) {
*** here ***
init: ExploreController();
if (exploreController.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),
);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {
在 ui 端使用 GetxBuilder
方法,您希望在函数 update();
中更新简单调用 built
我正在开发一个具有五个页面的 bottomnavitaionbar 的应用程序。我用getx。在第一页,我列出了数据。我的问题是,当我从数据库手动更改数据(bottomnavigationbar 中的第一页)然后我传递页面时,回到第一页我看不到更改。
控制器;
class ExploreController extends GetxController {
var isLoading = true.obs;
var articleList = List<ExploreModel>().obs;
@override
void onInit() {
fetchArticles();
super.onInit();
}
void fetchArticles() async {
try {
isLoading(true);
var articles = await ApiService.fetchArticles();
if (articles != null) {
//articleList.clear();
articleList.assignAll(articles);
}
} finally {
isLoading(false);
}
update();
}
}
和我的 UI;
body: SafeArea(
child: Column(
children: <Widget>[
Header(),
Expanded(
child: GetX<ExploreController>(builder: (exploreController) {
if (exploreController.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),
);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {
GetX< ExploreController >(builder: (controller) {
if (controller.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: controller.articleList.length,
separatorBuilder: (BuildContext context, int index) {});
});
您在这里不需要 GetBuilder,因为它不适用于可观察变量。您也不需要在 fetchArticles 函数中调用 update(),因为它仅用于 GetBuilder 和不可观察变量。
所以您有 2 个小部件用于更新 UI(GetBuilder 和 Obx),它们都遵循相同的控制器,您只需要 OBX。所以 Rahuls 的答案有效,或者您可以将 Obx 留在原地,摆脱 GetBuilder 并在构建方法的开头声明和初始化控制器。
final exploreController = Get.put(ExploreController());
然后在您的 OBX 小部件中使用该初始化控制器作为您的 Expanded 的子项。
Obx(() => exploreController.isLoading.value
? Center(
child:
SpinKitChasingDots(color: Colors.deepPurple[600], size: 40),
)
: ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {},
),
)
如果更改数据库中的值'manually',则需要一个 STREAM 来监听数据库中的更改。 你不能这样做:
var articles = await ApiService.fetchArticles();
你需要做这样的事情:
var articles = await ApiService.listenToArticlesSnapshot();
你解释的方式就像你需要在导航到另一个页面并单击按钮后刷新数据,然后导航到第一页 (GetBuilder) 或自动从第一页 (Obx) 添加数据.但是您的情况很简单,只需检索文章 SNAPSHOT,然后在控制器 onInit 中,使用 bindStream 方法订阅快照,并最终使用函数 ever() 对可观察文章列表中的任何更改做出反应。 像这样:
GetX 不知道/看不到数据库数据何时更改/更新。
您需要告诉 GetX 在适当的时候重建。
如果您将 GetX observables
与 GetX
或 Obx
小部件一起使用,那么您只需为 observable
字段分配一个新值。 obs
值更改时将进行重建。
如果您将 GetX 与 GetBuilder<MyController>
一起使用,则需要在 MyController
中调用 update()
方法来重建 GetBuilder<MyController>
个小部件。
下面的解决方案使用 GetX 控制器(即 TabX
)来:
保持应用程序状态:
- 所有标签列表(
tabPages
) - 哪个选项卡处于活动状态 (
selectedIndex
)
- 所有标签列表(
公开更改 active/visible 选项卡的方法 (
onItemTapped()
)
OnItemTapped()
此方法在 TabX
、GetXController 中。
调用时,它将:
- 设置哪个选项卡可见
- 将查看的选项卡保存到数据库中(
FakeDB
) - 使用
update()
重建任何 GetBuilder 小部件
void onItemTapped(int index) {
selectedIndex = index;
db.insertViewedPage(index); // simulate database update while tabs change
update(); // ← rebuilds any GetBuilder<TabX> widget
}
完整示例
Copy/paste 将整个代码放入您应用程序的飞镖页面中,以查看有效的 BottomNavigationBar 页面。
此选项卡式/BottomNavigationBar 示例取自 https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html 但已编辑为使用 GetX。
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyTabHomePage(),
);
}
}
class FakeDB {
List<int> viewedPages = [0];
void insertViewedPage(int page) {
viewedPages.add(page);
}
}
/// BottomNavigationBar page converted to GetX. Original StatefulWidget version:
/// https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html
class TabX extends GetxController {
TabX({this.db});
final FakeDB db;
int selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
List<Widget> tabPages;
@override
void onInit() {
super.onInit();
tabPages = <Widget>[
ListViewTab(db),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
}
/// INTERESTING PART HERE ↓ ************************************
void onItemTapped(int index) {
selectedIndex = index;
db.insertViewedPage(index); // simulate database update while tabs change
update(); // ← rebuilds any GetBuilder<TabX> widget
// ↑ update() is like setState() to anything inside a GetBuilder using *this*
// controller, i.e. GetBuilder<TabX>
// Other GetX controllers are not affected. e.g. GetBuilder<BlahX>, not affected
// by this update()
// Use async/await above if data writes are slow & must complete before updating widget.
// This example does not.
}
}
/// REBUILT when Tab Page changes, rebuilt by GetBuilder in MyTabHomePage
class ListViewTab extends StatelessWidget {
final FakeDB db;
ListViewTab(this.db);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: db.viewedPages.length,
itemBuilder: (context, index) =>
ListTile(
title: Text('Page Viewed: ${db.viewedPages[index]}'),
),
);
}
}
class MyTabHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
Get.put(TabX(db: FakeDB()));
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
/// ↓ Tab Page currently visible - rebuilt by GetBuilder when
/// ↓ TabX.onItemTapped() called
child: GetBuilder<TabX>(
builder: (tx) => tx.tabPages.elementAt(tx.selectedIndex)
),
),
/// ↓ BottomNavBar's highlighted/active item, rebuilt by GetBuilder when
/// ↓ TabX.onItemTapped() called
bottomNavigationBar: GetBuilder<TabX>(
builder: (tx) => BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: tx.selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: tx.onItemTapped,
),
),
);
}
}
感谢@Baker 的正确答案。但是,如果您有一个列表并且在 viewModel 中并且想要更新该列表,只需在列表更新时使用 list.refresh()
RxList<Models> myList = <Models>[].obs;
当添加或插入数据时像这样:
myList.add(newItem);
myList.refresh();
创建 final exploreController = Get.put(ExploreController());
添加 初始化:ExploreController();
body: SafeArea(
child: Column(
children: <Widget>[
Header(),
Expanded(
child: GetX<ExploreController>(builder: (exploreController) {
*** here ***
init: ExploreController();
if (exploreController.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),
);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {
在 ui 端使用 GetxBuilder
方法,您希望在函数 update();