shared_preferences returns null on existing value - flutter

I am trying to see if an id key is available in my app's shared_pereferences and if there is, redirect my user to the homepage. I am checking the Id in the initState() function of my main.dart and I know that the id exists because I can get it in other pages. but in my main.dart it returns null. any ideas?
here is my main.dart code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import './ui/pages/auth/auth_one.dart';
import './ui/pages/main_page.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitDown, DeviceOrientation.portraitUp])
.then((_) => runApp(MyApp()));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String userId;
#override
void initState() {
_getUserId().then((id) => userId = id);
super.initState();
}
#override
Widget build(BuildContext context) {
print(userId);
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.deepPurple),
debugShowCheckedModeBanner: false,
home: userId == null ? AuthOne() : MainPage(),
);
}
_getUserId() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var id = prefs.getString('id');
return id;
}
}

Your _getUserId method is async, so you will have to refresh the widget after you get the result.
Use this:
#override
void initState() {
_getUserId().then((id) {
//calling setState will refresh your build method.
setState(() {
userId = id;
});
});
super.initState();
}

This is happening because you are trying to use the value before its calculated.
you could use timer function for delay

Related

Flutter Amplify DataStore plugin has not been added to Amplify

All of the sudden I am getting the error DataStore plugin has not been added to Amplify, recoverySuggestion: Add DataStore plugin to Amplify and call configure before calling DataStore related APIs to rule out any of the work I was doing on that page I tried it on a fresh page with the same result.
I already did execute amplify codegen models, amplify pull and amplify env pull. Also tried to do a flutter clean but I don't see any change at all. I'm really puzzled and can't seem to figure out the issue.
One thing I did notice while debugging was that the initState of the screen seems to be executed earlier as the configureAmplify callback.
I will show the relevant parts of the code (sorry for the long code in advance).
Pubspec.yaml
dependencies:
...
amplify_flutter: ^0.2.10
amplify_datastore: ^0.2.10
amplify_api: ^0.2.10
amplify_auth_cognito: ^0.2.10
amplify_storage_s3: ^0.2.10
main.dart
import 'package:flutter/material.dart';
import 'package:my_package/screens/main/teams_screen.dart';
import 'package:my_package/services/amplify_services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
AmplifyService.configureAmplify();
}
#override
Widget build(BuildContext context) {
...
}
}
services/amplify_services.dart
import 'package:flutter/foundation.dart';
import 'package:amplify_flutter/amplify.dart';
import 'package:amplify_datastore/amplify_datastore.dart';
import 'package:amplify_api/amplify_api.dart';
import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
import 'package:amplify_storage_s3/amplify_storage_s3.dart';
import 'package:my_package/models/ModelProvider.dart';
import 'package:my_package/amplifyconfiguration.dart';
class AmplifyService {
static configureAmplify() async {
AmplifyAPI apiPlugin = AmplifyAPI();
AmplifyAuthCognito authPlugin = AmplifyAuthCognito();
AmplifyStorageS3 amplifyStorageS3 = AmplifyStorageS3();
AmplifyDataStore dataStorePlugin = AmplifyDataStore(
modelProvider: ModelProvider.instance,
);
await Amplify.addPlugins([
dataStorePlugin,
authPlugin,
amplifyStorageS3,
apiPlugin,
]);
try {
await Amplify.configure(amplifyconfig);
} on AmplifyAlreadyConfiguredException {
if (kDebugMode) {
print(
"Amplify was already configured. Looks like app restarted on android.");
}
}
}
}
Lastly the very basic page with not even an output (screens/teams_screen.dart)
import 'dart:async';
import 'package:amplify_datastore/amplify_datastore.dart';
import 'package:amplify_flutter/amplify.dart';
import 'package:flutter/material.dart';
import 'package:my_package/models/Team.dart';
class TeamsScreen extends StatefulWidget {
const TeamsScreen({Key? key}) : super(key: key);
#override
_TeamsScreenState createState() => _TeamsScreenState();
}
class _TeamsScreenState extends State<TeamsScreen> {
late StreamSubscription<QuerySnapshot<Team>> _teamsSubscription;
bool _isLoading = true;
List<Team> teams = [];
#override
void initState() {
super.initState();
_initializeApp();
}
#override
void dispose() {
_teamsSubscription.cancel();
super.dispose();
}
Future<void> _initializeApp() async {
_teamsSubscription = Amplify.DataStore.observeQuery(Team.classType)
.listen((QuerySnapshot<Team> snapshot) {
setState(() {
if (_isLoading) _isLoading = false;
teams = snapshot.items;
});
});
}
#override
Widget build(BuildContext context) {
return Container();
}
}
New day, fresh mind. The issue turned out to be quite simple, I didn't set an _isLoading state to indicate weter or not the configureAmplify callback was completed and let the app just continue loading all the other screens triggering the error. So after setting the state and only adding the rest of the app after the state was changed it worked without any problem.
To fix it I did the following:
import 'package:flutter/material.dart';
import 'package:my_package/screens/main/teams_screen.dart';
import 'package:my_package/services/amplify_services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
_initializeApp();
}
Future<void> _initializeApp() async {
await AmplifyService.configureAmplify(); // note the await!
setState(() {
_isLoading = false; // important to set the state!
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: _isLoading
? Center(child: CircularProgressIndicator())
: const MainScreen(), // _isLoading is very important here.
);
}
}

