riverpod icon indicating copy to clipboard operation
riverpod copied to clipboard

LoginController(StateNotifierProvider)+GoRouter autoSignIn firebase not working

Open pom11 opened this issue 1 year ago • 2 comments

Using a StateNotifierProvider with GoRouter to check if a User is already logged in into Firebase, the redirect feature is not working on flutter_riverpod 2.0.0-dev.9.

class LoginController extends StateNotifier<LoginState> {
  LoginController(this.ref) : super(const LoginStateInitial('initial'));

  final Ref ref;
  Stream<User> authStateChanges() {
    return ref.read(authRepositoryProvider).authStateChanges();
  }
.......
  void autoSignIn() async {
    try {
      if (state is LoginStateInitial || state is LoginStateError) {
        state = LoginStateInitial('initial');
        User user = await ref
            .read(loginControllerProvider.notifier)
            .authStateChanges()
            .first;
        print(user);
        if (user != null) {
          state = LoginStateSuccess('success', user.uid);
        } else
          state = LoginStateInitial('initial');
      }
    } catch (e) {
      state = LoginStateError('error', e.toString());
    }
  }
..........
}

final loginControllerProvider =
    StateNotifierProvider<LoginController, LoginState>((ref) {
  return LoginController(ref);
});

In the LandinPage of the app (first screen) I use a simple ref.listen to autoSignIn

class LandingPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ref.listen(currentUserProvider, (previous, next) {
      if (next is AsyncData) {
        if (next.value != null) {
          LoginState loginState =
              ref.read(loginControllerProvider.notifier).debugState;
          if (loginState is LoginStateInitial ||
              loginState is LoginStateError) {
            if (next.value.uid != null) {
              ref.read(loginControllerProvider.notifier).autoSignIn();
            }
          }
        }
      }
    });

    return Scaffold(...
final routerProvider = Provider.autoDispose<GoRouter>((ref) {
  final r = RouterNotifier(ref);
  return GoRouter(
      initialLocation: '/',
      debugLogDiagnostics: true,
      refreshListenable: r,
      redirect: r.redirectLogic,
      routes: r.routes);
});
class RouterNotifier extends ChangeNotifier {
  final Ref _ref;

  RouterNotifier(this._ref) {
    _ref.listen<LoginState>(
      loginControllerProvider,
      (_, __) => notifyListeners(),
    );
  }

  String redirectLogic(GoRouterState state) {
    final loginState = _ref.read(loginControllerProvider);
    final user = _ref.read(loginControllerProvider.notifier).authStateChanges();

    final areWeLoggingIn = state.location == '/auth/login' ||
        state.location == '/auth/register' ||
        state.location == '/';

    if (loginState is LoginStateInitial && user == null) {
      return areWeLoggingIn ? null : state.location;
    }

    if (loginState is LoginStateLoading && user == null) {
      return areWeLoggingIn ? null : '/loading';
    }

    if (loginState is LoginStateSuccess && user != null) {
      return !areWeLoggingIn ? null : '/home';
    }

    return null;
  }

  List<GoRoute> get routes => [
        GoRoute(
            path: '/',
            builder: (BuildContext context, GoRouterState state) {
              return LandingPage();
            }), .......

AutoSignIn works with go_router 4.4.1 and flutter_riverpod 1.0.4. Breaks with flutter_riverpod 2.0.0-dev.9

pom11 avatar Sep 13 '22 19:09 pom11

Hello! This is too broad. Could you reformulate your issue such that it does not depend on your business code, and ideally not on any package either?

We'd also need clear steps to reproduce the problem, and a description of what's happening vs what's expected.

rrousselGit avatar Sep 14 '22 12:09 rrousselGit

Hello! I am listening to a StreamProvider currentUserProvider to check if the user is logged into firebase and to access its documents i need its uid. To get the uid i check a StateNotifierProvider and get the uid from LoginStateSuccess props. This logic, with the code posted above, works with flutter_riverpod 1.0.4 but not with 2.0.0-dev.9 or any other 2.0.0-xxx.

This ref.read(loginControllerProvider.notifier).autoSignIn(); never gets called in the LandingPage listener / can't retrieve state / can't access State props.

With the code from above you can reproduce the issue.

Expected: Check if the user is logged in and get uid from LoginSuccessState and redirect the user to HomePage (restricted access, needs uid for firebase documents).

Happening: Even tho the StreamProvider currentUserProvider

final Ref ref;
  Stream<User> authStateChanges() {
    return ref.read(authRepositoryProvider).authStateChanges();
  }

says the user is logged into firebase, I can't get its uid from LoginSuccessState for further use and is not redirected to HomePage.

Sorry for this long bug report

pom11 avatar Sep 14 '22 13:09 pom11

SOLVED

class LoginController extends StateNotifier<LoginState> {
  LoginController(this.ref) : super(const LoginStateInitial('initial'));

  final Ref ref;
  Stream<User> authStateChanges() {
    return ref.read(authRepositoryProvider).authStateChanges();
  }
.......
  void autoSignIn() async {
    try {
      if (state is LoginStateInitial || state is LoginStateError) {
        state = LoginStateInitial('initial');
        User user = await ref
            .read(loginControllerProvider.notifier)
            .authStateChanges()
            .first;
        print(user);
        if (user != null) {
          state = LoginStateSuccess('success', user.uid);
        } else
          state = LoginStateInitial('initial');
      }
    } catch (e) {
      state = LoginStateError('error', e.toString());
    }
  }
..........
}

final loginControllerProvider =
    StateNotifierProvider<LoginController, LoginState>((ref) {
  return LoginController(ref);
});

Changed this

User user = await ref
            .read(loginControllerProvider.notifier)
            .authStateChanges()
            .first;

to this

User user =
            await ref.read(authRepositoryProvider).authStateChanges().first;

so that the StateNotifier doesn't depend on itself.

Conclusion: Till flutter_riverpod 1.0.4 it appears that a StateNotifier (dunno about the rest) can depend on itself. On flutter_riverpod 2.0.0-dev.9 the self dependency doesn't work anymore.

pom11 avatar Sep 23 '22 12:09 pom11