查找已停用小部件的祖先是不安全的 => 使用 Riverpod => 使用 "Navigator.of(context).pushReplacementNamed('/page'

Posted

技术标签:

【中文标题】查找已停用小部件的祖先是不安全的 => 使用 Riverpod => 使用 "Navigator.of(context).pushReplacementNamed(\'/page\');"【英文标题】:looking up a deactivated widget's ancestor is unsafe => using Riverpod => using "Navigator.of(context).pushReplacementNamed('/page');"查找已停用小部件的祖先是不安全的 => 使用 Riverpod => 使用 "Navigator.of(context).pushReplacementNamed('/page');" 【发布时间】:2021-11-08 07:29:36 【问题描述】:

我所做并导致此错误的场景:1.在我使用热重载按钮创建登录页面后 2.当我按下登录按钮并且页面状态发生变化。

最近我决定在我的flutter应用程序中使用riverpod包,所以我使用hooks_riverpod: ^1.0.0-dev.7和flutter_hooks: ^0.18.0但是当我在LoginScreen的帮助下创建我的Riverpod 和 Hooks 我遇到了一些问题,我在下面提供了我的登录信息。

登录屏幕:

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lambda/configs/sizes/index.dart';
import 'package:lambda/configs/strings.dart';
import 'package:lambda/core/validator/src/mobile_number_validator.dart';
import 'package:lambda/presentation/state_notifiers/auth/index.dart';
import 'package:lambda/presentation/utils/input_formatter/index.dart';
import 'package:lambda/presentation/widgets/alert_message/alert_messge.dart';
import 'package:lambda/presentation/widgets/background/background.dart';
import 'package:lambda/presentation/widgets/progress/progress.dart';
import 'package:lambda/presentation/widgets/spacer/spacer.dart';
import 'package:lambda/routes.dart';

class LoginScreen extends HookConsumerWidget with MobileNumberValidator 
  LoginScreen(Key? key) : super(key: key);

  @override
  Widget build(BuildContext context,WidgetRef ref) 
    ref.listen<AuthState>(authStateNotifierProvider, (state) 
      state.maybeWhen(
          orElse: () ,
          otpSent: (mobileNumber) 
            AppNavigator.replaceWith<String>(
                NavigationPaths.verifyLogin, mobileNumber);
          ,
          error: (message) 
            AlertMessage(context).warning(message);
          );
    );
    final phoneFieldController = useTextEditingController();

    return NormalBackground(
      child: Scaffold(
        body: Padding(
          padding: EdgeInsets.symmetric(
              horizontal: LayoutSizes(context).responsive(60)),
          child: Column(
            mainAxisSize: MainAxisSize.max,
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Text(
                Strings.pleaseEnterYourMobileNumberForLoginToTheLambda,
                style: Theme.of(context).textTheme.caption,
                textAlign: TextAlign.center,
              ),
              VSpacer(LayoutSizes(context).marginXXL),
              TextFormField(
                controller: phoneFieldController,
                style: Theme.of(context).textTheme.caption,
                textAlign: TextAlign.center,
                keyboardType: TextInputType.number,
                inputFormatters: [PersianNumberFormatter()],
                decoration: const InputDecoration(
                  hintText: Strings.mobileNumberHint,
                ),
              ),
              VSpacer(LayoutSizes(context).marginL),
              ref.watch(authStateNotifierProvider).maybeMap(
                orElse: () 
                  return ElevatedButton(
                    onPressed: () 
                      if (isValidIRMobileNumber(phoneFieldController.text)) 
                        ref
                            .read(authStateNotifierProvider.notifier)
                            .sendOtp(phoneFieldController.text);
                       else 
                        AlertMessage(context).warning(
                            Strings.isInvalidInput(Strings.mobileNumber));
                      
                    ,
                    style: ButtonStyle(
                      fixedSize: MaterialStateProperty.all(
                        Size(double.maxFinite,
                            LayoutSizes(context).buttonHeightL),
                      ),
                    ),
                    child: const Text(Strings.next),
                  );
                ,
                loading: (_) 
                  return const CircularProgress();
                ,
              ),
            ],
          ),
        ),
      ),
    );
  


AuthStateProviderNotifier:

import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lambda/configs/strings.dart';
import 'package:lambda/core/extensions/strings.dart';
import 'package:lambda/data/repositories/auth/authentication_repository.dart';
import 'package:lambda/services/http/index.dart';
import 'package:lambda/services/logger/logger.dart';

import 'auth_state.dart';

final authStateNotifierProvider =
    StateNotifierProvider<AuthStateNotifier, AuthState>((ref) 
  final authRepository = ref.read(authRepositoryProvider);
  return AuthStateNotifier(authRepository);
);

class AuthStateNotifier extends StateNotifier<AuthState> 
  final AuthenticationRepository _repository;

  AuthStateNotifier(this._repository) : super(const AuthState.initial());

  Future<void> sendOtp(String mobileNumber) async 
    try 
      state = const AuthState.loading();
      await _repository.sendValidationCode(
          mobileNumber: mobileNumber.convertToEnNum());
      state = AuthState.otpSent(mobileNumber: mobileNumber);
     catch (e, s) 
      _handleError(e, s);
    
  

  Future<void> verifyOtp(String mobileNumber, String code) async 
    try 
      state = const AuthState.loading();
      await _repository.login(
          mobileNumber: mobileNumber.convertToEnNum(),
          verificationCode: code.convertToEnNum());
      state = const AuthState.authenticated();
     catch (e, s) 
      _handleError(e, s);
    
  

  void _handleError(Object e, StackTrace s) 
    Logger().info('error : $e stack: $s');
    if (e is NetworkExceptionX) 
      state = AuthState.error(
          errorMessage: e.messageForUser ?? Strings.someErrorHappened);
     else 
      state = const AuthState.error(errorMessage: Strings.someErrorHappened);
    
  


