I'm trying to implement user authentication.
To log user in I created a Provider called Auth:
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import '../../utilities/http_exception.dart';
class Auth extends ChangeNotifier {
String _userId;
String _userUUID;
String _token;
DateTime _expiryDate;
bool get isAuth {
return token != null;
}
String get token {
if (_token != null) {
return _token;
}
return null;
}
Future<void> login(String email, String password) async {
const String url = "http://10.0.2.2:8000/users/api/token/";
try {
final response = await http.post(
url,
headers: {"Content-Type": "application/json"},
body: json.encode(
{
"email": email,
"password": password,
},
),
);
final responseData = json.decode(response.body);
if (response.statusCode == 200 && responseData["token"] != null) {
_token = responseData["token"];
_userUUID = responseData["user"]["uuid"];
notifyListeners();
} else {
print("SOMETHING WENT WRONG HERE");
}
} catch (e) {
throw e;
}
}
}
As you can see I called notifyListeners() to notify listeners to do some operations.
What kind of operations?
I used a Consumer around my MateriaApp widget and for home parameter I've checked weather the token is null or not.
if it's null OK, load GeneralAuth screen otherwise load Home screen.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return OrientationBuilder(
builder: (context, orientation) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: Auth()),
ChangeNotifierProvider.value(value: Users()),
ChangeNotifierProvider.value(value: Teachers()),
],
child: Consumer<Auth>(
builder: (context, auth, _) => MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
supportedLocales: [Locale("fa", "IR")],
onGenerateRoute: Router.generateRoute,
initialRoute: GeneralAuthRoute,
onUnknownRoute: (settings) => MaterialPageRoute(
builder: (context) => Undefined(name: settings.name),
),
locale: Locale("fa", "IR"),
theme: ThemeData(
fontFamily: "IranSans",
primaryColor: Color(0xFF212121),
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: Colors.black.withOpacity(0),
),
),
home: auth.isAuth ? Home() : GeneralAuth(),
),
),
);
},
);
},
);
}
}
Looks like everything is OK because as I said I called notifyListeners() but after receiving token even though I have token and auth.isAuth returns true, screen won't load up.
What is the problem?
Home property is something that is not dynamically changing your screen, and this behavior is expected. What you need to do is you need to follow through logic where you wait for login endpoint to execute, and based on response you get, you would need to decide on the screen that gets presented.
Example of that would be this:
await login('a#a.com', '12345');
if (Auth.isAuth) {
// Present here Home screen
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Home()),
);
}
Related
I am trying to load make a dashboard and now developing the login site. It works that the user gets and email but when I click on the link provided in the email, the "FirebaseAuth.instance.isSignInWithEmailLink($link)" returns false, because $link is "localhost:8080/login" (the current page) instead of the link that has been sent via email.
Here is the FirebaseAuthService code:
class FirebaseAuthService implements AuthService {
FirebaseAuthService() {
_initialize();
}
Future<void> _initialize() async {
/// Set auth persistance for web so user stays signed in
await FirebaseAuth.instance.setPersistence(Persistence.LOCAL);
print('debug// window.location.href: ' + window.location.href);
print('debug// Uri.base.toString(): ' + Uri.base.toString());
print('debug2// window.localStorage[email]: ' + window.localStorage['email'].toString());
/// idk man...
FirebaseAuth.instance.authStateChanges().listen((User? firebaseUser) {
if (firebaseUser == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});
/// Checks if the incoming link is the OTP email link.
// if (FirebaseAuth.instance.isSignInWithEmailLink(Uri.base.toString())) {
if (FirebaseAuth.instance.isSignInWithEmailLink(window.location.href)) {
print('in method debug2// window.location.href: ' + window.location.href);
print('in method debug2// html.window.document.referrer: ' + (window.document as HtmlDocument).referrer);
print('in method debug// Uri.base.toString(): ' + Uri.base.toString());
print('in method debug2// window.localStorage[email]: ' + window.localStorage['email'].toString());
if (kDebugMode) print('Trying to sign in the user with OTP');
try {
await FirebaseAuth.instance
.signInWithEmailLink(
email: window.localStorage['email'] ?? '',
emailLink: window.location.href,
)
.timeout(const Duration(seconds: 10))
.then((value) => print('value: ${value.toString()}'));
} catch (_) {
print('Exceptino.... $_');
}
window.localStorage.remove('email');
if (kDebugMode) print('Successfully signed in the user with OTP');
}
}
#override
bool get isSignedIn => FirebaseAuth.instance.currentUser != null;
#override
Future<void> signOut() async {
await FirebaseAuth.instance.signOut().timeout(const Duration(seconds: 10));
}
}
And here is my main class where FirebaseAuthService is provided (with the provider package):
class VamosEventsDashboard extends StatelessWidget {
VamosEventsDashboard();
final GoRouter _vamosRouter = GoRouter(
debugLogDiagnostics: true,
initialLocation: EventsPage.route,
errorBuilder: (_, __) => const ErrorPage(),
routes: [
GoRoute(path: EventsPage.route, builder: (_, __) => const EventsPage()), // events
GoRoute(path: LoginPage.route, builder: (_, __) => const LoginPage()), // login
],
redirect: (BuildContext context, GoRouterState state) {
return context.watch<AuthService>().isSignedIn ? EventsPage.route : LoginPage.route; // todo change back to events
},
);
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// Data sources and services
Provider<OrganizationDataSource>(create: (_) => const FirestoreDataSource()),
Provider<AuthService>(create: (_) => FirebaseAuthService()),
],
child: MultiProvider(
providers: [
// View models
ChangeNotifierProvider(
create: (context) => OrganizationViewModel(organizationDataSource: context.read<OrganizationDataSource>()),
),
ChangeNotifierProvider(create: (_) => LoginViewModel()),
],
child: MaterialApp.router(
theme: vamosTheme,
routerConfig: _vamosRouter,
title: 'vamos! Events Dashboard',
),
),
);
}
}
I am not Abel to listen changes in my main.dart file while I am using consumer widget basically I am trying to login and save accessToken in variable and I want to redirect to home page from routes when it is true
following is mine main.dart file
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => UserBookings(),
),
ChangeNotifierProvider(
create: (context) => AuthenticationDetails(),
),
ChangeNotifierProvider(create: (context) => FetchPlaces()),
ChangeNotifierProvider(
create: (context) => Authclass(),
),
],
child: Consumer<AuthenticationDetails>(
builder: (context, value, _) => MaterialApp(
primarySwatch: Colors.red,
),
home: value.isAuth ? PrepareRide() : Otp(),
routes: {
'verifyotp': (context) =>
value.isAuth ? const PrepareRide() : const VerifyOtp(),
'mapview': (context) => const PrepareRide(),
},
following is my login class
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'cabapi.dart';
class AuthenticationDetails extends ChangeNotifier {
int? sessionId;
String? acessToken;
DateTime? acessTokenExpiryDate;
String? refreshToken;
DateTime? refreshTokenExpiryDate;
bool get isAuth {
return token != null;
}
String? get token {
if (acessTokenExpiryDate != null &&
acessTokenExpiryDate!.isAfter(DateTime.now()) &&
acessToken != null) {
return acessToken;
}
return null;
}
bool get refresh {
return refreshtoken != null;
}
String? get refreshtoken {
if (refreshTokenExpiryDate != null &&
refreshTokenExpiryDate!.isAfter(DateTime.now()) &&
refreshToken != null) {
return acessToken;
}
return null;
}
Future<void> registerUser(String? phoneNumber, BuildContext context) async {
final url = Ninecabsapi().urlHost + Ninecabsapi().login;
try {
var response = await http.post(Uri.parse(url),
headers: {'Content-Type': 'application/json; charset=UTF-8'},
body: json.encode({'mobileno': phoneNumber}));
var userDetails = json.decode(response.body);
//print(userDetails);
// switch (response.statusCode) {
// case 201:
// print(acessToken);
// //showsnackbar(context, "logged in");
// break;
// }
sessionId = userDetails['data']['session_id'];
acessToken = userDetails['data']['access_token'];
acessTokenExpiryDate = DateTime.now().add(
Duration(seconds: userDetails['data']['access_token_expires_in']),
);
refreshToken = userDetails['data']['refresh_token'];
refreshTokenExpiryDate = DateTime.now().add(
Duration(seconds: userDetails['data']['refresh_token_expires_in']),
);
print(isAuth);
notifyListeners();
} catch (e) {
print(e);
}
}
}
I am getting response and storing data in variables but not Abel to listen data can any one help me please
you can see here consumer is not listening to my listeners when auth is true
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.
I am using Provider for app state management in Flutter, Auth data is stored in shared preferences and I want to load it using provider when app starts, what is the best way to do it.
I am going to use auth status to decide whether user should see login screen or Dashboard screen
this is part of a code i used when i started flutter, implement rest of the functions as you wish.
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './providers/authentication.dart';
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => Authentication(),
),
],
child: Consumer<Authentication>(
builder: (ctx, auth, _) => MaterialApp(
title: 'MyApp',
home: auth.isAuthorized
? MyScreen()
: FutureBuilder(
future: auth.tryLogin(),
builder: (ctx, authResultSnapshot) =>
authResultSnapshot.connectionState ==
ConnectionState.waiting
? SplashScreen()
: AuthScreen(),
),
authentication.dart
String _token;
DateTime _expiryDate;
String _userId;
bool get isAuthorized {
// that is a very simple check
return token != null;
}
Future<bool> tryLogin() async {
final prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey('userData')) {
return false;
}
final extractedUserData = json.decode(prefs.getString('userData')) as Map<String, Object>;
final expiryDate = DateTime.parse(extractedUserData['expiryDate']);
if (expiryDate.isBefore(DateTime.now())) {
return false;
}
_token = extractedUserData['token'];
_userId = extractedUserData['userId'];
_expiryDate = expiryDate;
notifyListeners();
return true;
}
Something like this:
Future<void> main() async {
final appState = await loadAppStateFromSharedData();
runApp(
Provider.value(
value: appState,
child: MyApp(),
),
);
}
Try this one!
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<Auth>(
builder: (ctx, auth, _) => MaterialApp(
title: 'AppName',
home: auth.isAuth
? DashboardScreen()
: AuthScreen(),
),
),
};
}
what I am trying to do is check if user is logged in already or not. there is a futurebuilder inside consumer which is notifying listeners. check code:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: Auth(),
)
],
child: Consumer<Auth>(
builder: (ctx, auth, _) {
print('making it again and again');
return MaterialApp(
title: 'MY app',
theme: ThemeData(
primarySwatch: Colors.blue,
accentColor: Colors.deepOrange,
),
home: auth.isAuth
? FirstScreen()
: FutureBuilder(
future: auth.tryAutoLogin(), //inside this function there is notifylisteners()
builder: (ctx, authResultSnapShot) =>
authResultSnapShot.connectionState ==
ConnectionState.waiting
? SplashScreen()
: AuthScreen(),
));
},
),
);
}
What I get:
this is rebuilding the app again and again. tryautologin is called repeatedly.
What I want:
check for is a user logged in only once the app is started.
Future<bool> tryAutoLogin() async {
final prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey('userData')) {
return false;
}
final extractedUserData = json.decode(prefs.getString('userData'));
final expiryDate = DateTime.parse(extractedUserData['expiryDate']);
_accessToken = extractedUserData['accessToken'];
_refreshToken = extractedUserData['refreshToken'];
print(extractedUserData['userId']);
_userId = extractedUserData['userId'];
if (expiryDate.isBefore(DateTime.now())) { //for now this if statement is not running
try {
await _getAccessToken();
} catch (error) {
return false;
}
}
print('tryautologin');
notifyListeners();
_autoGetAccessToken(); //this function doesn't have any notifylisteners
return true;
}
edited:
String get accessToken {
if (_expiryDate != null &&
_expiryDate.isAfter(DateTime.now()) &&
_accessToken != null) {
return _accessToken;
}
return null;
}
bool get isAuth {
print(accessToken != null);
return accessToken != null;
}
Even the question is old quite a bit of time, this answer will help another. I have faced the problem also. this happens when you use a future builder inside a consumer. it is rebuilding again and again infinitely. So, instead of the future builder try using an if-else statement.
child: Consumer<Auth>(
builder: (ctx, auth, _) {
//Sizedbox() used for nullsafety
Widget child = SizedBox();
if (auth.isAuth) {
child = FirstScreen();
} else if (!auth.triedAutoLogin) {
auth.tryAutoLogin();
child = SplashScreen();
} else {
child = AuthScreen();
}
return MaterialApp(
title: 'MY app',
theme: ThemeData(
primarySwatch: Colors.blue,
accentColor: Colors.deepOrange,
),
home: child,
);
},
),
inside Auth class keep a boolean variable like triedAutoLogin to keep track.