Search code examples
flutterfirebasestatebloc

Flutter bloc/ Error / Looking up a deactivated widget's ancestor is unsafe. / Firebase SignUp


Description

I tried to setup Firebase Signup feature to my app using bloc/cubit, but encountered the following error.

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.

This error message seems to say that the following code in the CustomButton package is not stable.

 context.read<SignupCubit>().signupWithCredentials().whenComplete(() {
              User user = User(
                id: context.read<SignupCubit>().state.user!.uid,
                name: '',
                age: 0,
              );

This error does not occur in debug mode. When I run the code line by line, the process proceeds without problems. I don't know the reason.

I have tried to solve this error, but no means don't work.

CustomButton The button widget displayed on the Signup page. When user pushed the button after entering email and password, the user account will be created. Then userid, name and age document fields of the user will be created on the Firebase database.

class CustomButton extends StatelessWidget {
  final TabController? tabController;
  final String text;
  CustomButton({
    Key? key,
    this.tabController,
    this.text = 'START',
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
          return DecoratedBox(
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(10),
            gradient: LinearGradient(
              colors: [
                Theme.of(context).primaryColor,
                Theme.of(context).primaryColorLight
              ],
            ),
          ),
          child: ElevatedButton(
            style: ElevatedButton.styleFrom(
                elevation: 0, backgroundColor: Colors.transparent),
            onPressed: () {
              if (tabController!.index <= 4) {
                tabController!.animateTo(tabController!.index + 1);
              } else {
                Navigator.pushNamed(context, '/');
              }
              if (tabController!.index == 2) {
                context
                    .read<SignupCubit>()
                    .signupWithCredentials()
                    .whenComplete(() {
                  User user = User(
                    id: context.read<SignupCubit>().state.user!.uid,
                    name: '',
                    age: 0,
                  );
                  context
                      .read<OnboardingBloc>()
                      .add(StartOnboarding(user: user));
                });
              }
            },
            child: SizedBox(
              child: Center(
                child: Text(
                  text,
                  style: Theme.of(context)
                      .textTheme
                      .headline4!
                      .copyWith(color: Colors.white),
                ),
              ),
              width: double.infinity,
            ),
          ));
    }
  }

SignupCubit

class SignupCubit extends Cubit<SignupState> {
  final AuthRepository? _authRepository;
  SignupCubit({ AuthRepository? authRepository})
      : _authRepository = authRepository,
        super(SignupState.initial());

  void emailChanged(String value) {
    emit(state.copywith(email: value, status: SignupStatus.initial));
  }

  void passwordChanged(String value) {
    emit(state.copywith(password: value, status: SignupStatus.initial));
  }

  Future<void> signupWithCredentials() async {
    if (!state.isValid || state.status == SignupStatus.submitting) return;
    emit(state.copywith(status: SignupStatus.submitting));
    try {
      var user = await _authRepository!.signUp(
        email: state.email,
        password: state.password,
      );
      emit(state.copywith(
        status: SignupStatus.success,
        user: user,
      ));
    } catch (e) {
      debugPrint('$e');
    }
  }
}

SignupState

enum SignupStatus { initial, submitting, success, error }

class SignupState extends Equatable {
  final String email;
  final String password;
  final SignupStatus status;
  final auth.User? user;

  const SignupState(
      {required this.email,
      required this.password,
      required this.status,
      this.user,
      });

  factory SignupState.initial() {
    return const SignupState(
        email: '', password: '', status: SignupStatus.initial, user: null,);
  }

  SignupState copywith({
    String? email,
    String? password,
    SignupStatus? status,
    auth.User? user,
    String? uid,
  }) {
    return SignupState(
        email: email ?? this.email,
        password: password ?? this.password,
        status: status ?? this.status,
        user: user ?? this.user,
        );
  }

  bool get isValid => email.isNotEmpty && password.isNotEmpty;

  @override
  bool get stringify => true;

  @override
  List<Object?> get props => [email, password, status, user];
}

AuthRepository

class AuthRepository extends BaseAuthRepository {
  final auth.FirebaseAuth _firebaseAuth;
  AuthRepository({auth.FirebaseAuth? firebaseAuth})
      : _firebaseAuth = firebaseAuth ?? auth.FirebaseAuth.instance;

  @override
  Future<auth.User?> signUp(
      {required String email, required String password}) async {
    final credential = await _firebaseAuth.createUserWithEmailAndPassword(
        email: email, password: password);
    final user = credential.user;
    return user;
  }
}

Solution

  • I gave up using context.read<SignupCubit>().state.user!.uid, but came up with the idea to use BlocConsumer!!

    This really solved the stack. I hope this help someone who is suffered from the same issue...

    class EmailScreen extends StatelessWidget {
      final TabController tabController;
      const EmailScreen({Key? key, required this.tabController}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return BlocConsumer<SignupCubit, SignupState>(
          listenWhen: (previous, current) => current.status == SignupStatus.success,
          listener: (context, state) {
            User user = User(
              id: state.user!.uid,
              name: '',
              age: 0,
            );
            context.read<OnboardingBloc>().add(StartOnboarding(user: user));
          },
          builder: (context, state) {
            return Padding(
              padding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 50),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const CustomTextHeader(text: 'What\'s Your Email Address?'),
                      CustomTextfield(
                        text: 'ENTER YOUR EMAIL',
                        onChanged: (value) {
                          context.read<SignupCubit>().emailChanged(value);
                        },
                      ),
                      const SizedBox(height: 100),
                      const CustomTextHeader(text: 'Choose a Password'),
                      CustomTextfield(
                        text: 'ENTER YOUR PASSWORD',
                        onChanged: (value) {
                          context.read<SignupCubit>().passwordChanged(value);
                        },
                      ),
                    ],
                  ),
                  Column(
                    children: [
                      const StepProgressIndicator(
                        totalSteps: 5,
                        currentStep: 1,
                        selectedColor: Colors.grey,
                        unselectedColor: Colors.purpleAccent,
                      ),
                      const SizedBox(
                        height: 10,
                      ),
                      CustomButton(
                        tabController: tabController,
                        text: 'NEXT STEP',
                        isCompleted: (state.email.isEmpty || state.password.isEmpty)
                            ? false
                            : true,
                      )
                    ],
                  ),
                ],
              ),
            );
          },
        );
      }
    }