2

The scenario which I did and caused this Error: 1. after Login page created when I used hot-reload button 2. when I pressed Login-button and state of the page changed.

Recently I decided to use riverpod package in my flutter application, so I used hooks_riverpod: ^1.0.0-dev.7 with flutter_hooks: ^0.18.0 But when I create my LoginScreen with the help of Riverpod and Hooks I faced with problems which I provided my log in below.

LoginScreen:

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);
    }
  }
}

Run:

======== 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)
...
====================================================================================================

These errors happened when I used TextField in HookConsumerWidget class. I am using HookConsumerWidget instead of StatefullWidget. I also tried to use StatefullConsumerWidget but the problem was not > solved.(ConsumerStatefulWidget+riverPod). My question is how can we use Textfield in HookConsumerWidget + Riverpod ????

If you want to run it by yourself, I Provided a sample code of this error on my Github: smaple_hook_riverpod

Taleb
  • 1,944
  • 2
  • 11
  • 36

2 Answers2

1

I think "Navigator.of (context).pushReplacementNamed" is not the right solution for "Flutter Navigator + Riverpod".

A better solution is an entire navigation stack (which is a List of Page's) to create from a list of immutable objects. The problem of navigation is than reduced to manipulation an immutable collection.

  • final navigationStackProvider = Provider<>((ref) => [Obj1, Obj2, Obj3]);
  • [Obj1, Obj2, Obj3] je následně synchronizován s navigation stack, odpovídající [Screen1, Screen2, Screen3]...

I prepared example that implements a simple login logic, where some pages are not available without login.

Example is here: riverpod_navigator_example

Pavel Zika
  • 128
  • 4
0

The cause of this issue is that you're calling Navigator on Widget build. You may want to consider moving ref.listen<AuthState> on initState. Then wrap the Navigator with a SchedulerBinding to wait for the rendering state to finish before navigating.

SchedulerBinding.instance.addPostFrameCallback((_) {
  AppNavigator.replaceWith<String>(NavigationPaths.verifyLogin, mobileNumber);
});
Omatt
  • 8,564
  • 2
  • 42
  • 144
  • Thanks for your answer, But this does not solve my problem. I am using HooksWidget instead of StatefullWidget. I also tried to use StatefullConsumerWidget but the problem was not solved.(ConsumerStatefulWidget+riverPod). My question is how can we use Textfield in Hookswidget + Riverpod ???? – Taleb Sep 21 '21 at 10:37
  • Hi @Taleb. I tried the code you've posted on GitHub but I'm unsure on which step the app supposed to throw an error similar to what you've reported. Have you tried wrapping the Navigator with a `SchedulerBinding.instance.addPostFrameCallback()` and see if it still throws the same error? – Omatt Sep 21 '21 at 12:00
  • When you enter the OTP code and want to navigate from VerifyOtpPage to HomePage. yeah I tried ```SchedulerBinding.instance.addPostFrameCallback()``` – Taleb Sep 21 '21 at 16:00
  • Do you know any sample source code for using Hooks with RiverPods on Github? – Taleb Sep 21 '21 at 16:01
  • That seems to be a different issue from the error on the original post. You may want to consider posting a more focused question for that along with a minimal repro. – Omatt Sep 21 '21 at 18:13
  • If you can solve that error I believe all errors will solve. – Taleb Sep 21 '21 at 20:26