I am trying to write a simple signup/login page. The following codes are the main.dart
, screen
page for the view and the auth_controller
for the control page of the project.
main.dart:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: Auth(),
),
],
child: Consumer<Auth>(
builder: (ctx, auth, _) => MaterialApp(
title: 'MyShop',
theme: ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.deepOrange,
fontFamily: 'Lato',
),
home: auth.isAuth
? MapScreen()
: FutureBuilder(
future: auth.tryAutoLogin(),
builder: (ctx, authResultSnapshot) =>
authResultSnapshot.connectionState ==
ConnectionState.waiting
? SplashScreen()
: AuthScreen(),
),
routes: {
MapScreen.routeName: (ctx) => MapScreen(),
},
),
),
);
}
}
auth_screen.dart:
enum AuthMode { Signup, Login }
class AuthScreen extends StatelessWidget {
static const routeName = '/auth';
@override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;
// final transformConfig = Matrix4.rotationZ(-8 * pi / 180);
// transformConfig.translate(-10.0);
return Scaffold(
// resizeToAvoidBottomInset: false,
body: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
const Color.fromRGBO(215, 117, 255, 1).withOpacity(0.5),
const Color.fromRGBO(255, 188, 117, 1).withOpacity(0.9),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
stops: const [0, 1],
),
),
),
SingleChildScrollView(
child: Container(
height: deviceSize.height,
width: deviceSize.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
margin: const EdgeInsets.only(bottom: 20.0),
padding: const EdgeInsets.symmetric(
vertical: 8.0, horizontal: 94.0),
transform: Matrix4.rotationZ(-8 * pi / 180)
..translate(-10.0),
// ..translate(-10.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.deepOrange.shade900,
boxShadow: const [
BoxShadow(
blurRadius: 8,
color: Colors.black26,
offset: Offset(0, 2),
)
],
),
child: Text(
'USmobile',
style: TextStyle(
color: Theme.of(context)
.textSelectionTheme
.selectionColor,
fontSize: 50,
fontFamily: 'Anton',
fontWeight: FontWeight.normal,
),
),
),
),
Flexible(
flex: deviceSize.width > 600 ? 2 : 1,
child: const AuthCard(),
),
],
),
),
),
],
),
);
}
}
class AuthCard extends StatefulWidget {
const AuthCard({
Key? key,
}) : super(key: key);
@override
_AuthCardState createState() => _AuthCardState();
}
class _AuthCardState extends State<AuthCard>
with SingleTickerProviderStateMixin {
final GlobalKey<FormState> _formKey = GlobalKey();
AuthMode _authMode = AuthMode.Login;
Map<String, String> _authData = {
'email': '',
'password': '',
};
var _isLoading = false;
final _passwordController = TextEditingController();
AnimationController? _controller;
Animation<Offset>? _slideAnimation;
Animation<double>? _opacityAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 300,
),
);
_slideAnimation = Tween<Offset>(
begin: const Offset(0, -1.5),
end: const Offset(0, 0),
).animate(
CurvedAnimation(
parent: _controller as Animation<double>,
curve: Curves.fastOutSlowIn,
),
);
_opacityAnimation = Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller as Animation<double>,
curve: Curves.easeIn,
),
);
// _heightAnimation.addListener(() => setState(() {}));
}
@override
void dispose() {
super.dispose();
_controller!.dispose();
}
void _showErrorDialog(String message) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('An Error Occurred!'),
content: Text(message),
actions: <Widget>[
TextButton(
child: const Text('Okay'),
onPressed: () {
Navigator.of(ctx).pop();
},
)
],
),
);
}
Future<void> _submit() async {
if (!_formKey.currentState!.validate()) {
// Invalid!
return;
}
_formKey.currentState!.save();
setState(() {
_isLoading = true;
});
try {
if (_authMode == AuthMode.Login) {
// Log user in
await Provider.of<Auth>(context, listen: false).login(
_authData['email'] as String,
_authData['password'] as String,
);
} else {
// Sign user up
await Provider.of<Auth>(context, listen: false).signup(
_authData['email'] as String,
_authData['password'] as String,
);
}
// } on HttpException catch (error) {
// var errorMessage = 'Authentication failed';
// print("this is the auth data");
// print(_authData);
// if (error.toString().contains('EMAIL_EXISTS')) {
// errorMessage = 'This email address is already in use.';
// } else if (error.toString().contains('INVALID_EMAIL')) {
// errorMessage = 'This is not a valid email address';
// } else if (error.toString().contains('WEAK_PASSWORD')) {
// errorMessage = 'This password is too weak.';
// } else if (error.toString().contains('EMAIL_NOT_FOUND')) {
// errorMessage = 'Could not find a user with that email.';
// } else if (error.toString().contains('INVALID_PASSWORD')) {
// errorMessage = 'Invalid password.';
// }
// _showErrorDialog(errorMessage);
} catch (error) {
var errorMessage = 'Could not authenticate you. Please try again later.'
error.toString();
_showErrorDialog(errorMessage);
}
setState(() {
_isLoading = false;
});
}
void _switchAuthMode() {
if (_authMode == AuthMode.Login) {
setState(() {
_authMode = AuthMode.Signup;
});
_controller!.forward();
} else {
setState(() {
_authMode = AuthMode.Login;
});
_controller!.reverse();
}
}
@override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
elevation: 8.0,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
height: _authMode == AuthMode.Signup ? 320 : 260,
// height: _heightAnimation.value.height,
constraints:
BoxConstraints(minHeight: _authMode == AuthMode.Signup ? 320 : 260),
width: deviceSize.width * 0.75,
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
TextFormField(
decoration: const InputDecoration(labelText: 'E-Mail'),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value!.isEmpty || !value.contains('@')) {
return 'Invalid email!';
}
},
onSaved: (value) {
_authData['email'] = value as String;
},
),
TextFormField(
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
controller: _passwordController,
validator: (value) {
if (value!.isEmpty || value.length < 5) {
return 'Password is too short!';
}
},
onSaved: (value) {
_authData['password'] = value as String;
},
),
AnimatedContainer(
constraints: BoxConstraints(
minHeight: _authMode == AuthMode.Signup ? 60 : 0,
maxHeight: _authMode == AuthMode.Signup ? 120 : 0,
),
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
child: FadeTransition(
opacity: _opacityAnimation as Animation<double>,
child: SlideTransition(
position: _slideAnimation as Animation<Offset>,
child: TextFormField(
enabled: _authMode == AuthMode.Signup,
decoration: const InputDecoration(
labelText: 'Confirm Password'),
obscureText: true,
validator: _authMode == AuthMode.Signup
? (value) {
if (value != _passwordController.text) {
return 'Passwords do not match!';
}
}
: null,
),
),
),
),
const SizedBox(
height: 20,
),
if (_isLoading)
const CircularProgressIndicator()
else
ElevatedButton(
child:
Text(_authMode == AuthMode.Login ? 'LOGIN' : 'SIGN UP'),
onPressed: _submit,
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
primary: Theme.of(context).primaryColor,
padding: const EdgeInsets.symmetric(
horizontal: 30.0, vertical: 8.0),
onPrimary:
Theme.of(context).primaryTextTheme.button!.color,
),
),
TextButton(
child: Text(
'${_authMode == AuthMode.Login ? 'SIGNUP' : 'LOGIN'} '),
onPressed: _switchAuthMode,
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 30.0, vertical: 4),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
textStyle: TextStyle(color: Theme.of(context).primaryColor),
),
),
],
),
),
),
),
);
}
}
auth_controller.dart:
class Auth with ChangeNotifier {
String? _token;
DateTime? _expiryDate;
String? _userId;
Timer? _authTimer;
bool get isAuth {
return token != null;
}
String? get token {
if (_expiryDate != null &&
_expiryDate!.isAfter(DateTime.now()) &&
_token != null) {
return _token;
}
return null;
}
String? get userId {
return _userId;
}
Future<void> _authenticate(
String email, String password, String urlSegment) async {
final url = Uri.parse('http://10.0.2.2:8000/api/$urlSegment');
try {
final http.Response response = await http.post(
url,
headers: {"Content-Type": "application/json"},
body: json.encode(
{
'email': email,
'password': password,
//'returnSecureToken': true,
},
),
);
print("This is response.body");
print(response.body);
print("This is response.body.runtimeType");
print(response.body.runtimeType);
final responseData = json.decode(response.body);
print("this is responseData");
print(responseData);
print("this is responseData['error']");
print(responseData['error']);
if (responseData['error'] != null) {
throw HttpException(responseData['error']['message']);
} else {
print("There wasn't error here!");
}
_token = responseData['idToken'];
print("This is the token");
print(_token);
_userId = responseData['id'];
print("This is userId");
print(_userId);
print("This is expiryDate");
print(responseData['expiresIn']);
_expiryDate = DateTime.now().add(
Duration(
seconds: int.parse(
responseData['expiresIn'],
),
),
);
print("I can no see thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis");
_autoLogout();
notifyListeners();
final prefs = await SharedPreferences.getInstance();
final userData = json.encode(
{
'token': _token,
'userId': _userId,
'expiryDate': _expiryDate!.toIso8601String(),
},
);
prefs.setString('userData', userData);
} catch (error) {
throw error;
}
}
Future<void> signup(String email, String password) async {
return _authenticate(email, password, 'register');
}
Future<void> login(String email, String password) async {
return _authenticate(email, password, 'verifyPassword');
}
Future<bool> tryAutoLogin() async {
final prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey('userData')) {
return false;
}
final extractedUserData = json.decode(prefs.getString('userData') as String)
as Map<String, Object>;
final expiryDate =
DateTime.parse(extractedUserData['expiryDate'] as String);
if (expiryDate.isBefore(DateTime.now())) {
return false;
}
_token = extractedUserData['token'] as String;
_userId = extractedUserData['userId'] as String;
_expiryDate = expiryDate;
notifyListeners();
_autoLogout();
return true;
}
Future<void> logout() async {
_token = null;
_userId = null;
_expiryDate = null;
if (_authTimer != null) {
_authTimer!.cancel();
_authTimer = null;
}
notifyListeners();
final prefs = await SharedPreferences.getInstance();
// prefs.remove('userData');
prefs.clear();
}
void _autoLogout() {
if (_authTimer != null) {
_authTimer!.cancel();
}
final timeToExpiry = _expiryDate!.difference(DateTime.now()).inSeconds;
_authTimer = Timer(Duration(seconds: timeToExpiry), logout);
}
}
I know the backend part works fine and the following is the result I do get while running my app and try to register a new user through the Android Emulator:
I/flutter ( 6814): This is response.body
I/flutter ( 6814): {"id":"619c5141e08b15bf9173ab06","name":"","email":"[email protected]","password":"$2a$10$yYUtDS1PrSAgpQBxoVm7Ae7O8ujR33ONz4gM5t7iHWt6e907pjFrq","idToken":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0QHRlc3RpbmcuY29tIiwiZXhwIjoxNjM5Mjc1NjAzNzQzfQ.LgrlwFNrLhGr5UfHikL83e2tBjbqMyN4OKG6Fz816AeOs6ezfnhvQoXBDAsV2pIt4CWuBVf8qGbBYSXCQxgarw","expiresIn":1639275603743}
I/flutter ( 6814): This is response.body.runtimeType
I/flutter ( 6814): String
I/flutter ( 6814): this is responseData
I/flutter ( 6814): {id: 619c5141e08b15bf9173ab06, name: , email: test@testing.com, password: $2a$10$yYUtDS1PrSAgpQBxoVm7Ae7O8ujR33ONz4gM5t7iHWt6e907pjFrq, idToken: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0QHRlc3RpbmcuY29tIiwiZXhwIjoxNjM5Mjc1NjAzNzQzfQ.LgrlwFNrLhGr5UfHikL83e2tBjbqMyN4OKG6Fz816AeOs6ezfnhvQoXBDAsV2pIt4CWuBVf8qGbBYSXCQxgarw, expiresIn: 1639275603743}
I/flutter ( 6814): this is responseData['error']
I/flutter ( 6814): null
I/flutter ( 6814): There wasn't error here!
I/flutter ( 6814): This is the token
I/flutter ( 6814): eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0QHRlc3RpbmcuY29tIiwiZXhwIjoxNjM5Mjc1NjAzNzQzfQ.LgrlwFNrLhGr5UfHikL83e2tBjbqMyN4OKG6Fz816AeOs6ezfnhvQoXBDAsV2pIt4CWuBVf8qGbBYSXCQxgarw
I/flutter ( 6814): This is userId
I/flutter ( 6814): 619c5141e08b15bf9173ab06
I/flutter ( 6814): This is expiryDate
I/flutter ( 6814): 1639275603743
I see all the print()
statements work except this:
print("I can no see thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis");
I don't know why? I also see the following error message in my Android Emulator that I can't understand the reason?
type 'int' is not a subtype of type 'String'
I also know the error occures in this part of the code in auth_controller.dart
file:
} catch (error) {
print(error);
throw error;
}
CodePudding user response:
int.parse(
responseData['expiresIn'],
)
For the above code to work responseData['expiresIn']
should be a String
. But in the response, this comes as an int
value. Make sure this is a String
.
Or you can just use it like this,
Duration(
seconds: responseData['expiresIn'],
),