Riverpod 2.6.1中提供者构建方法重复执行(引发重复API请求)及编辑时状态值无法保存的问题求助
Riverpod 2.6.1中提供者构建方法重复执行(引发重复API请求)及编辑时状态值无法保存的问题求助
大家好,我现在用Riverpod 2.6.1配合代码生成(@riverpod注解)开发Flutter应用,遇到两个棘手的问题,想请教下各位:
第一个问题是Provider的build方法在App启动时会执行两次,直接导致重复的网络请求,哪怕是最基础的Provider设置也会出现这个情况。我在applyForMembershipControllerProvider的build逻辑里做了API调用,每次打开ApplyForMembershipScreen都会触发两次请求,这显然不符合预期。
第二个问题是编辑表单时状态值没法正确同步保存。比如在MembershipDetailsForm里,我用TextEditingController绑定输入框,但每次更新表单数据后,控制器的文本没法及时同步;我尝试在didChangeDependencies里初始化控制器,还加了ref.listen监听,但逻辑绕来绕去,有时候还是不生效——比如修改输入后切换步骤再切回来,输入框的内容又回到旧值了。
相关代码片段
ApplyForMembershipScreen 核心代码
class ApplyForMembershipScreen extends ConsumerStatefulWidget with AppBaseUtils { final String serviceId; final String? applicationId; ApplyForMembershipScreen({super.key, required this.serviceId, this.applicationId}); @override ConsumerState<ApplyForMembershipScreen> createState() => _ApplyForMembershipScreenState(); } class _ApplyForMembershipScreenState extends ConsumerState<ApplyForMembershipScreen> { @override void initState() { super.initState(); } @override Widget build(BuildContext context) { final membershipFormKey = GlobalKey<FormState>(); final stepperList = ['Membership Details', 'Certificate Details', 'Attachments']; final provider = applyForMembershipControllerProvider(applicationId: widget.applicationId); final asyncState = ref.watch(provider); final controller = ref.read(provider.notifier); return AppScaffold( dismissKeyboardOnTap: true, hideWhiteBackground: true, appBar: AppBar( title: AppText('Apply for Membership', style: AppTextStyles.heading1, copyWith: (c) => c.copyWith(color: Colors.white)), ), child: Container( decoration: BoxDecoration( color: AppColorPallete.whiteColor, borderRadius: BorderRadius.only(topLeft: AppTheme.AppCardRadius, topRight: AppTheme.AppCardRadius), ), padding: const EdgeInsets.all(15), child: asyncState.when( data: (state) => Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ FormStepper(activeStep: state.currentStep, stepNames: stepperList), const SizedBox(height: 20), Expanded( child: SingleChildScrollView( child: Column( children: [ switch (state.currentStep) { 0 => MembershipDetailsForm(state, controller, formKey: membershipFormKey), 1 => CertificateDetailsForm(state, controller, formKey: membershipFormKey), 2 => AttachmentsForm(state, controller, formKey: membershipFormKey), _ => const SizedBox.shrink(), }, SaveAsDraftButton( onPressed: () { controller.submitForm(context, serviceId: widget.serviceId, isDraft: true); }, ), ], ), ), ), ], ), loading: () => const Loader(), error: (e, st) => AppStateError(error: e), ), ), bottomNavigationBar: asyncState.whenOrNull( skipError: true, data: (state) => FormBottomNavigationBar( isFinalStep: state.currentStep == (stepperList.length - 1), onPreviousTap: state.currentStep >= 1 ? () { final isValid = membershipFormKey.currentState?.validate() ?? false; if (isValid) { membershipFormKey.currentState?.save(); } controller.previousStep(); } : null, onCancelTap: state.currentStep >= 1 ? null : () { controller.resetForm(); }, onNextTap: () { final isValid = membershipFormKey.currentState?.validate() ?? false; if (isValid) { membershipFormKey.currentState?.save(); controller.nextStep(); } }, onSubmit: () { final isValid = membershipFormKey.currentState?.validate() ?? false; if (isValid) { membershipFormKey.currentState?.save(); controller.submitForm(context, serviceId: widget.serviceId); } }, ), ), ); } }
MembershipDetailsForm 核心代码
class MembershipDetailsForm extends ConsumerStatefulWidget { final GlobalKey<FormState> formKey; final ApplyForMembershipState state; final ApplyForMembershipController controller; const MembershipDetailsForm(this.state, this.controller, {super.key, required this.formKey}); @override ConsumerState<MembershipDetailsForm> createState() => _MembershipDetailsState(); } class _MembershipDetailsState extends ConsumerState<MembershipDetailsForm> { bool _hasListenerAttached = false; bool _controllersInitialized = false; TextEditingController _identityTextCtrl = TextEditingController(); TextEditingController _membershipTextCtrl = TextEditingController(); @override void didChangeDependencies() { super.didChangeDependencies(); final asyncState = ref.read(applyForMembershipControllerProvider()); asyncState.whenData((state) { if (!_controllersInitialized) { _identityTextCtrl.text = state.form.identityNumber ?? ''; _membershipTextCtrl.text = state.form.membershipNumber ?? ''; _controllersInitialized = true; // Set default if null if (state.form.anyPreviousMembership == null) { ref.read(applyForMembershipControllerProvider().notifier).updateForm((form) => form.copyWith(anyPreviousMembership: false)); } } }); } @override void dispose() { _identityTextCtrl.dispose(); _membershipTextCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final formKey = widget.formKey; // 临时测试用的监听,后续想删掉但暂时没找到更好的办法 if (!_hasListenerAttached) { ref.listen<AsyncValue<ApplyForMembershipState>>(applyForMembershipControllerProvider(), (previous, next) { next.whenData((data) { debugPrint('邏MembershipDetailsForm邏 Updated From Lisner'); _identityTextCtrl.text = data.form.identityNumber ?? ''; _membershipTextCtrl.text = data.form.membershipNumber ?? ''; }); }); _hasListenerAttached = true; } return Form( key: formKey, child: Column( children: [ TextFormField( controller: _identityTextCtrl, decoration: const InputDecoration(labelText: 'Identity Number'), validator: (value) => value?.isEmpty ?? true ? 'Please enter identity number' : null, onSaved: (value) => widget.controller.updateForm((form) => form.copyWith(identityNumber: value)), ), TextFormField( controller: _membershipTextCtrl, decoration: const InputDecoration(labelText: 'Membership Number'), onSaved: (value) => widget.controller.updateForm((form) => form.copyWith(membershipNumber: value)), ), // 其他表单字段... ], ), ); } }
Provider 大致结构(@riverpod生成)
@riverpod class ApplyForMembershipController extends _$ApplyForMembershipController { @override FutureOr<ApplyForMembershipState> build({String? applicationId}) async { // 这里的API调用会执行两次 final initialData = await ref.read(apiServiceProvider).getMembershipApplication(applicationId); return ApplyForMembershipState( currentStep: 0, form: initialData.form, ); } void updateForm(ApplyForMembershipForm Function(ApplyForMembershipForm) update) { state = AsyncData(state.value!.copyWith(form: update(state.value!.form))); } // nextStep、previousStep、submitForm等其他方法... }
我的疑问
- 为什么Provider的build方法会触发两次?有没有办法彻底避免重复的API请求?
- 表单输入框和Riverpod状态之间的同步,有没有更优雅的实现方式?不想用这么多TextEditingController和监听来兜底...
内容来源于stack exchange
相关产品推荐
相关产品推荐

