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

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

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.

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

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

Related

Flutter GetX dependency Injection

I'm new to GetX flutter state management. I'm using two controllers, one for Login and other for Home data(fetching some restaurants data through API call). I'm having trouble in bindings. I'm using bindings in my app following GetX docs. But I'm unable to use it properly and getting error. Following is the code -:
main.dart
void main() async {
await GetStorage.init('My Storage');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flunkey Task',
getPages: [
GetPage(
name: '/',
page: () => LandingPage(),
binding: BindingsBuilder(() {
Get.lazyPut<LoginController>(() => LoginController());
})),
GetPage(
name: '/login',
page: () => LoginScreen(),
binding: BindingsBuilder(() {
Get.lazyPut<LoginController>(() => LoginController());
})),
GetPage(
name: '/home',
page: () => HomeScreen(),
binding: BindingsBuilder(() {
Get.lazyPut<HomeController>(() => HomeController());
}),
)
],
initialRoute: '/',
);
}
}
class LandingPage extends StatelessWidget {
LandingPage({Key? key}) : super(key: key);
final _controller = Get.find<LoginController>();
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Obx(() =>
_controller.isLoggedIn.value == true ? HomeScreen() : LoginScreen());
}
}
loginController.dart
class LoginController extends GetxController {
final box = GetStorage('My Storage');
var isLoggedIn = false.obs;
final formKey = GlobalKey<FormState>();
final usernameTED = TextEditingController();
final passwordTED = TextEditingController();
#override
void onInit() {
isLoggedIn(loginStatus);
super.onInit();
}
#override
void onClose() {
usernameTED.dispose();
passwordTED.dispose();
super.onClose();
}
String? checkUsername(String username) {
if (username.isEmpty || username.length < 3 || username.length > 11) {
return 'Username must have 3-11 characters';
}
return null;
}
String? checkPassword(String password) {
if (password.isEmpty || password.length < 3 || password.length > 11) {
return 'Password must have 3-11 characters';
}
return null;
}
Future<void> login() async {
if (!formKey.currentState!.validate()) {
return;
}
if ((usernameTED.text.trim() == 'flunkey' &&
passwordTED.text.trim() == 'password123') ||
(usernameTED.text.trim() == 'user' &&
passwordTED.text.trim() == 'password123')) {
formKey.currentState!.save();
await changeLoginStatus(true);
await saveUserName(usernameTED.text);
usernameTED.clear();
passwordTED.clear();
} else {
Get.snackbar('Login Error', 'User does not exists',
backgroundColor: Colors.red[400]);
}
}
void signOut() async {
await changeLoginStatus(false);
}
Future<void> changeLoginStatus(bool status) async {
await box.write('status', status);
isLoggedIn(status);
}
Future<void> saveUserName(String name) async {
await box.write('name', name);
}
bool get loginStatus => box.read('status') ?? false;
String get currentUserName => box.read('name') ?? '';
}
homeController.dart
class HomeController extends GetxController {
final _isLoading = false.obs;
final _restaurantData = <restau.Datum>[].obs;
#override
void onInit() {
getData();
super.onInit();
}
bool get isLoading => _isLoading.value;
List<restau.Datum> get getRestaurants => _restaurantData;
Future<void> getData() async {
try {
_isLoading(true);
var apiData = await RestaurantDataApiCall.getRestaurantData();
_restaurantData.value = apiData!.data.data;
_isLoading(false);
} catch (e, s) {
print(e);
print(s);
}
}
}
Following is the error I'm getting.
I'm using Get.find() on Login Screen and Get.find() on Home screen as following,
Please guide me how to properly use Bindings in GetX.
I don't like to bind the controllers on route.
I create a MainBind.dart and put inside this class all getx controllers.
class MainBinding implements Bindings {
#override
Future<void> dependencies() async{
Get.lazyPut<AppController>(() => AppController(), fenix: true);
}
}
And in my Main.dart :
void main() async{
WidgetsFlutterBinding.ensureInitialized();
MainBinding mainBinding = MainBinding();
await mainBinding.dependencies();
runApp(const MyApp());
}
In this way I'm sure that Controllers are binded.
But you can try use Put insted lazyPut too..
You can use StatefulWidget with state class which will contain your controller.
E.g.
StateClass bla, bla {
late final yourController = Get.put<YourController>();
#override
dispose() {
Get.delete<YourController>();
}
}
That's it!

