Login Flow Navigation using FutureBuilder: Flutter? - flutter

import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<AppThemeNotifier>(
builder: (BuildContext context, AppThemeNotifier value, Widget child) {
return Provider(
create: "XXXXXXX",
dispose: "XXXXXXX",
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: AppTheme.getThemeFromThemeMode(value.themeMode()),
home: Base()),
);
},
);
}
}
class Base extends StatefulWidget {
#override
_BaseState createState() => _BaseState();
}
class _BaseState extends State<Base> {
#override
Widget build(BuildContext context) {
return FutureBuilder<SessionAuth>(
future: Provider.of<AppThemeNotifier>(context, listen: false).validate,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
SessionAuth session = new SessionAuth();
if (session.userId != null && session.isLoggedIn) {
return FullApp();
} else if (isFirst) {
return OnBoardingScreen();
}
return LoginScreen();
} else {
return Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
},
);
}
}
I was trying to navigate different screens based on session validation is done through the provider package. (Login Flow Management)
Simply, I want to replace the Screens based on session details
Break Down
If userId is not equal to null and isLoggedin is true -> FullApp
else If user isFirst is true -> OnBoardingScreen
else Login Screen
Error
flutter: The method '>=' was called on null.
flutter: Receiver: null
flutter: Tried calling: >=(0.0)
SessionAuth
class SessionAuth {
SessionAuth({this.isLoggedIn, this.userId, this.isFirst});
int userId;
bool isLoggedIn;
bool isFirst;
}
validate
Future<SessionAuth> get validate async {
SharedPreferences prefs = await SharedPreferences.getInstance();
SessionAuth auth = new SessionAuth();
auth.userId = prefs.getInt('userId') ?? null;
auth.isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
auth.isFirst = prefs.getBool("isFirst") ?? null;
return auth;
}

Related

Why is flutter printing out widget name?

I have a problem with flutter printing out the name and rendering Widget name after running the application
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? loggedIn = prefs.getBool('loggedin');
if (loggedIn == true) {
Home();
} else {
return LoginOrSignup();
}
}
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(body:SafeArea(
child: FutureBuilder(
future: autoLogin(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return Text('${snapshot.data}');
} else {
return LoginOrSignup();
}
}),
))
);
}
}
After running the app the output is LoginOrSignup()
class LoginOrSignup extends StatelessWidget {
const LoginOrSignup({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: MaterialButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Login()),
);
},
child: Text('Loginsss'),
),
),
Center(
child: MaterialButton(
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Signup()),
);
},
child: Text('Signup'),
),
)
],
),
);
}
}
I have tried using another widget like Text() but it still prints out the same when i run the application on a mobile app. The problem seems to appear in the autoLogin() function that i have
The issue is your future return Widget itself, and when you use Text('${snapshot.data}') it print the widget, To simplfity this you can return data from Future(this is what mostly we do). Let say you like to return widget itself.
A little correction is needed on Future.
Future<Widget> autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? loggedIn = prefs.getBool('loggedin');
if (loggedIn == true) {
return Home();
} else {
return LoginOrSignup();
}
}
And
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: FutureBuilder<Widget>(
future: autoLogin(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return snapshot.data!;
} else {
return LoginOrSignup();
}
}),
)));
You are returning a Widget in autoLogin function. Instead you should return a bool.
Future<bool?> autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? loggedIn = prefs.getBool('loggedin');
if (loggedIn == null) return null;
if (loggedIn == true) {
return true;
} else {
return false;
}
}
Then in the FutueBuilder you can check if it's then return Home()
if (snapshot.hasData && snapshot.data! == true) {
return Home();
} else {
return LoginOrSignup();

Implement verification user exist

I would like to check if the user has already filled in the registration form:
Here is my code for the connectionState:
class LandingPage extends StatelessWidget {
// final Geolocator _geolocator = Geolocator()..forceAndroidLocationManager;
#override
Widget build(BuildContext context) {
final auth = Provider.of<AuthBase>(context, listen: false);
return StreamBuilder<User>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
User user = snapshot.data;
if (user == null) {
return SignInPage();
} else {
// _geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.best)
MatchEngine.instance.initialise(user.uid);
return Chat();
}
} else {
return Scaffold(
body: MyAppsCircularProgressIndicator(title: "MyApp",),
);
}
},
);
}
}
this code works fine for connectionstate.
I would like to add in the first code:
if (not signed in) {
show sign in page
} else {
if (not registered)
show register page
else
show home page
}
or
StreamBuilder(
stream: auth.authStateChanges()
builder: (_, snapshot) {
// check connectionState too
if (snapshot.hasData) {
StreamBuilder(
stream: database.userData() // this is a stream you create that reads from `userData/$uid` or similar
builder: (_, snapshot) {
if (snapshot.hasData) {
return HomePage()
} else {
return RegisterPage()
}
}
)
} else {
return SignInPage()
}
}
)
I would like to add the last code to the previous one to have my connectionstate + my redirection to RegisterPage.
I tried everything but to no avail ... could someone help me? Thank you
You could use the provider package and then create a seperate file which has the following code. I personally use this and it works well.
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user == null) {
return SignIn();
} else {
return Dashboard();
}
}
}
and in your main.dart file where you are building the material app. Put the wrapper (or whatever you name it) widget instead such as the following.
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return FutureBuilder(
// Initialize FlutterFire
future: Firebase.initializeApp(),
builder: (context, snapshot) {
// Check for errors
if (snapshot.hasError) {
return ErrorPage();
}
// Show Application
if (snapshot.connectionState == ConnectionState.done) {
return StreamProvider<Help4YouUser>.value(
value: AuthService().user,
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: Wrapper(),
),
);
}
// Initialization
return LoadingWidget();
},
);
}
}
Any clarification needed please comment

