Flutter with Sqflite + Getx 数据更新问题
Flutter with Sqflite + Getx data update issues
我目前在Flutter中使用GetX和Sqflite,遇到插入后数据更新的问题。
首先,有一个 LoadingScreen 检查用户数据库中是否有任何条目。如果 none,则它会打开用户填写的注册页面。如果用户数据库中已有条目,则打开登录页面。
注册页面完成后,数据将写入用户数据库,然后用户将被带到主页,用户名显示在抽屉中。
用户在 LoadingScreenController 中使用 .obs,我在主页中使用 Obx 作为用户名。尽管如此,插入命令完成后的数据并没有自动更新。
但是,如果我在HomePage的init中再次调用fetch函数,那么新的数据就会被引入。
我的问题是,每次数据库更新时我都必须 运行 获取函数吗?这不违背 Getx 的目的吗?显然,我哪里错了。
下面是主页、LoadingScreen、LoadingScreenController、注册页面和数据库助手的代码。
在此方面如有任何帮助,我们将不胜感激。
主页代码:
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
LoadingController loadingController = Get.find();
var notifyHelper;
@override
void initState() {
super.initState();
notifyHelper = NotifyHelper();
notifyHelper.initializeNotification();
notifyHelper.requestIOSPermissions();
username = loadingController.users[0].name.toString();
usermail = loadingController.users[0].mail.toString();
}
String name = '', usernames = '', usermail = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appbar(context),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Center(
child: Obx(() => Text(loadingController.users.length.toString())),
),
),
drawer: MyDrawer(usernames, usermail),
);
}
_appbar(BuildContext context) {
return AppBar(
//leading: Drawer(),
actions: [
const Icon(
Icons.person,
size: 20.0,
),
],
);
}
}
加载屏幕
class LoadingScreen extends StatelessWidget {
final loadingController = Get.put(LoadingController());
final splashController = Get.put(SplashController());
final TextEditingController passwordController = TextEditingController();
int index = 0;
LoadingScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
//backgroundColor: Colors.black,
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
const SizedBox(
height: 20,
),
_splash(context),
GetBuilder<SplashController>(
builder: (_) => splashController.completes.value == false
? MyTextContainer(
20.0,
'Counting Your Money ... \n Please wait',
.4,
true,
subTitleStyle)
: const Text('')),
GetBuilder<LoadingController>(
builder: (_) => loadingController.users.length > 0
? _signin(
context, loadingController.users[0].name.toString())
: _signup(context),
),
],
),
),
),
);
}
onTap() {
if (passwordController.text !=
loadingController.users[0].password.toString()) {
Get.snackbar('Alert', 'Passwords do not match',
snackPosition: SnackPosition.BOTTOM);
} else {
Get.to(() => const Home());
}
}
Widget _splash(BuildContext context) {
return Center(
child: GetBuilder<SplashController>(
builder: (_) => ScaleTransition(
scale: splashController.anim,
child: Container(
margin: const EdgeInsets.only(left: 8.0, top: 8.0, right: 8.0),
padding: const EdgeInsets.only(bottom: 12.0),
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [primaryClr, yellowClr],
),
borderRadius: BorderRadius.circular(18),
border: Border.all(color: Colors.white, width: 2.0),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Lottie.asset('assets/loadingScreen.json',
height: MediaQuery.of(context).size.height * .40),
Text('Welcome ', style: titleStyle),
],
)),
),
));
}
Widget _signin(BuildContext context, String username) {
return GetBuilder<SplashController>(
builder: (_) => ScaleTransition(
scale: splashController.anim,
child: Container(
margin: const EdgeInsets.only(left: 10.0, right: 10.0),
child: Column(
children: [
Text(
"Welome back $username \nEnter Password to Sign-In",
textAlign: TextAlign.center,
),
MyTextInputField(
title: 'Password',
hint: 'Enter Your Password',
textStyle: subTitleStyle,
textInputType: TextInputType.number,
textCapital: TextCapitalization.none,
controller: passwordController,
isObscure: true,
),
const SizedBox(
height: 20.0,
),
passwordController.text.isEmpty
? Container()
: MyButton(
label: 'SignIn',
onTap: onTap,
textStyle: titleStyle,
colorScheme: primaryClr,
),
],
),
),
),
);
}
Widget _signup(BuildContext context) {
return GetBuilder<SplashController>(
builder: (_) => ScaleTransition(
scale: splashController.anim,
child: Column(
children: [
Text(
"This is your first Login \nClick here to Sign-Up",
style: subTitleStyle,
),
const SizedBox(
height: 20.0,
),
MyButton(
label: 'Sign-Up',
textStyle: titleStyle,
onTap: () => Get.to(() => const SignUp()),
colorScheme: primaryClr,
),
],
),
),
);
}
}
加载屏幕控制器
class LoadingController extends GetxController {
// ignore: deprecated_member_use
//var allacc = List<AllAccounts>().obs;
var users = <User>[].obs;
var allaccounts = <AllAccounts>[].obs;
List<AllAccounts> allacc = <AllAccounts>[].obs;
@override
void onInit() async {
await fetchUsers();
await fetchAllAccounts();
super.onInit();
}
fetchUsers() async {
var userList = await DatabaseHelper.db.getAllUsers();
users.assignAll(userList);
update();
}
fetchAllAccounts() async {
await DatabaseHelper.db.getAllAccounts().then((accountList) {
allaccounts.value = accountList.cast<AllAccounts>();
update();
});
}
Future<void> insertUser({User? user}) async {
await DatabaseHelper.db.insertUser(user!);
update();
}
}
DatabaseHelper(没有数据库创建代码)
Future<int> insertUser(User user) async {
Database dbase = await database;
var res = dbase.insert(User.tblUser, user.toMap());
return res;
}
Future<List<User>> getAllUsers() async {
Database db = await database;
List<Map<String, dynamic>> username = await db.rawQuery('''
SELECT COALESCE(${User.colUserId},'') as 'id', COALESCE(${User.colUserName},'') as 'name',
COALESCE(${User.colUserPhone},'') as 'phone', COALESCE(${User.colUserMail},'9999') as 'mail',
COALESCE(${User.colUserPassword},'') as 'password', COALESCE(${User.colUserStartDate},'') as 'startdate'
FROM ${User.tblUser}
''');
//print('Printing from db $usernames');
return username.isEmpty
? []
: username.map((e) => User.fromMap(e)).toList();
}
注册
class SignUp extends StatefulWidget {
const SignUp({Key? key}) : super(key: key);
@override
State<SignUp> createState() => _SignUpState();
}
class _SignUpState extends State<SignUp> {
final TextEditingController nameController = TextEditingController();
final TextEditingController phoneController = TextEditingController();
final TextEditingController mailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final TextEditingController repasswordController = TextEditingController();
final TextEditingController startdateController = TextEditingController();
final LoadingController loadingController = Get.find();
String alertmsg = '';
Future<void> onTap() async {
if (_validateText() == null) {
_showSnackBar('Writing Data... Please wait');
_addDataToDB();
}
//Get.to(() => Home());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appbar(context),
body: SafeArea(
child: Container(
padding: const EdgeInsets.only(left: 20, top: 10, right: 20),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Sign-Up', style: headingStyle),
MyTextInputField(
title: 'Name',
hint: 'Enter First Name and Last Name here',
textStyle: subTitleStyle,
textInputType: TextInputType.text,
textCapital: TextCapitalization.words,
controller: nameController,
),
MyTextInputField(
title: 'Phone Number',
hint: 'Phone Number is to send reminders - Required',
textStyle: subTitleStyle,
textInputType: TextInputType.number,
textCapital: TextCapitalization.none,
controller: phoneController,
),
MyTextInputField(
title: 'Mail ID',
hint: 'Mail ID is to back up the Data - Required',
textStyle: subTitleStyle,
textInputType: TextInputType.emailAddress,
textCapital: TextCapitalization.none,
controller: mailController,
),
MyTextInputField(
title: 'Password',
hint: 'Enter Pasword Here - Minimum of 4 digits',
textStyle: subTitleStyle,
textInputType: TextInputType.number,
textCapital: TextCapitalization.none,
controller: passwordController,
isObscure: true,
),
MyTextInputField(
title: 'Confirm Password',
hint: 'Re-enter Your Pasword Here',
textStyle: subTitleStyle,
textInputType: TextInputType.number,
textCapital: TextCapitalization.none,
controller: repasswordController,
isObscure: true,
),
MyTextInputField(
title: 'Start Date',
hint: 'Date from when the Transactions start',
textStyle: subTitleStyle,
textInputType: TextInputType.number,
textCapital: TextCapitalization.none,
controller: startdateController,
widget: IconButton(
icon: const Icon(Icons.calendar_today_outlined),
onPressed: () {
print('Printed');
},
)),
const SizedBox(
height: 20.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
MyButton(
label: 'Confirm',
onTap: onTap,
textStyle: titleStyle,
colorScheme: primaryClr,
)
],
)
],
),
),
),
));
}
_addDataToDB() async {
await loadingController
.insertUser(
user: User(
name: nameController.text,
phone: phoneController.text,
mail: mailController.text,
password: passwordController.text,
startdate: DateTime.now().toString(),
created: DateTime.now().toString()))
.then((value) {
_showSnackBar('Data Saved');
Get.to(() => Home());
});
}
_validateText() {
if (nameController.text.isEmpty) {
alertmsg = 'Name is Required';
_showSnackBar(alertmsg);
return alertmsg;
} else if (_validatePhone(phoneController.text) != null) {
_showSnackBar(alertmsg);
return alertmsg;
} else if (_validateMail(mailController.text) != null) {
_showSnackBar(alertmsg);
return alertmsg;
} else if (_validatePassword(
passwordController.text, repasswordController.text) !=
null) {
_showSnackBar(alertmsg);
return alertmsg;
}
return null;
}
String? _validatePhone(String value) {
if (value.isEmpty || value.length < 10) {
alertmsg = 'Please enter valid Phone Number';
// _showSnackBar(alertmsg);
return alertmsg;
}
return null;
}
String? _validateMail(String value) {
bool emailValid = RegExp(
r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+")
.hasMatch(value);
if (!emailValid) {
alertmsg = 'Please enter valid mail ID';
return alertmsg;
}
return null;
}
String? _validatePassword(String value1, String value2) {
print('$value1 $value2');
if (!value1.isNum && value1.length < 4) {
alertmsg = 'Password should be atleast a 4 digit Number ';
_showSnackBar(alertmsg);
return alertmsg;
} else if (value2.isEmpty) {
alertmsg = 'Confirm Password';
_showSnackBar(alertmsg);
return alertmsg;
} else if (value1 != value2) {
alertmsg = 'Passwords do not match';
_showSnackBar(alertmsg);
return alertmsg;
}
}
_showSnackBar(String value) {
Get.snackbar('Alert', value, snackPosition: SnackPosition.BOTTOM);
}
_appbar(BuildContext context) {
return AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
leading: InkWell(
onTap: () {
Get.back();
},
child: const Icon(Icons.arrow_back),
),
);
}
}
您必须在每次 add/update/delete 操作后调用获取数据,除非您的数据库以某种方式 returns 一个流。它并没有违背 GetX 的宗旨。这是预期的行为。用户应该在代码中明确定义他们想要做什么。
另一件事:您在使用 .obs
时正在使用 GetBuilder
。这是行不通的,因为 GetBuilder
不是反应性的,并且当可观察对象 (.obs
) 发生变化时不会 react/update UI。您应该改用 GetX
或 Obx
。
我目前在Flutter中使用GetX和Sqflite,遇到插入后数据更新的问题。
首先,有一个 LoadingScreen 检查用户数据库中是否有任何条目。如果 none,则它会打开用户填写的注册页面。如果用户数据库中已有条目,则打开登录页面。
注册页面完成后,数据将写入用户数据库,然后用户将被带到主页,用户名显示在抽屉中。
用户在 LoadingScreenController 中使用 .obs,我在主页中使用 Obx 作为用户名。尽管如此,插入命令完成后的数据并没有自动更新。
但是,如果我在HomePage的init中再次调用fetch函数,那么新的数据就会被引入。
我的问题是,每次数据库更新时我都必须 运行 获取函数吗?这不违背 Getx 的目的吗?显然,我哪里错了。
下面是主页、LoadingScreen、LoadingScreenController、注册页面和数据库助手的代码。
在此方面如有任何帮助,我们将不胜感激。
主页代码:
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
LoadingController loadingController = Get.find();
var notifyHelper;
@override
void initState() {
super.initState();
notifyHelper = NotifyHelper();
notifyHelper.initializeNotification();
notifyHelper.requestIOSPermissions();
username = loadingController.users[0].name.toString();
usermail = loadingController.users[0].mail.toString();
}
String name = '', usernames = '', usermail = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appbar(context),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Center(
child: Obx(() => Text(loadingController.users.length.toString())),
),
),
drawer: MyDrawer(usernames, usermail),
);
}
_appbar(BuildContext context) {
return AppBar(
//leading: Drawer(),
actions: [
const Icon(
Icons.person,
size: 20.0,
),
],
);
}
}
加载屏幕
class LoadingScreen extends StatelessWidget {
final loadingController = Get.put(LoadingController());
final splashController = Get.put(SplashController());
final TextEditingController passwordController = TextEditingController();
int index = 0;
LoadingScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
//backgroundColor: Colors.black,
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
const SizedBox(
height: 20,
),
_splash(context),
GetBuilder<SplashController>(
builder: (_) => splashController.completes.value == false
? MyTextContainer(
20.0,
'Counting Your Money ... \n Please wait',
.4,
true,
subTitleStyle)
: const Text('')),
GetBuilder<LoadingController>(
builder: (_) => loadingController.users.length > 0
? _signin(
context, loadingController.users[0].name.toString())
: _signup(context),
),
],
),
),
),
);
}
onTap() {
if (passwordController.text !=
loadingController.users[0].password.toString()) {
Get.snackbar('Alert', 'Passwords do not match',
snackPosition: SnackPosition.BOTTOM);
} else {
Get.to(() => const Home());
}
}
Widget _splash(BuildContext context) {
return Center(
child: GetBuilder<SplashController>(
builder: (_) => ScaleTransition(
scale: splashController.anim,
child: Container(
margin: const EdgeInsets.only(left: 8.0, top: 8.0, right: 8.0),
padding: const EdgeInsets.only(bottom: 12.0),
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [primaryClr, yellowClr],
),
borderRadius: BorderRadius.circular(18),
border: Border.all(color: Colors.white, width: 2.0),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Lottie.asset('assets/loadingScreen.json',
height: MediaQuery.of(context).size.height * .40),
Text('Welcome ', style: titleStyle),
],
)),
),
));
}
Widget _signin(BuildContext context, String username) {
return GetBuilder<SplashController>(
builder: (_) => ScaleTransition(
scale: splashController.anim,
child: Container(
margin: const EdgeInsets.only(left: 10.0, right: 10.0),
child: Column(
children: [
Text(
"Welome back $username \nEnter Password to Sign-In",
textAlign: TextAlign.center,
),
MyTextInputField(
title: 'Password',
hint: 'Enter Your Password',
textStyle: subTitleStyle,
textInputType: TextInputType.number,
textCapital: TextCapitalization.none,
controller: passwordController,
isObscure: true,
),
const SizedBox(
height: 20.0,
),
passwordController.text.isEmpty
? Container()
: MyButton(
label: 'SignIn',
onTap: onTap,
textStyle: titleStyle,
colorScheme: primaryClr,
),
],
),
),
),
);
}
Widget _signup(BuildContext context) {
return GetBuilder<SplashController>(
builder: (_) => ScaleTransition(
scale: splashController.anim,
child: Column(
children: [
Text(
"This is your first Login \nClick here to Sign-Up",
style: subTitleStyle,
),
const SizedBox(
height: 20.0,
),
MyButton(
label: 'Sign-Up',
textStyle: titleStyle,
onTap: () => Get.to(() => const SignUp()),
colorScheme: primaryClr,
),
],
),
),
);
}
}
加载屏幕控制器
class LoadingController extends GetxController {
// ignore: deprecated_member_use
//var allacc = List<AllAccounts>().obs;
var users = <User>[].obs;
var allaccounts = <AllAccounts>[].obs;
List<AllAccounts> allacc = <AllAccounts>[].obs;
@override
void onInit() async {
await fetchUsers();
await fetchAllAccounts();
super.onInit();
}
fetchUsers() async {
var userList = await DatabaseHelper.db.getAllUsers();
users.assignAll(userList);
update();
}
fetchAllAccounts() async {
await DatabaseHelper.db.getAllAccounts().then((accountList) {
allaccounts.value = accountList.cast<AllAccounts>();
update();
});
}
Future<void> insertUser({User? user}) async {
await DatabaseHelper.db.insertUser(user!);
update();
}
}
DatabaseHelper(没有数据库创建代码)
Future<int> insertUser(User user) async {
Database dbase = await database;
var res = dbase.insert(User.tblUser, user.toMap());
return res;
}
Future<List<User>> getAllUsers() async {
Database db = await database;
List<Map<String, dynamic>> username = await db.rawQuery('''
SELECT COALESCE(${User.colUserId},'') as 'id', COALESCE(${User.colUserName},'') as 'name',
COALESCE(${User.colUserPhone},'') as 'phone', COALESCE(${User.colUserMail},'9999') as 'mail',
COALESCE(${User.colUserPassword},'') as 'password', COALESCE(${User.colUserStartDate},'') as 'startdate'
FROM ${User.tblUser}
''');
//print('Printing from db $usernames');
return username.isEmpty
? []
: username.map((e) => User.fromMap(e)).toList();
}
注册
class SignUp extends StatefulWidget {
const SignUp({Key? key}) : super(key: key);
@override
State<SignUp> createState() => _SignUpState();
}
class _SignUpState extends State<SignUp> {
final TextEditingController nameController = TextEditingController();
final TextEditingController phoneController = TextEditingController();
final TextEditingController mailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final TextEditingController repasswordController = TextEditingController();
final TextEditingController startdateController = TextEditingController();
final LoadingController loadingController = Get.find();
String alertmsg = '';
Future<void> onTap() async {
if (_validateText() == null) {
_showSnackBar('Writing Data... Please wait');
_addDataToDB();
}
//Get.to(() => Home());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appbar(context),
body: SafeArea(
child: Container(
padding: const EdgeInsets.only(left: 20, top: 10, right: 20),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Sign-Up', style: headingStyle),
MyTextInputField(
title: 'Name',
hint: 'Enter First Name and Last Name here',
textStyle: subTitleStyle,
textInputType: TextInputType.text,
textCapital: TextCapitalization.words,
controller: nameController,
),
MyTextInputField(
title: 'Phone Number',
hint: 'Phone Number is to send reminders - Required',
textStyle: subTitleStyle,
textInputType: TextInputType.number,
textCapital: TextCapitalization.none,
controller: phoneController,
),
MyTextInputField(
title: 'Mail ID',
hint: 'Mail ID is to back up the Data - Required',
textStyle: subTitleStyle,
textInputType: TextInputType.emailAddress,
textCapital: TextCapitalization.none,
controller: mailController,
),
MyTextInputField(
title: 'Password',
hint: 'Enter Pasword Here - Minimum of 4 digits',
textStyle: subTitleStyle,
textInputType: TextInputType.number,
textCapital: TextCapitalization.none,
controller: passwordController,
isObscure: true,
),
MyTextInputField(
title: 'Confirm Password',
hint: 'Re-enter Your Pasword Here',
textStyle: subTitleStyle,
textInputType: TextInputType.number,
textCapital: TextCapitalization.none,
controller: repasswordController,
isObscure: true,
),
MyTextInputField(
title: 'Start Date',
hint: 'Date from when the Transactions start',
textStyle: subTitleStyle,
textInputType: TextInputType.number,
textCapital: TextCapitalization.none,
controller: startdateController,
widget: IconButton(
icon: const Icon(Icons.calendar_today_outlined),
onPressed: () {
print('Printed');
},
)),
const SizedBox(
height: 20.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
MyButton(
label: 'Confirm',
onTap: onTap,
textStyle: titleStyle,
colorScheme: primaryClr,
)
],
)
],
),
),
),
));
}
_addDataToDB() async {
await loadingController
.insertUser(
user: User(
name: nameController.text,
phone: phoneController.text,
mail: mailController.text,
password: passwordController.text,
startdate: DateTime.now().toString(),
created: DateTime.now().toString()))
.then((value) {
_showSnackBar('Data Saved');
Get.to(() => Home());
});
}
_validateText() {
if (nameController.text.isEmpty) {
alertmsg = 'Name is Required';
_showSnackBar(alertmsg);
return alertmsg;
} else if (_validatePhone(phoneController.text) != null) {
_showSnackBar(alertmsg);
return alertmsg;
} else if (_validateMail(mailController.text) != null) {
_showSnackBar(alertmsg);
return alertmsg;
} else if (_validatePassword(
passwordController.text, repasswordController.text) !=
null) {
_showSnackBar(alertmsg);
return alertmsg;
}
return null;
}
String? _validatePhone(String value) {
if (value.isEmpty || value.length < 10) {
alertmsg = 'Please enter valid Phone Number';
// _showSnackBar(alertmsg);
return alertmsg;
}
return null;
}
String? _validateMail(String value) {
bool emailValid = RegExp(
r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+")
.hasMatch(value);
if (!emailValid) {
alertmsg = 'Please enter valid mail ID';
return alertmsg;
}
return null;
}
String? _validatePassword(String value1, String value2) {
print('$value1 $value2');
if (!value1.isNum && value1.length < 4) {
alertmsg = 'Password should be atleast a 4 digit Number ';
_showSnackBar(alertmsg);
return alertmsg;
} else if (value2.isEmpty) {
alertmsg = 'Confirm Password';
_showSnackBar(alertmsg);
return alertmsg;
} else if (value1 != value2) {
alertmsg = 'Passwords do not match';
_showSnackBar(alertmsg);
return alertmsg;
}
}
_showSnackBar(String value) {
Get.snackbar('Alert', value, snackPosition: SnackPosition.BOTTOM);
}
_appbar(BuildContext context) {
return AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
leading: InkWell(
onTap: () {
Get.back();
},
child: const Icon(Icons.arrow_back),
),
);
}
}
您必须在每次 add/update/delete 操作后调用获取数据,除非您的数据库以某种方式 returns 一个流。它并没有违背 GetX 的宗旨。这是预期的行为。用户应该在代码中明确定义他们想要做什么。
另一件事:您在使用 .obs
时正在使用 GetBuilder
。这是行不通的,因为 GetBuilder
不是反应性的,并且当可观察对象 (.obs
) 发生变化时不会 react/update UI。您应该改用 GetX
或 Obx
。