Provider dont update a data in Flutter

I'm create a project on Flutter. And I'm using a provider to change screens in my app.
Here is my main.dart file:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/services/auth_service.dart';
import 'package:school_app/wrapper.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthService().auth,
child: MaterialApp(
home: Wrapper(),
),
);
}
}
Also this is my wrapper.dart file where the screens choose:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/screens/authenticate/auth.dart';
import 'package:school_app/models/user.dart';
import 'package:school_app/screens/school/home.dart';
import 'package:school_app/services/auth_service.dart';
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<AuthProvider>(context);
print(user.auth);
if(!user.auth) return Auth();
return Home();
}
}
And it is my AuthProvider class:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
/* AuthUser _user(User user) {
return user != null ? AuthUser(uid: user.uid) : null;
}*/
AuthProvider auth = new AuthProvider();
//sign in
Future signIn(String username, String password) async {
try {
SharedPreferences prefs = await SharedPreferences.getInstance();
var dio = Dio();
Response user = await dio.post('url', data: {
'username': username,
'password': password
});
if(user.data['success'] == false) return user.data['msg'];
await prefs.setString('token', user.data['token']);
auth.setAuth(true);
print("SUCCESS");
} catch(e) {
print('Error ' + e.toString());
}
}
}
class AuthProvider with ChangeNotifier {
bool _auth;
AuthProvider() {
_auth = false;
}
bool get auth => _auth;
void setAuth(bool auth) {
_auth = auth;
notifyListeners();
}
}
And when I call a function in AuthProvider class setAuth, nothing changed. Can you help me and find my mistake?
EDIT
I'm making all changes that you writes but it is not working. Here is my main.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/services/auth_service.dart';
import 'package:school_app/wrapper.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthProvider(),
child: MaterialApp(
home: Wrapper(),
),
);
}
}
Also wrapper.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/screens/authenticate/auth.dart';
import 'package:school_app/screens/school/home.dart';
import 'package:school_app/services/auth_service.dart';
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
#override
void initState() {
// TODO: implement initState
super.initState();
AuthService().auth;
}
#override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(builder: (context, authProvider, child) {
print(authProvider.auth);
if (!authProvider.auth) {
return Auth();
} else {
return Home();
}
});
}
}
And AuthService and AuthProvider classes:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
/* AuthUser _user(User user) {
return user != null ? AuthUser(uid: user.uid) : null;
}*/
AuthProvider auth = new AuthProvider();
//sign in
Future signIn(String username, String password) async {
try {
SharedPreferences prefs = await SharedPreferences.getInstance();
var dio = Dio();
Response user = await dio.post('url', data: {
'username': username,
'password': password
});
if(user.data['success'] == false) return user.data['msg'];
await prefs.setString('token', user.data['token']);
auth.setAuth(true);
print("SUCCESS");
} catch(e) {
print('Error ' + e.toString());
}
}
}
class AuthProvider with ChangeNotifier {
bool _auth;
AuthProvider() {
_auth = false;
}
bool get auth => _auth;
void setAuth(bool auth) {
_auth = auth;
notifyListeners();
}
}
Notice, that here two classes and in AuthService I'm calling function .setAuth(true).
In your current implementation of Wrapper, you are rendering the widget once and not listening to whether the values changed. You could use Consumer as suggested above. You could also choose to watch the value for changes - like this:
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = context.watch<AuthProvider>();
print(user.auth);
if(!user.auth) return Auth();
return Home();
}
}
When you use a watch or Consumer pattern, the widget will be rendered when the values of the underlying store (which is AuthProvider here) gets changed.
The only missing part here is that you never Consume the AuthProvider to listen to the notifyListeners() trigger.
The correct implementation looks like the following (I didn't try it, you may have to correct some typo errors, but you'll get the idea !)
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(
builder: (context, authProvider, child) {
if (!authProvider.auth) {
return Auth();
} else {
return Home();
}
}
);
}
}
EDIT
I didn't notice you weren't injecting the right Class in your ChangeNotifierProvider. You'll also have to update your widget MyApp
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthProvider(),
child: MaterialApp(
home: Wrapper(),
),
);
}
And in this case you probably should transform your Wrapper widget to a Stateful widget, and in the initState method you should call AuthService().auth.
I strongly recommend you to read the official documentation of Provider, looks like things aren't crystal clear yet in your mind
EDIT 2
You're still missing the point of the Provider library.
The goal of this lib is to provide an instance of a class to your widget tree so you don't have to re-create an instance in each widget.
Here, in AuthService class you're re-creating a AuthProvider with AuthProvider auth = new AuthProvider(); instead of referring to the existing instance created in the parent Widget.
To refer to a previously created instance, you should use Provider.of<AuthProvider>(context); in the AuthService class, or, even better, pass the instance of AuthProvider as a parameter in the signIn method.