运行:

======== Exception caught by widgets library =======================================================
The following assertion was thrown building LoginScreen(dirty, dependencies: [_LocalizationsScope-[GlobalKey#aacaf], UncontrolledProviderScope, _InheritedTheme], state: _ConsumerState#cf20e, useTextEditingController: TextEditingController#f5c6d(TextEditingValue(text: ┤├, selection: TextSelection(baseOffset: -1, extentOffset: -1, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1)))):
Looking up a deactivated widget's ancestor is unsafe.

At this point the state of the widget's element tree is no longer stable.

To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.

The relevant error-causing widget was: 
  LoginScreen file:///Users/taleb/FlutterProjects/lambda/lib/routes.dart:40:36
When the exception was thrown, this was the stack: 
#0      Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:3944:9)
#1      Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:3958:6)
#2      Element.findAncestorWidgetOfExactType (package:flutter/src/widgets/framework.dart:3996:12)
#3      debugCheckHasMediaQuery.<anonymous closure> (package:flutter/src/widgets/debug.dart:218:50)
#4      debugCheckHasMediaQuery (package:flutter/src/widgets/debug.dart:234:4)
...
====================================================================================================

======== Exception caught by widgets library =======================================================
The following assertion was thrown building LoginScreen(dirty, dependencies: [_LocalizationsScope-[GlobalKey#aacaf], UncontrolledProviderScope, _InheritedTheme], state: _ConsumerState#cf20e, useTextEditingController: TextEditingController#f5c6d(TextEditingValue(text: ┤├, selection: TextSelection(baseOffset: -1, extentOffset: -1, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1)))):
Looking up a deactivated widget's ancestor is unsafe.

At this point the state of the widget's element tree is no longer stable.

To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.

The relevant error-causing widget was: 
  LoginScreen file:///Users/taleb/FlutterProjects/lambda/lib/routes.dart:40:36
When the exception was thrown, this was the stack: 
#0      Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:3944:9)
#1      Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:3958:6)
#2      Element.findAncestorWidgetOfExactType (package:flutter/src/widgets/framework.dart:3996:12)
#3      debugCheckHasMediaQuery.<anonymous closure> (package:flutter/src/widgets/debug.dart:218:50)
#4      debugCheckHasMediaQuery (package:flutter/src/widgets/debug.dart:234:4)
...
====================================================================================================

这些错误发生在我在 HookConsumerWidget 中使用 TextField 时 班级。 我正在使用 HookConsumerWidget 而不是 StatefullWidget。 我也尝试使用 StatefullConsumerWidget,但问题没有解决。(ConsumerStatefulWidget+riverPod)。 我的问题是我们如何在 HookConsumerWidget + Riverpod 中使用 Textfield ????

如果你想自己运行,我在我的 Github 上提供了这个错误的示例代码: smaple_hook_riverpod

【问题讨论】:

能否请你做一个最小的可重现样本,这些代码有点长 还有,完整的堆栈跟踪是什么?尝试打印出整个堆栈,而不是只打印#0~#4 @ch271828n 是的,我在我的 GitHub 中提供了一个示例代码。请尝试并查看日志。 >>github.com/TalebRafiepour/smaple_hook_riverpod 得到它。请提供完整的错误跟踪 以上运行日志是发生的所有错误。 @ch271828n 这些错误发生在我在 HookConsumerWidget 类中使用 TextField 时。 【参考方案1】:

此问题的原因是您在 Widget build 上调用 Navigator。您可能需要考虑在 initState 上移动 ref.listen&lt;AuthState&gt;。然后用 SchedulerBinding 包裹 Navigator 以等待渲染状态完成后再进行导航。

SchedulerBinding.instance.addPostFrameCallback((_) 
  AppNavigator.replaceWith<String>(NavigationPaths.verifyLogin, mobileNumber);
);

【讨论】:

感谢您的回答,但这并不能解决我的问题。我正在使用 HooksWidget 而不是 StatefullWidget。我也尝试使用StatefullConsumerWidget,但问题没有解决。(ConsumerStatefulWidget+riverPod)。我的问题是我们如何在 Hookswidget + Riverpod 中使用 Textfield ???? 嗨@Taleb。我尝试了您在 GitHub 上发布的代码,但我不确定应用程序应该在哪一步引发类似于您报告的错误。您是否尝试过用 SchedulerBinding.instance.addPostFrameCallback() 包装 Navigator 并查看它是否仍然抛出相同的错误? 当您输入 OTP 代码并希望从 VerifyOtpPage 导航到主页时。是的,我试过SchedulerBinding.instance.addPostFrameCallback() 你知道在 Github 上使用 Hooks 和 RiverPods 的任何示例源代码吗? 这似乎与原始帖子上的错误不同。您可能需要考虑发布一个更集中的问题以及最少的重复。

以上是关于查找已停用小部件的祖先是不安全的 => 使用 Riverpod => 使用 "Navigator.of(context).pushReplacementNamed('/page'的主要内容,如果未能解决你的问题,请参考以下文章

如果添加到动画列表,Flutter 查找已停用小部件的祖先是不安全的

使用提供程序和快餐栏查找已停用小部件的祖先是不安全的

Flutter:查找已停用小部件的祖先是不安全的

Flutter:错误是查找已停用小部件的祖先是不安全的

尝试使用谷歌地图时查找已停用小部件的祖先是不安全的

未处理的异常:查找已停用小部件的祖先是不安全的。如何解决这个问题?