How can i access to a variable in main.dart to other pages in flutter, i am using Getx state management

How can I access a variable in main.dart to other pages in flutter with Getx state management, Here I want to make the localMemberid in main.dart as Global to access from anywhere or pass it to other pages and is it the right way to use secure storage for storing the data
main.dart
void main() {
SecureStorage secureStorage = SecureStorage();
var localMemberid; // i would like to make this varial global or pass this value to other pages
runApp(
ScreenUtilInit(
builder: (BuildContext context, Widget? child) {
return GetMaterialApp(
title: "onyx",
initialRoute: AppPages.INITIAL,
getPages: AppPages.routes,
theme: ThemeData(primarySwatch: MaterialColor(0xFF0456E5, color)),
);
},
),
);
SecureStorage.readLocalSecureData('memberid')
.then((value) => localMemberid = value);
}
Login Controller
class LoginController extends GetxController {
final AuthenticationRepo _authRepe = AuthenticationRepo();
final SecureStorage secureStorage = SecureStorage();
String? localMemberid; // i would like to get the localMemberid from the main.dart
//TODO: Implement LoginController
#override
void onInit() {
super.onInit();
}
#override
void onReady() {
super.onReady();
}
#override
void onClose() {}
var userid;
var password;
onSinginButton() async {
var res = await _authRepe.login(username: userid, password: password);
if (res.status == ApiResponseStatus.completed) {
print(res.data);
await SecureStorage.writeLocalSecureData('memberid', res.data!.memberid);
localMemberid == null
? Get.toNamed(Routes.LOGIN)
: Get.toNamed(Routes.HOME);
} else {
Get.defaultDialog(title: res.message.toString());
}
}
}
Uplift your variable from the main function and make it Rx:
var localMemberid=Rxn<String>(); // i would like to make this varial global or pass this value to other pages
void main() {
SecureStorage secureStorage = SecureStorage();
.......
SecureStorage.readLocalSecureData('memberid')
.then((value) => localMemberid.value = value);
}
And then on your LoginController remove String? localMemberid; // and import main.dart:
localMemberid.value == null
? Get.toNamed(Routes.LOGIN)
: Get.toNamed(Routes.HOME);

shared preference + firebase auth not working properly

