I am implementing remember me option on the login screen, want to call shared preference before the widget is created. we have the one and only entry point that is the main function, but how we can call a function here to read primitive data (email/password).
void main() {
setupLocator();
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
theme: new ThemeData(fontFamily: 'OpenSans-Light'),
initialRoute: "/",
onGenerateRoute: Router.generateRoute,
));
}
reading bool value
Future<bool> read(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getbool(key);
}
I also try to run a asyn function before route
String firstNav;
void main() {
setupLocator();
readSharedPref();
if(firstNav!=null)
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
theme: new ThemeData(fontFamily: 'OpenSans-Light'),
initialRoute: firstNav,
onGenerateRoute: Router.generateRoute,
));
}
void readSharedPref() async {
Utiles df=Utiles();
String isRem=await df.read("remember");
if (isRem.contains("true")) {
firstNav='homeview';
} else {
firstNav='/';
}
}
You need to set your main function as async, and add an await and a line of code:
void main() async{
//Add this lines is necessary now that your main is async
WidgetsFlutterBinding.ensureInitialized();
//Now you have to "await" the readSharedPref() function
await readSharedPref();
// And here comes all your code
}
Instead of waiting waiting for sharedPreference to load before building any widgets, just show a loader widget with progress indicator until the shared preference is loaded, and when it's loaded, show the required view based on the value loaded from sharedPreference, here is how you can modify your code, (replace HomeView and RootView widgets with your respective widgets for your homeView and / routes)
void main() {
setupLocator();
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
theme: new ThemeData(fontFamily: 'OpenSans-Light'),
initialRoute: Loader(),
onGenerateRoute: Router.generateRoute,
));
}
class Loader extends StatefulWidget {
#override
_LoaderState createState() => _LoaderState();
}
class _LoaderState extends State<Loader> {
Widget firstNav;
#override
void initState() {
super.initState();
readSharedPref();
}
void readSharedPref() async {
Utiles df=Utiles();
String isRem=await df.read("remember");
if (isRem.contains("true")) {
setState(() {
// firstNav='homeview';
firstNav=HomeView(); // replace HomeView with the widget you use for homeview route
});
} else {
setState(() {
// firstNav='/';
firstNav=RootView(); // replace RootView with the widget you use for / route
});
}
}
#override
Widget build(BuildContext context) {
return firstNav != null ? firstNav : Center(child: CircularProgressIndicator(),);
}
}
Related
I get this error when I open the App.
when I am trying to initialize the router from authStore get this error
"You are trying to use contextless navigation without
a GetMaterialApp or Get.key.
If you are testing your app, you can use:
[Get.testMode = true], or if you are running your app on
a physical device or emulator, you must exchange your [MaterialApp]
for a [GetMaterialApp].
"
main.dart
#override
Widget build(BuildContext context) {
return GetMaterialApp(
navigatorKey: Get.key,
smartManagement: SmartManagement.full,
initialBinding: InitialBinding(),
debugShowCheckedModeBanner: false,
title: 'Ponte Delivery',
initialRoute: Routers.home,
getPages: Pages.getPages,
theme: Themes.light,
);
}
class InitialBinding implements Bindings {
#override
void dependencies() {
Get.lazyPut<LocalStorage>(() => LocalStorageImpl());
Get.put<HttpClient>(HttpClientImpl(Dio()), permanent: true);
Get.put<AuthStore>(AuthStore(Get.find<LocalStorage>()), permanent: true);
}
}
AuthStore
#override
void onInit() {
ever(isLogged, fireRoute);
super.onInit();
token = _storage.read(TOKEN);
user = _storage.read(USER, construct: (map) => UserModel.fromMap(map));
if (token == null && user == null) {
isLogged.value = false;
}
}
fireRoute(logged) {
print(logged);
if (!logged) {
Get.offNamed(Routers.initialRoute);
}
}
I am using already GetMaterialApp.
I tried to use debugger and I got error in this Line
Get.offNamed(Routers.initialRoute);
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);
I am using getx on my project, I have a mainBianding page :
class MainBinding implements Bindings {
#override
Future<void> dependencies() async {
Get.putAsync<HiveService>(() => HiveService().init(), permanent: true);
Get.lazyPut<HomeController>(
() => HomeController(
dbclient: Get.find<HiveService>()),
);
}
}
I have a GETXService for initializing Hive
class HiveService extends GetxService {
late Box<Model> vBox;
Future<HiveService> init() async {
final appDocumentDirectory =
await path_provider.getApplicationDocumentsDirectory();
Hive
..init(appDocumentDirectory.path)
..registerAdapter<Model>(ModelAdaptor())
return this;
}
After lunching App HomePage and HomeController will be launched but I got this error:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following message was thrown building Builder:
"HiveService" not found. You need to call "Get.put(HiveService())" or
"Get.lazyPut(()=>HiveService())"
The relevant error-causing widget was:
ScrollConfiguration
because Hive service is a future Is has a delay to be loaded but I used Future<void> dependencies() async on this binding class. How do I have to wait to be sure HiveService load completely and after that Home Page load?
I am using MainBinding Inside GetMaterialApp;
Future main() async {
await MainBinding().dependencies();
...
runApp(MyApp()
return GetMaterialApp(
debugShowCheckedModeBanner: false,
useInheritedMediaQuery: true,
locale: DevicePreview.locale(context),
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: Routes.HOME,
getPages: AppPages.pages,
initialBinding: MainBinding(),
test this
await Get.putAsync<HiveService>(() => HiveService().init(), permanent: true);
You need to run your service.init() function first. and then return the getx Service(HiveService) and complete to load this service to the memory.
#override
Future<void> dependencies() async {
await Get.putAsync< HiveService >(() async {
final HiveService service = HiveService();
await service.init();
return service;
});
Get.lazyPut<HomeController>(
() => HomeController(
dbclient: Get.find<HiveService>()),
);
}
}
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);
}
}
}
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');
});
}