How can I use condition and go to different page on flutter using Getx without click

4.5k Views Asked by At

I am trying to use firebase data to route different pages using Getx. First I have a splash screen and want to automatically go to different pages according to conditions. If the user has already login, it will redirect the Home page, if not the route to the login page. But I can't use initState() on the Stateless widget as I using Getx, I don't want a Stateful widget.

    class SplashPage extends StatelessWidget {
  RxBool isloading = true.obs;

  @override
  Widget build(BuildContext context) {
    String Uid = "";
    return isloading.value
        ? SpinKitThreeInOut(
            color: Colors.red,
          )
        : Obx(() {
            return Get.find<AuthController>().user != null
                ? homeMethod()
                : login();
          });
  }

  Widget homeMethod() {
    return Home(AuthController.instance.user.toString());
    isloading.value = false;
  }
}

But I ain't able to override isloading.value = false;

My Getx Auth Controller:

class AuthController extends GetxController {
  static AuthController instance = Get.find();
  FirebaseAuth auth = FirebaseAuth.instance;
  Rxn<User> _firebaseUser = Rxn<User>();
  String? get user => _firebaseUser.value?.uid;

  @override
  void onReady() {
    // TODO: implement onReady
    super.onReady();
    _firebaseUser.value = auth.currentUser;
    _firebaseUser.bindStream(auth.userChanges());
    ever(_firebaseUser, _initialScreen);
  }
/*  @override
  void onInit() {
    _firebaseUser.bindStream(_auth.authStateChanges());
  }*/

  _initialScreen(User? user) {
    if (user == null) {
      Get.offAll(login());
    } else {
      String userId = user.uid;
      Get.offAll(Home(userId));
    }
  }

  Future<User?> LogInAccounts(String Email, String Password) async {
    FirebaseAuth auth = FirebaseAuth.instance;
    try {
      User? user = (await auth.signInWithEmailAndPassword(
              email: Email, password: Password))
          .user;
      if (user != null) {
        Fluttertoast.showToast(msg: "Account Create Sucessfully");
        return user;
      } else {
        Fluttertoast.showToast(msg: "Account Create Failed!");
        return user;
      }
    } catch (e) {
      return null;
    }
  }
}
3

There are 3 best solutions below

1
On
class AuthController extends GetxController {
 late Rx<User?> firebaseUser;
 @override
 void onReady() async {
  super.onReady();
  firebaseUser = Rx<User?>(FirebaseAuth.instance.currentUser);
  firebaseUser.bindStream(firebaseAuth.instance.userChanges());
  ever(firebaseUser, _setInitialScreen);
}
_setInitialScreen(user) async{
  if (user != null) {      
    Get.offAllNamed(Routes.home);      
  } else  {
    Get.offAllNamed(Routes.login);
  } 
 }
}
0
On

You can handle initialRoute of GetMaterialApp using isLogin flag

class _MyAppState extends State<MyApp> {
  bool isLogin = false;

  @override
  void initState() {
    isLogin = isAlreadyLogin();// Your function to check is user logged in.
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Rider App',
      translationsKeys: AppTranslation.translationsKeys,
      locale: Get.find<CacheManager>().getLocale(),
      getPages: AppPages.pages,
      initialRoute: isLogin ? Routes.homeScreen : Routes.loginScreen,
      initialBinding: InitialBinding(),
    );
  }
class Routes {
  static const homeScreen = '/home-screen';
  static const loginScreen = '/login-screen';
}
1
On

Updated Answer

You can use bindStream and do it that way, but instead of trying to turn your User object into a stream this can be done with a simple RxBool. Firebase already provides a function to listen to auth state changes.


class AuthController extends GetxController {
  RxBool loggedIn = false.obs;
  @override
  void onInit() {
    super.onInit();
    _subscribe();
  }

  void _subscribe() {
    FirebaseAuth.instance.authStateChanges().listen((User? user) {
      if (user == null) {
        loggedIn(false);
        log('User is currently signed out');
      } else {
        loggedIn(true);
        log('User is signed in');
      }
    });
  }
}

Then you can add another couple methods to your GetX class.

  void initNaviationListener() {
    /// inital startup naviation
    _navigateBasedOnLogin();

    /// future navigation based on auth state changes
    ever(loggedIn, (value) {
      _navigateBasedOnLogin();
    });
  }

  void _navigateBasedOnLogin() {
    if (loggedIn.value == false) {
      Get.offAndToNamed(LoginPage.id);
    } else {
      Get.offAndToNamed(HomePage.id);
    }
  }

Then you can call initNaviationListener in the onReady of GetMaterialApp

GetMaterialApp(
        /// onReady is called after GetMaterialApp is fully initialized
        onReady: () => Get.find<AuthController>().initNaviationListener(),
        theme: ThemeData.dark(),
        initialRoute: LoginPage.id,
        getPages: [
          GetPage(
            name: SplashPage.id,
            page: () => SplashPage(),
          ),
          GetPage(
            name: HomePage.id,
            page: () => HomePage(),
          ),
          GetPage(
            name: LoginPage.id,
            page: () => LoginPage(),
          ),
        ],
      )

That will navigate on app start to the corresponding screen and also respond to any future changes in auth status.

Original Answer

You don't have to navigate from the SplashPage you can do it from the controller.

Let's say your GetMaterialApp looks like this. This takes you to SplashPage first.

GetMaterialApp(
        initialRoute: SplashPage.id,
        getPages: [
          GetPage(
            name: SplashPage.id,
            page: () => SplashPage(),
          ),
          GetPage(
            name: HomePage.id,
            page: () => HomePage(),
          ),
          GetPage(
            name: LoginPage.id,
            page: () => LoginPage(),
          ),
        ],
      )

Then check logged in status and navigate to the corresponding screen from your AuthController.

class AuthController extends GetxController {
  @override
  void onInit() {
    super.onInit();
    _navigateBasedOnLogin();
  }

  Future<void> _navigateBasedOnLogin() async {
    final loggedIn = await _isLoggedIn();

    if (loggedIn) {
      Get.offAndToNamed(HomePage.id); // offAndToNamed will remove the SplashScreen from the navigation stack
    } else {
      Get.offAndToNamed(LoginPage.id);
    }
  }

  Future<bool> _isLoggedIn() async {
    /// run your code to check logged in status and return true or false
  }
}

Then just init the AuthController in your main.

void main() async {
  Get.put(AuthController());

  runApp(MyApp());
}

With this setup, your SplashScreen can be a generic loading screen with zero logic.