Firebase Auth is working properly, I'm able to log in, Sign in but I want to preserve the state of the application. For state persistence, I'm using Share preferences.
I'm using sharedpreferences. I made a class and defined some keys and methods to fetch and set the data in the keys at the time of login and use it later in the app.
Please help me out with an easy approch how to do this.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class CurrentUser {
static SharedPreferences? mypreferences;
static const emailKey = 'emailKey';
static const passwordKey = 'paswordKey';
static const loginStatusKey = 'loginStatusKey';
static Future init() async {
mypreferences = await SharedPreferences.getInstance();
}
static Future setUserEmail(String? emailValue) async {
return await mypreferences?.setString(emailKey, emailValue!);
}
static Future setUserPassword(String? passwordValue) async {
return await mypreferences?.setString(passwordKey, passwordValue!);
}
static Future setLoginStatus(bool status) async {
return await mypreferences?.setBool(loginStatusKey, status);
}
static String? getUserEmail() {
return mypreferences?.getString(emailKey);
}
static String? getUserPassword() {
return mypreferences?.getString(passwordKey);
}
static bool? getUserLoginStatus(){
if(loginStatusKey==null){
mypreferences?.setBool(loginStatusKey, false);
}
return mypreferences?.getBool(loginStatusKey);
}
}```
// storing the value during login //
``` myLogIn() async {
try {
UserCredential myuser= await FirebaseAuth.instance
.signInWithEmailAndPassword(email: email, password: password);
// print(myuser);
// print(myuser.user?.email);
CurrentUser.setUserEmail(myuser.user?.email);
CurrentUser.setUserPassword(password);
CurrentUser.setLoginStatus(true);
Navigator.pushReplacementNamed(context, MyRoutes.homeRoute);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
print('user is not registered');
ScaffoldMessenger.of(context).showSnackBar(
MySnackBar.showcustomSnackbar('user is not registered'));
} else if (e.code == 'wrong-password') {
print('wrong password');
ScaffoldMessenger.of(context)
.showSnackBar(MySnackBar.showcustomSnackbar('wrong password'));
}
}
}```
// using the store login status in main file to show login page and home page accordingly.
```import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
// import 'firebase_options.dart';
import './pages/login.dart';
import './pages/signup.dart';
import './pages/homepage.dart';
import './utils/my_theme.dart';
import './utils/my_routes.dart';
import './pages/forgot_password.dart';
import './utils/my_user.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
CurrentUser.init();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// const MyApp({ Key? key }) : super(key: key);
final Future<FirebaseApp> initializeMyFirebaseApp = Firebase.initializeApp();
bool? isLogin = CurrentUser.getUserLoginStatus();
#override
void initState() {
// TODO: implement initState
isLogin = CurrentUser.getUserLoginStatus();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: initializeMyFirebaseApp,
builder: (context, snaphot) {
//Error checking
if (snaphot.hasError) {
print("Something went wrong!");
}
//If snapshot state is in waiting or so
if (snaphot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
//else we will show the normal login page.
return MaterialApp(
theme: MyTheme.lightTheme(context),
//isUserLoggedIn !=null ? isUserLoggedIn ? Home() : SignUp() : SignIn(),
home:isLogin!=null ?
isLogin! ? HomePage() : Login() : Login(),
// Login(),
routes: {
// "/":(context) => Login(),
MyRoutes.loginRoute: (context) => Login(),
MyRoutes.signupRoute: (context) => SignUp(),
MyRoutes.homeRoute: (context) => HomePage(),
MyRoutes.forgotpasswordRoute: (context) => ForgotPassword(),
},
);
});
}
}
```

How to Keep Users Always Logged In [duplicate]

This question already has answers here:
Persist user Auth Flutter Firebase
(8 answers)
Closed 2 years ago.
I want to keep the user logged in even if the user closes the app. The problem is that if I close the app, I appear in the WelcomePage() instead of the HomePage().
In the main.dart, the initialRoute is the WelcomePage(), but I need it to change once the user has registered or logged in. What do I need to change?
Here's what I have:
import 'package:flutter/material.dart';
import 'pages/account/WelcomePage.dart';
import 'pages/match/HomePage.dart';
void main() => runApp(MyApp());
// Test
class MyApp extends StatelessWidget {
//
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
//
initialRoute: WelcomePage.id,
//
routes: {
WelcomePage.id: (context) => WelcomePage(),
HomePage.id: (context) => HomePage(),
},
);
}
}
Edit: I was reading that the user must be still logged in, so what I have to do here is check if the user is not null, and then pus to the HomePage.id. But to check if the user is logged in I need an async function, and the initialRoute doesn't accept Futures.
Rather than always directly going to the WelcomePage, make a widget that displays a loading indicator until you determine the login state. Once this state is determined you can show either the WelcomePage or HomePage based on this state.
An example of such a widget would be the RootPage from this medium article.
Example from article:
enum AuthStatus {
NOT_DETERMINED,
NOT_LOGGED_IN,
LOGGED_IN,
}
class RootPage extends StatefulWidget {
RootPage({this.auth});
final BaseAuth auth;
#override
State<StatefulWidget> createState() => new _RootPageState();
}
class _RootPageState extends State<RootPage> {
AuthStatus authStatus = AuthStatus.NOT_DETERMINED;
String _userId = "";
#override
void initState() {
super.initState();
widget.auth.getCurrentUser().then((user) {
setState(() {
if (user != null) {
_userId = user?.uid;
}
authStatus =
user?.uid == null ? AuthStatus.NOT_LOGGED_IN : AuthStatus.LOGGED_IN;
});
});
}
void loginCallback() {
widget.auth.getCurrentUser().then((user) {
setState(() {
_userId = user.uid.toString();
});
});
setState(() {
authStatus = AuthStatus.LOGGED_IN;
});
}
void logoutCallback() {
setState(() {
authStatus = AuthStatus.NOT_LOGGED_IN;
_userId = "";
});
}
Widget buildWaitingScreen() {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: CircularProgressIndicator(),
),
);
}
#override
Widget build(BuildContext context) {
switch (authStatus) {
case AuthStatus.NOT_DETERMINED:
return buildWaitingScreen();
break;
case AuthStatus.NOT_LOGGED_IN:
return new LoginSignupPage(
auth: widget.auth,
loginCallback: loginCallback,
);
break;
case AuthStatus.LOGGED_IN:
if (_userId.length > 0 && _userId != null) {
return new HomePage(
userId: _userId,
auth: widget.auth,
logoutCallback: logoutCallback,
);
} else
return buildWaitingScreen();
break;
default:
return buildWaitingScreen();
}
}
}

initialRoute string is changed, but I end up at the same page regardless the initialRoute string

When using shared_preferences on flutter in main.dart in order to change the initialRoute depending on if user have seen the first page or if user is logged in I am getting the boolean which is created throughout the app and added to shared_preferences, every time I start app, I get the initialRoute string correct when debugging, but I still end up getting on the first page, regardless the conditions.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:developer';
import './pages/registration.dart';
import './pages/login_page.dart';
import './pages/confirmation.dart';
import './pages/lang_page.dart';
import './pages/main_page.dart';
import './pages/user_data.dart';
import './provider/provider.dart';
void main() => runApp(CallInfoApp());
class CallInfoApp extends StatefulWidget {
#override
_CallInfoAppState createState() => _CallInfoAppState();
}
class _CallInfoAppState extends State<CallInfoApp> {
SharedPreferences prefs;
void getSPInstance() async {
prefs = await SharedPreferences.getInstance();
}
dynamic langChosen;
dynamic isLoggedIn;
String initialRoute;
void dataGetter() async {
await getSPInstance();
setState(() {
langChosen = prefs.getBool('langChosen');
// print(langChosen);
isLoggedIn = prefs.getBool('isLoggedIn');
});
}
void getRoute() async {
await dataGetter();
debugger();
if (langChosen == true && isLoggedIn != true) {
setState(() {
initialRoute = '/login_page';
});
} else if (isLoggedIn == true) {
initialRoute = '/main_page';
} else {
setState(() {
initialRoute = '/';
});
}
}
#override
void initState() {
super.initState();
debugger();
getRoute();
}
#override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return ChangeNotifierProvider<AppData>(
create: (context) => AppData(),
child: MaterialApp(
title: 'Call-INFO',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: initialRoute,
routes: {
'/': (context) => LanguagePage(),
'/registration_page': (context) => RegistrationPage(),
'/login_page': (context) => LoginPage(),
'/confirmation_page': (context) => ConfirmationPage(),
'/user_data_page': (context) => UserDataPage(),
'/main_page': (context) => MainPage(),
},
),
);
}
}
Since SharedPreference.getInstance() is an async function it will need some time until the instance is available. If you want to use it for initial route you have to make your main function async and preload it there before your MaterialApp is build.
SharedPreference prefs; //make global variable, not best practice
void main() async {
prefs = await SharedPreference.getInstance();
runApp(CallInfoApp());
}
And remove getSPInstance() from dataGetter
Also keep in midn that prefs.getBool('langChosen') will return null and not false if no entry is made into shared preference so use
langChosen = prefs.getBool('langChosen')??false;
isLoggedIn = prefs.getBool('isLoggedIn')??false;
While this solution will work it's not really good practice. I would recommend to have the initialRoute fixed to a splash screen and handle forwarding to the right page from there. A simple splash screen could look like that:
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: CircularProgressIndicator()));
}
#override
void initState() {
initSplash();
super.initState();
}
Future<void> initSplash() async {
final prefs = await SharedPreferences.getInstance();
final langChosen = prefs.getBool("lang_chosen") ?? false;
final isLoggedIn = prefs.getBool("logged_in") ?? false;
if (langChosen == true && isLoggedIn != true) {
Navigator.of(context).pushReplacementNamed('/login_page');
} else if (isLoggedIn == true) {
Navigator.of(context).pushReplacementNamed('/main_page');
} else {
Navigator.of(context).pushReplacementNamed('/');
}
}
}
Use initState to derive the data your logic is based on (i.e. fetching shared pref info). And use await keyword so that program will wait until the data is fetched from SharedPrefs. Adding the following code to class _CallInfoAppState should help
#override
void initState() {
super.initState();
dataGetter();
}
void dataGetter() async {
await getSPInstance();
setState(() {
langChosen = prefs.getBool('langChosen');
isLoggedIn = prefs.getBool('isLoggedIn');
});
}