Flutter - контроллер getx не обновляется при изменении данных
Я разрабатываю приложение с пятистраничной нижней навигационной панелью. Я использую getx. На первой странице я перечисляю данные. Моя проблема в том, что когда я вручную изменил данные (первая страница в нижней панели навигации) из базы данных и, когда я перехожу по страницам, вернулся на первую страницу, я не увидел изменений.
Контроллер;
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();
}
}
и мой интерфейс;
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) {
8 ответов
GetX не знает / не видит, когда данные базы данных были изменены / обновлены.
Вы должны сообщить GetX о необходимости перестройки, когда это необходимо.
Если вы используете GetX
observables
с участием
GetX
или же
Obx
виджеты, то вы просто присваиваете новое значение своему
observable
поле. Восстановление произойдет, когда
obs
изменение стоимости.
Если вы используете GetX с, вам нужно вызвать метод внутри
MyController
, перестроить
GetBuilder<MyController>
виджеты.
В приведенном ниже решении используется контроллер GetX (т.е.) для:
удерживать состояние приложения:
- список всех вкладок (
tabPages
) - какая вкладка активна (
selectedIndex
)
- список всех вкладок (
выставить метод для изменения активной / видимой вкладки (
onItemTapped()
)
OnItemTapped()
Этот метод внутри
TabX
, GetXController.
При вызове он:
- установить, какая вкладка видна
- сохранить просмотренную вкладку в базу данных (
FakeDB
) - перестроить любые виджеты GetBuilder, используя
update()
void onItemTapped(int index) {
selectedIndex = index;
db.insertViewedPage(index); // simulate database update while tabs change
update(); // ← rebuilds any GetBuilder<TabX> widget
}
Полный пример
Скопируйте / вставьте весь этот код на страницу дротика в своем приложении, чтобы увидеть рабочую страницу 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()
}
}
/// 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();
GetBuilder здесь не нужен, поскольку он не предназначен для наблюдаемых переменных. Вам также не нужно вызывать update () в функции fetchArticles, поскольку это только для использования с GetBuilder и ненаблюдаемыми переменными.
Итак, у вас было 2 виджета, предназначенных для обновления пользовательского интерфейса (GetBuilder и Obx), которые работают с одним и тем же контроллером, и все, что вам нужно, это просто OBX. Итак, ответ Рахула работает, или вы можете оставить Obx на месте, избавиться от GetBuilder и объявить и инициализировать контроллер в начале вашего метода сборки.
final exploreController = Get.put(ExploreController());
Затем используйте этот инициализированный контроллер в своем виджете OBX как дочерний элемент расширенного.
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) {},
),
)
с использованием
GetxBuilder
подход на стороне пользовательского интерфейса и где вы хотите обновить простую функцию, называемую встроенной
update();
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) {});
});
Если вы изменяете значение в базе данных «вручную», вам понадобится ПОТОК, чтобы отслеживать изменения в базе данных. Вы не можете:
var articles = await ApiService.fetchArticles();
Вам нужно сделать что-то вроде этого:
var articles = await ApiService.listenToArticlesSnapshot();
Вы объяснили, как если вам нужно обновить данные после перехода на другую страницу и нажатия кнопки, а затем перехода на первую страницу (GetBuilder) ИЛИ автоматически добавляет данные с первой страницы (Obx). Но ваш случай прост: просто получите статьи SNAPSHOT, затем в контроллере onInit подпишитесь на моментальный снимок с помощью метода bindStream и в конечном итоге используйте функцию ever() для реагирования на любое изменение в наблюдаемом списке статей. Что-то вроде этого:
Самый простой способ, который я мог. В контроллере создайте obs (var indexClick = 1.obs;) На каждом тайле проверьте selected==index...; По щелчку каждого элемента измените indexClick последовательно
return Obx(() {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
ListTile(
leading: const Icon(Icons.dns),
title: const Text('Menu1'),
selected: controller.indexClick.value==1?true:false,
onTap: () {
controller.indexClick.value=1;
Navigator.pop(context);
},
),
ListTile(
leading: const Icon(Icons.search),
title: const Text('Menu2'),
selected: controller.indexClick.value==2?true:false,
onTap: () {
controller.indexClick.value=2;
Navigator.pop(context);
},
),
создать окончательный exploreController = Get.put(ExploreController());
Добавить init: 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) {