Error: Class 'String' has no instance getter 'token'. I/flutter ( 3268): Receiver: "dc9e0de8fa2eaa917657e810db06aad2458e4f65"

I have been struggling with this problem for like two days. My social media app should save its state, when signed in so that when you leave the app and come back again it should start from the home page, not the sign in page. I have found that it is possible to do this with StreamBuilder and FutureBuilder. I have tried some things with FutureBuilder and I have some errors.
Below is how my main page looks like:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => UserData(),
child: MaterialApp(
title: 'Curtain App',
debugShowCheckedModeBanner: false,
home: FutureBuilder(
future: SharedPreferencesHelper.getPrefs(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasData) {
Provider.of<UserData>(context).currentUserId =
snapshot.data.token;
return HomeScreen();
} else {
return LoginScreen();
}
},
),
),
);
}
}
class SharedPreferencesHelper {
static final String _tokenCode = "token";
static Future<String> getPrefs() async {
final SharedPreferences preferences = await SharedPreferences.getInstance();
return preferences.getString(_tokenCode) ?? "empty";
}
}
And this is my LoginPage submit btn code:
_submit() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
// logging in the user w/ Firebase
//AuthService.login(_email, _password);
var user = await DatabaseService.loginUser(_username, _password);
final data = json.decode(user);
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
print("Hi ${data['username']}");
print("Status ${data['status']}");
print("Token ${data['token']}");
if (data['username'] != null) {
setState(() {
_message = "Hi ${data['username']}";
sharedPreferences.setString('token', data['token']);
});
Navigator.of(context).pushAndRemoveUntil(
CupertinoPageRoute(
builder: (context) => HomeScreen(),
),
(Route<dynamic> route) => false);
}
}
}
Any ideas on how to solve this ?
Just remove the .token from the line where the error occurs. snapshot.data already is the token.

Flutter + SharedPreferences: a build function returned null

I'm trying to get access to the SharedPreferences and create a permanent file named "first_run".
At the first start of the application it should return "true" and then change it to false.
I declared a future function that return true or false based on that.
Now i got a Wrapper() widget that shows either Loading... , the HomeScreen() or the LoginScreen()
based on the result of the future function.
Why is it that the build function returns null ?
How can I avoid the "first_run" to get deleted when I update the app ?
Here's the code:
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../screens/home_screen.dart';
import '../screens/login_screen.dart';
import '../providers/auth_provider.dart';
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
FirebaseAuth auth = FirebaseAuth.instance;
#override
void initState() {
AuthProvider().isUserLoggedIn();
super.initState();
}
#override
Widget build(BuildContext context) {
FutureBuilder(
future: hasAlreadyStarted(),
builder: (context, snapshot) {
if (snapshot.hasData == true) {
return LoginScreen();
} else {
return CircularProgressIndicator(
backgroundColor: Colors.deepOrange,
);
}
},
);
}
Future <bool> hasAlreadyStarted() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getBool("first_run") == true) {
prefs.setBool("first_run", false);
return true;
} else {
return false;
}
}
}
You need return keyword
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: hasAlreadyStarted(),
builder: (context, snapshot) {
if (snapshot.hasData == true) {
return LoginScreen();
} else {
return CircularProgressIndicator(
backgroundColor: Colors.deepOrange,
);
}
},
);
}
Apart from the fact that you need a return, (which the other answer already pointed out), you should also not produce a new Future every time the build method is called.
Future<bool> _yourFuture;
#override
void initState() {
AuthProvider().isUserLoggedIn();
super.initState();
_yourFuture = hasAlreadyStarted(); // <= start it once
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _yourFuture, // <= reference it for every build

Flutter Future<bool> I want to return boolean value

I am new in Flutter, I am trying to get it from StatelessWidget Class, but it's not allow me to get return value from Future Method, How do i get return value?
class HomePage extends StatelessWidget{
#override
Widget build(BuildContext context) {
Widget _defaultPage = userLogin();
var loginStatus = isLoggedIn();
print(loginStatus);
if(loginStatus == true){
_defaultPage = NoteList();
}
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.purple,
),
debugShowCheckedModeBanner: false,
title: "User notes",
home: _defaultPage,
routes: <String, WidgetBuilder> {
'/login': (BuildContext context) => new userLogin(),
'/note_list' : (BuildContext context) => new NoteList(),
'/note_detail' : (BuildContext context) => new NoteDetail(),
},
);
}
}
Future method :
Future<bool> isLoggedIn() async{
SharedPreferences sharedPreferences;
sharedPreferences = await SharedPreferences.getInstance();
String userId = sharedPreferences.getString("userId");
bool isLogin = sharedPreferences.getBool("userLoggedIn");
return isLogin;
}
But it's keep saying Instance of 'Future' in log.
You should wait for it to finish.
// in async function
var loginStatus = await isLoggedIn();
or
var loginStatus;
isLoggedIn().then((onValue){
loginStatus = onValue;
})
in your case you should use future builder
home: FutureBuilder<bool>(
future: isLoggedIn(),
builder: (context, snapshot){
if(snapshot.hasData){
return snapshot.data ? NoteList() : userLogin();
}
return CircularProgressIndicator();
})
EDIT:
Future<bool> isLoggedIn() async {
final sharedPreferences = await SharedPreferences.getInstance();
final isContains = sharedPreferences.containsKey("userLoggedIn");
return isContains
? sharedPreferences.getBool("userLoggedIn")
: throw NullThrownError();
}