Flutter-How do I switch from login screen to home and back? Back-end works but can't show screen without manually refresh

1.this is the main entry
void main() {
WidgetsFlutterBinding.ensureInitialized();
StorageUtil.getInstance();
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Wrapper(),
);
}
}
This is the Wrapper. The log-in form or the home page do not show unless I manually hot-reload the app.
I've tried everything but i am stuck. Please help.
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
User _user = User();
#override
Widget build(BuildContext context) {
_user.uId = StorageUtil.getString('access_token');
if(_user.uId != null && _user.uId != ""){
print('filled ${_user.uId}');
return Home();
}else{
print('empty ${_user.uId}');
return Authenticate();
}
}
}
I think your StorageUtil is giving you promise for get data back to you but you are not waiting for it when app loads at first time.You can try await StorageUtil.getInstance(); in main block.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await StorageUtil.getInstance();
runApp(MaterialApp(home: MyApp()));
}
You need to watch the instance. Right now you are grabbing the instance to get the value but you are not subscribing to the value itself, which means that when the value changes nothing will happen until you refresh the page. I recommend subscribing to the value (access_token) that is determining the login screen vs the home screen.
Flutter has some built in features that makes this a bit easier such as streams and or quicker widgets like the ValueListenerBuilder. Let's see if we can do that with StorageUtil.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await StorageUtil.getInstance();
runApp(MaterialApp(home: MyApp()));
}
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
User _user = User();
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: StorageUtil.getString('access_token');,
builder: (BuildContext context, String value, _) {
if(value != null && value != ""){
print('filled ${_user.uId}');
return Home();
} else {
print('empty ${_user.uId}');
return Authenticate();
}
},
),
}
}
It is rough but it should get the job done! I recommend probably finding a more streamlined way to store your state than just the StorageUtil that'll better scale as your application grows.

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

How can I do if seen check on custom splash screen? If walkthrough screen is already seen

Im doing a splashscreen first app, to be followed by a walkthrough page if the user first used the app, else go to a welcome page to sign in/ sign up if already saw the walkthrough screen.
My code came from this projects main.dart file: https://github.com/instaflutter/flutter-login-screen-firebase-auth-facebook-login and modified it to this code(from splashscreen tutorial FlutterKart)
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:binder/ui/screens/root_screen.dart';
import 'package:binder/ui/screens/walk_screen.dart';
void main() {
Firestore.instance.settings(timestampsInSnapshotsEnabled: true);
SharedPreferences.getInstance().then((prefs) {
SplashScreen(prefs: prefs);
});
}
class SplashScreen extends StatefulWidget {
final SharedPreferences prefs;
SplashScreen({Key key,this.prefs}): super(key: key);
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
Timer(Duration(seconds: 3), () => _handleCurrentScreen(context));
}
#override
Widget build(BuildContext context) {
final logowhite = Hero(
tag: 'hero',
child: //code insert flutterkart splashscreen
)
],
),
)
],
)
],
),
);
}
Widget _handleCurrentScreen (BuildContext context) {
bool seen = (widget.prefs.getBool('seen') ?? false);
if (seen) {
return new RootScreen();
} else {
return new WalkthroughScreen(prefs: widget.prefs);
}
}
}
I want it to show the splashscreen first and directed to the rootscreen if already seen and to the walkthrough screen if first use.
You'd probably want to use shared_preferences or something similar. Something like this:
// add this static variable somewhere
// could technically be initialized during splash screen and added to a Provider or something similar after
static SharedPreferences prefs;
// make `main` async if it is not already
Future<void> main() async {
prefs = await SharedPreferences.getInstance();
...
}
Future<void> onSplashScreenDone() async {
if (prefs.getBool('isFirstTime') ?? true) {
// you might want to put this at the end of your walkthrough, so they don't miss it if they close the app, for example
await prefs.setBool('isFirstTime', false);
// this is their first time, show walkthrough, etc.
...
} else {
// this is not their first time, do normal things.
}
}