I'm beginner in Flutter, Riverpod for the state management and firebase for the authentication.
I'm looking to retrieve the logged user's email to pass to my postgres database and retrieve all the user information. In a first time, I just try to display the nickname of the current user. I am facing a problem when I log out of the app to log back in. The auth providers are not updated so I get informations from the very first connected user. For example currentUserEmailProvider still get the first connected user email. Any help is welcome, I'm really stuck.
My auth_repository.dart:
class AuthRepository {
const AuthRepository(this._auth);
final FirebaseAuth _auth;
Stream<User?> get authStateChange => _auth.idTokenChanges();
Stream<User?>get authUserChange => _auth.userChanges();
String? get currentUserEmail => _auth.currentUser?.email;
Future<User?> signInWithEmailAndPassword(
String email, String password) async {
_auth.currentUser?.reload();
try {
final result = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
return result.user;
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
throw const ErrorHandler(message: 'User not found');
} else if (e.code == 'wrong-password') {
throw const ErrorHandler(message : 'Wrong password');
} else {
throw const ErrorHandler(message: 'An error occurred. Please try again later');
}
}
}
Future<AppUser?> registerWithEmailAndPassword(String email, String password, String nickname, String role, String firstname, String lastname) async {
// Sans ces deux lignes la création d'un nouveau compte entraîne un login automatique sur ce compte
FirebaseApp app = await Firebase.initializeApp(
name: 'Secondary', options: Firebase.app().options);
try {
AppUser? appUser = await UserRepository(email).saveUser(email, nickname, role, firstname, lastname);
if(appUser != null) {
try {
UserCredential result =
await FirebaseAuth.instanceFor(app: app).createUserWithEmailAndPassword(email: email, password: password);
User? user = result.user;
if(user == null) {
throw Exception("user from firebase not found");
}
return appUser;
} on FirebaseException catch(e) {
await UserRepository(email).deleteUser(email);
print(ErrorHandler(message: e.code.toString()));
}
} else {
throw Exception("user from postgres database not found");
}
} on PostgreSQLException catch(e) {
print(ErrorHandler(message: e.code.toString()));
}
return null;
}
Future<void> signOut() async {
await _auth.signOut();
}
}
My user_repository.dart:
class UserRepository {
final String email;
PostgreSQLConnection? connection;
UserRepository(this.email){
connection = (connection == null || connection!.isClosed == true
? PostgreSQLConnection(
'10.0.2.2', 5432, DatabaseAccess.databaseName,
queryTimeoutInSeconds: 3600,
timeoutInSeconds: 3600,
username: DatabaseAccess.databaseUser,
password: DatabaseAccess.databasePassword) : connection);
}
}
Future<AppUser?> getCurrentUser(String? currentEmail) async {
print(currentEmail);
try {
await connection!.open();
final result = await connection!.mappedResultsQuery(
'select * from public.user where email = #emailValue',
substitutionValues: {
'emailValue': currentEmail,
},
allowReuse: true,
timeoutInSeconds: 30,
);
final userFromDataBase = result[0]['user']!;
return AppUser(
email: userFromDataBase['email'],
nickname: userFromDataBase['nickname'],
role: userFromDataBase['role'],
firstname: userFromDataBase['firstname'],
lastname: userFromDataBase['lastname'],
);
} on PostgreSQLException catch(e) {
print(ErrorHandler(message: e.toString()));
return null;
}
}
}
My providers.dart:
final authRepositoryProvider = Provider<AuthRepository>((ref) {
return AuthRepository(FirebaseAuth.instance);
});
final authStateProvider = StreamProvider<User?>((ref) {
return ref.read(authRepositoryProvider).authStateChange;
});
final currentUserEmailProvider = Provider<String?>((ref) {
return AuthRepository(FirebaseAuth.instance).currentUserEmail;
});
final userRepositoryProvider = Provider.autoDispose<UserRepository>((ref) {
return UserRepository(ref.read(currentUserEmailProvider)!);
});
final futureCurrentUserProvider = Provider<Future<AppUser?>>((ref) {
return ref.read(userRepositoryProvider).getCurrentUser(ref.read(currentUserEmailProvider));
});
final currentUserProvider = FutureProvider.autoDispose<AppUser?>((ref) => ref.read(userRepositoryProvider).getCurrentUser(ref.read(currentUserEmailProvider)));
My home_screen.dart:
class HomeScreen extends HookConsumerWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final currentUser = ref.watch(currentUserProvider);
return Scaffold(
body: currentUser.when(
data: (user) => _buildBody(context, user, ref),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => _errorBody(context, ref),
)
);
}
Widget _buildBody(BuildContext context, AppUser? user, WidgetRef ref) {
if(user == null) {
return _errorBody(context, ref);
} else {
return Center(child: Text(
'Welcome ${user.getNickname}',
style: const TextStyle(fontSize: 20),
));
}
}
Widget _errorBody(BuildContext context, WidgetRef ref) {
return const Center(child: Text(
"Error: No user found",
style: TextStyle(fontSize: 20, color: Colors.red),
));
}
}
Try changing
final currentUserProvider = FutureProvider.autoDispose<AppUser?>((ref) => ref.read(userRepositoryProvider).getCurrentUser(ref.read(currentUserEmailProvider)));
to
final currentUserProvider = FutureProvider.autoDispose<AppUser?>((ref) => ref.read(userRepositoryProvider).getCurrentUser(ref.watch(currentUserEmailProvider)));
and changing
final currentUserEmailProvider = Provider<String?>((ref) {
return AuthRepository(FirebaseAuth.instance).currentUserEmail;
});
to
final currentUserEmailProvider = Provider<String?>((ref) {
return ref.read(authRepositoryProvider).currentUserEmail;
});
Ok I resolved my problem with this following. To be simplier I removed the singleton in my user_repository.dart. The problem was from my providers and I found a way to use the authUserChanges() method :
providers.dart :
final futureCurrentUserProvider = Provider<Future<AppUser?>>((ref) {
return UserRepository().getCurrentUser(ref.watch(emailChangeProvider));
});
final currentUserProvider = FutureProvider.autoDispose<AppUser?>((ref) => UserRepository().getCurrentUser(ref.watch(emailChangeProvider)));
final authChangeProvider = StreamProvider<User?>((ref) {
return ref.read(authRepositoryProvider).authUserChange;
});
final emailChangeProvider = Provider<String?>((ref) {
return ref.watch(authChangeProvider).value?.email;
});
Related
I have a simple controller like this
class UserController with ChangeNotifier {
UserData user = UserData();
UserData get userdata => user;
void setUser(UserData user) {
user = user;
print(user.sId);
notifyListeners();
}
login(data) async {
var response = await ApiService().login(data);
final databody = json.decode(response);
if (databody['success']) {
UserData authUser = UserData.fromJson(databody['data']);
setUser(authUser);
notifyListeners();
return true;
} else {
return false;
}
}
}
I am trying to just print it like this on both widget and in initstate function but values are showing null. I can see in set function value is not null.
print('id ${context.watch<UserController>().user.sId.toString()}');
print(
'id2 ${Provider.of<UserController>(context, listen: false).user.sId.toString()}');
I already have added
ChangeNotifierProvider(create: (_) => UserController()),
],
in main.dart in MultiProvider
Also on Tap of login button I am doing this
showLoader(context);
UserController auth = Provider.of<UserController>(
context,
listen: false);
var data = {
"userEmail":
emailController.text.trim().toLowerCase(),
"userPassword": passwordController.text.trim(),
};
auth.login(data).then((v) {
if (v) {
hideLoader(context);
context.go('/homeroot');
} else {
hideLoader(context);
Fluttertoast.showToast(
backgroundColor: green,
textColor: Colors.white,
msg:
'Please enter correct email and password');
}
});
Try to include this while naming is same,
void setUser(UserData user) {
this.user = user;
print(user.sId);
notifyListeners();
}
Follow this structure
class UserController with ChangeNotifier {
UserData user = UserData();
UserData get userdata => user;
void setUser(UserData user) {
this.user = user;
print(user.sId);
notifyListeners();
}
Future<bool> login(String data) async {
await Future.delayed(Duration(seconds: 1));
UserData authUser = UserData(sId: data);
setUser(authUser);
notifyListeners();
return true;
}
}
class HPTest extends StatelessWidget {
const HPTest({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<UserController>(
builder: (context, value, child) {
return Text(value.user.sId);
},
),
floatingActionButton: FloatingActionButton(onPressed: () async {
final result = await Provider.of<UserController>(context, listen: false)
.login("new ID");
print("login $result");
;
}),
);
}
}
This is the NotesView:
import 'package:app_two/lib/constants/routes.dart';
import 'package:app_two/lib/enums/menu_action.dart';
import 'package:app_two/lib/services/auth/auth_service.dart';
import 'package:app_two/lib/services/auth/crud/notes_service.dart';
import 'package:flutter/material.dart';
class NotesView extends StatefulWidget {
const NotesView({super.key});
#override
State<NotesView> createState() => _NotesViewState();
}
class _NotesViewState extends State<NotesView> {
late Future _myFuture;
late final NotesService _notesService;
String get userEmail => AuthService.firebase().currentUser!.email!;
#override
void initState() {
super.initState();
_notesService = NotesService();
_myFuture = getGet();
}
// #override //delete
// void dispose() {
// _notesService.close();
// super.dispose();
// }
getGet() async {
return await _notesService.getOrCreateUser(email: userEmail);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Your Notes'),
actions: [
IconButton(
onPressed: () {
Navigator.of(context).pushNamed(newNoteRoute);
},
icon: const Icon(Icons.add),
),
PopupMenuButton<MenuAction>(
onSelected: (value) async {
switch (value) {
case MenuAction.logout:
final shouldLogout = await showLogOutDialog(context);
if (shouldLogout) {
await AuthService.firebase().logOut();
Navigator.of(context).pushNamedAndRemoveUntil(
loginRoute,
(_) => false,
);
}
}
},
itemBuilder: (context) {
return const [
PopupMenuItem<MenuAction>(
value: MenuAction.logout,
child: Text('Logout'),
),
];
},
)
],
),
body: FutureBuilder(
future: _myFuture,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
return StreamBuilder(
stream: _notesService.allNotes,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
case ConnectionState.active:
if (snapshot.hasData) {
final allNotes = snapshot.data as List<DatabaseNote>;
return ListView.builder(
itemCount: allNotes.length,
itemBuilder: (context, index) {
final note = allNotes[index];
return ListTile(
title: Text(
note.text,
maxLines: 1,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
);
},
);
} else {
return const CircularProgressIndicator();
}
default:
return const CircularProgressIndicator();
}
},
);
default:
return const CircularProgressIndicator();
}
},
),
);
}
Future<bool> showLogOutDialog(BuildContext context) {
return showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Sign out'),
content: const Text('Are you sure you want to sign out?'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: const Text('Cancel')),
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: const Text('Logout'))
],
);
},
).then((value) => value ?? false);
}
}
//Notes_service
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' show join;
import 'crud_exceptions.dart';
class NotesService {
Database? _db;
List<DatabaseNote> _notes = [];
static final NotesService _shared = NotesService._sharedInstance();
NotesService._sharedInstance() {
_notesStreamController = StreamController<List<DatabaseNote>>.broadcast(
onListen: () {
_notesStreamController.sink.add(_notes);
//populate stream with a stream of notes that we've already read from the database
},
);
}
factory NotesService() => _shared; //singleton
late final StreamController<List<DatabaseNote>> _notesStreamController;
Stream<List<DatabaseNote>> get allNotes => _notesStreamController.stream;
Future<DatabaseUser?> getOrCreateUser({required String email}) async {
try {
final user = await getUser(email: email);
return user;
} on CouldNotFindUser {
final createdUser = await createUser(email: email);
return createdUser;
} on DatabaseIsNotOpen {
open();
} catch (e) {
print(e.toString());
rethrow;
}
}
Future<void> _cacheNotes() async {
final allNotes = await getAllNotes();
_notes = allNotes.toList();
_notesStreamController.add(_notes);
}
Future<DatabaseNote> updateNote({
required DatabaseNote note,
required String text,
}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
//make sure note exists
await getNote(id: note.id);
//update db
final updatesCount = await db.update(noteTable, {
textColumn: text,
isSyncedwithCloudColumn: 0,
});
if (updatesCount == 0) {
throw CouldNotUpdateNote();
} else {
final updatedNote = await getNote(id: note.id);
_notes.removeWhere((note) => note.id == updatedNote.id);
_notes.add(updatedNote);
_notesStreamController.add(_notes);
return updatedNote;
}
}
Future<Iterable<DatabaseNote>> getAllNotes() async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
final notes = await db.query(noteTable);
return notes.map((noteRow) => DatabaseNote.fromRow(noteRow));
}
Future<DatabaseNote> getNote({required int id}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
final notes = await db.query(
noteTable,
limit: 1,
where: 'id = ?',
whereArgs: [id],
);
if (notes.isEmpty) {
throw CouldNotFindNote();
} else {
final note = DatabaseNote.fromRow(notes.first);
_notes.removeWhere((note) => note.id == id);
_notesStreamController.add(_notes);
return note;
}
}
Future<int> deleteAllNotes() async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
final numberOfdeletions = await db.delete(noteTable);
_notes = [];
_notesStreamController.add(_notes);
return numberOfdeletions;
}
Future<void> deleteNote({required int id}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
final deletedCount = await db.delete(
noteTable,
where: 'id = ?',
whereArgs: [id],
);
if (deletedCount == 0) {
throw CouldNotDeleteNote();
} else {
_notes.removeWhere((note) => note.id == id);
_notesStreamController.add(_notes);
}
}
Future<DatabaseNote> createNote({required DatabaseUser owner}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
//Make sure owner exists in database withthe correct id
final dbUser = await getUser(email: owner.email);
if (dbUser != owner) {
throw CouldNotFindUser();
}
const text = '';
//create the note
final noteId = await db.insert(noteTable, {
userIdColumn: owner.id,
textColumn: text,
isSyncedwithCloudColumn: 1,
});
final note = DatabaseNote(
id: noteId,
userId: owner.id,
text: text,
isSyncedWithCloud: true,
);
_notes.add(note);
_notesStreamController.add(_notes);
return note;
}
Future<DatabaseUser> getUser({required String email}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
final results = await db.query(
userTable,
limit: 1,
where: 'email = ?',
whereArgs: [email.toLowerCase()],
);
if (results.isEmpty) {
throw CouldNotFindUser();
} else {
return DatabaseUser.fromRow(results.first);
}
}
Future<DatabaseUser> createUser({required String email}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
final results = await db.query(
userTable,
limit: 1,
where: 'email = ?',
whereArgs: [email.toLowerCase()],
);
if (results.isNotEmpty) {
throw UserAlreadyExists();
}
final userId = await db.insert(userTable, {
emailColumn: email.toLowerCase(),
});
return DatabaseUser(id: userId, email: email);
}
Future<void> deleteUser({required String email}) async {
final db = _getDatabaseorThrow();
final deletedCount = await db.delete(
userTable,
where: 'email = ?',
whereArgs: [email.toLowerCase()],
);
if (deletedCount != 1) {
throw CouldNotDeleteUser();
}
}
Database _getDatabaseorThrow() {
final db = _db;
if (db == null) {
throw DatabaseIsNotOpen();
} else {
return db;
}
}
Future<void> close() async {
final db = _db;
if (db == null) {
try {
await open();
} on DatabaseIsNotOpen {
open();
}
// throw DatabaseIsNotOpen();
} else {
await db.close();
_db = null;
}
}
Future<void> _ensureDbIsOpen() async {
if (_db != null) {
try {
await open();
} on DatabaseAlreadyOpenException {
//empty
}
}
}
Future<void> open() async {
if (_db != null) {
throw DatabaseAlreadyOpenException();
}
try {
await _ensureDbIsOpen();
final docsPath = await getApplicationDocumentsDirectory();
final dbPath = join(docsPath.path, dbName);
final db = await openDatabase(dbPath);
_db = db;
//create user table
await db.execute(createUserTable);
//create note table
await db.execute(createNoteTable);
await _cacheNotes();
} on MissingPlatformDirectoryException {
throw UnableToGetDocumentDirectory();
}
}
}
#immutable
class DatabaseUser {
final int id;
final String email;
const DatabaseUser({
required this.id,
required this.email,
});
DatabaseUser.fromRow(Map<String, Object?> map)
: id = map[idColumn] as int,
email = map[emailColumn] as String;
#override
String toString() => 'Person, ID = $id, email= $email';
#override
bool operator ==(covariant DatabaseUser other) => id == other.id;
#override
int get hashCode => id.hashCode;
}
class DatabaseNote {
final int id;
final int userId;
final String text;
final bool isSyncedWithCloud;
DatabaseNote({
required this.id,
required this.userId,
required this.text,
required this.isSyncedWithCloud,
});
DatabaseNote.fromRow(Map<String, Object?> map)
: id = map[idColumn] as int,
userId = map[userIdColumn] as int,
text = map[textColumn] as String,
isSyncedWithCloud =
(map[isSyncedwithCloudColumn] as int) == 1 ? true : false;
#override
String toString() =>
'Note, ID = $id, userId = $userId, isSyncedWithCloud = $isSyncedWithCloud, text = $text';
#override
bool operator ==(covariant DatabaseNote other) => id == other.id;
#override
int get hashCode => id.hashCode;
}
const dbName = 'notes.db';
const noteTable = 'note';
const userTable = 'user';
const idColumn = 'id';
const emailColumn = 'email';
const userIdColumn = 'user_id';
const textColumn = 'text';
const isSyncedwithCloudColumn = 'is_synced_with_cloud';
const createUserTable = '''CREATE TABLE IF NOT EXISTS "user" (
"id" INTEGER NOT NULL,
"email" TEXT NOT NULL UNIQUE,
PRIMARY KEY("id" AUTOINCREMENT)
);''';
const createNoteTable = '''CREATE TABLE IF NOT EXISTS "note" (
"id" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"text" TEXT,
"is_synced_with_cloud" INTEGER DEFAULT 0,
FOREIGN KEY("user_id") REFERENCES "user"("id"),
PRIMARY KEY("id" AUTOINCREMENT)
);''';
I'm making a note app and the NotesView clears all other notes and replaces it with my most recent not. I have no idea why this is happening
Each note you input should display on a tile. But instead this happens:
But only after I restart the app. In the next images, I add a new note and then restart the application
I believe I did something very in my StreamBuilder but don't know what.
I'd appreciate any assistance.
Try changing the case ConnectionState.active to case ConnectionState.done in your StreamBuilder.
Duplication may be occurring due to building your list inside case ConnectionState.active.
I don't know what your db.update does or your SQL query looks like but if you don't pass the id how are you supposed to update the note?
Future<DatabaseNote> updateNote({
required DatabaseNote note,
required String text,
}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
//make sure note exists
await getNote(id: note.id);
//update db
final updatesCount = await db.update(noteTable, { ///how does magically know what note to update?
textColumn: text,
isSyncedwithCloudColumn: 0,
});
if (updatesCount == 0) {
throw CouldNotUpdateNote();
} else {
final updatedNote = await getNote(id: note.id);
_notes.removeWhere((note) => note.id == updatedNote.id);
_notes.add(updatedNote);
_notesStreamController.add(_notes);
return updatedNote;
}
}
If you're using INSERT OR REPLACE INTO because you don't pass the id you're creating a new row instead of updating it and your cache list is giving you false information because at the end you think you update it correctly and just replace the note with the same id, your cache list doesn't reflect the real information anymore.
If you're updating all notes in your table because the where clause doesn't have an id you will present your current problem when restarting
Check your db layer apart to see if you're actually doing what you want.
Hello I am doing my fist App in Flutter and i am just setting up the authentication with FireBase, and a mistake its happenning. Im trying to manage the state and navigate between Home and Login with the streambuilder, and its working fine but only if i do a hot restart on Android Studio after registering a new user. After the hot reload it works fine, i am able to login with any of the existing users and it navigates to the home screen, and when i log out goes back to login screen, but if i create new user than just stops working, the screens do not alterate anymore when i log in with any of the old users...
here its my main
class _MyAppState extends State<MyApp> {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(primaryColor: cPrimary),
home: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.active){
if(snapshot.hasData){
return Home_Screen();
} else if (snapshot.hasError){
return Center(child: Text('${snapshot.error}'),);
}
} else if (snapshot.connectionState == ConnectionState.waiting){
return Center(child: CircularProgressIndicator(color: cPrimary,));
}
return LoginScreen();
}
)
);
}
}
Here is my login and register functions
//Sign Up
Future <void> signUp({
required String email,
required String password,
required String username,
}) async {
try {
if(email.isNotEmpty && password.isNotEmpty && username.isNotEmpty){
UserCredential userCredential = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
//add user to database
await _firestore.collection('users').doc(userCredential.user!.uid).set({
'username' : username,
'email' : email,
'uid' : userCredential.user!.uid,
});
}
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
print('The password provided is too weak.');
} else if (e.code == 'email-already-in-use') {
print('The account already exists for that email.');
}
} catch (e) {
print(e);
}
}
//Log In
Future <void> logIn({
required String email,
required String password,
}) async {
try {
if(email.isNotEmpty && password.isNotEmpty ){
UserCredential userCredential = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);}
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
print('No user found for that email.');
} else if (e.code == 'wrong-password') {
print('Wrong password provided for that user.');
}
}
}
and here its how i am calling the functions on my screens
ElevatedButton(
onPressed: () async {
setState(() {
_isLoading = true;
});
await AuthService().logIn(
email: _emailController.text,
password: _passwordController.text,);
setState(() {
setState(() {
_isLoading = false;
});
});
},
ElevatedButton(
onPressed: () async {
setState(() {
_isLoading = true;
});
await AuthService().signUp(
email: _emailController.text,
password: _passwordController.text,
username: _usernameController.text,
);
setState(() {
_isLoading = false;
});
After creating the user account, ensure you log in as well.
//Sign Up
Future <void> signUp({
required String email,
required String password,
required String username,
}) async {
try {
if(email.isNotEmpty && password.isNotEmpty && username.isNotEmpty){
UserCredential userCredential = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
//add user to database
await _firestore.collection('users').doc(userCredential.user!.uid).set({
'username' : username,
'email' : email,
'uid' : userCredential.user!.uid,
});
}
// add this
await logIn(email: email, password: password);
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
print('The password provided is too weak.');
} else if (e.code == 'email-already-in-use') {
print('The account already exists for that email.');
}
} catch (e) {
print(e);
}
}
I want to write a simple registration function and this is my code:
auth_screen.dart:
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;
});
}
auth.dart:
Future<void> signup(String email, String password) async {
return _authenticate(email, password, 'register');
}
Future<void> _authenticate(
String email, String password, String urlSegment) async {
final url = Uri.parse('http://10.0.2.2:8000/api/$urlSegment');
// final url = Uri.http('http://localhost:8000/api/', 'urlSegment');
try {
final response = await http.post(
url,
body: json.encode(
{
'email': email,
'password': password,
//'returnSecureToken': true,
},
),
);
final responseData = json.decode(response.body);
if (responseData['error'] != null) {
throw HttpException(responseData['error']['message']);
}
_token = responseData['idToken'];
_userId = responseData['localId'];
_expiryDate = DateTime.now().add(
Duration(
seconds: int.parse(
responseData['expiresIn'],
),
),
);
_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;
}
}
This is the backend(Deno) part of the project:
auth_controller.ts:
async register(ctx: RouterContext) {
const { email, password } = await ctx.request.body().value;
let user = await User.findOne({ email });
if (user) {
ctx.response.status = 422;
ctx.response.body = { message: "Email is already exist" };
return;
}
const hashedPassword = hashSync(password);
user = new User({ email, password: hashedPassword });
await user.save();
ctx.response.status = 201;
ctx.response.body = {
id: user.id,
name: user.name,
email: user.email
};
}
And this is the user.ts class:
export default class User extends BaseModel {
public id: string = "";
public name: string = "";
public email: string = "";
public password: string = "";
constructor({ id = "", name = "", email = "", password = "" }) {
super();
this.id = id;
this.name = name;
this.email = email;
this.password = password;
}
static async findOne(params: object): Promise<User | null> {
const user = await userCollection.findOne(params);
if (!user) {
return null;
}
return new User(User.prepare(user));
}
async save() {
const _id = await userCollection.insertOne(this);
this.id = _id;
return this;
}
}
I get this error message when I want to test the application on Android emulator:
type 'Null' is not a subtype of type 'String'
When I try the backend server using Postman and send post request to http://0.0.0.0:8000/api/register address. I get correct response and it works, but I don't know why do I get Null response using the Flutter app?
I tried to print the variables in both front and backend side of the application and it seems they all are good and correct but I can not understand why do I get this error message?!
probably you got a null value in your map in this part since map return a null value when can't find a key, check if the Map value is not null
try {
if (_authMode == AuthMode.Login) {
// Log user in
await Provider.of<Auth>(context, listen: false).login(
_authData['email'] as String, // can return a null value
_authData['password'] as String, // can return a null value
);
} else {
// Sign user up
await Provider.of<Auth>(context, listen: false).signup(
_authData['email'] as String, // can return a null value
_authData['password'] as String, // can return a null value
);
}
}
Seems you're getting a null as a response value instead of a String.
EDIT:
Future<void> signup(String email, String password) async {
return _authenticate(email, password, 'register');
}
Check email and password are not null. And getting the correct values.
Actually the problem was from these two lines of code:
_token = responseData['idToken'];
_userId = responseData['localId'];
Because it wasn't passing these data in response from the backend side.
I'm trying to call the service like this, I think something wrong over here because the "result" return Future and cant assign to String, or how to parse Future to String?
and MyApi return Json without Key Something like this
body
ReturnStatus
so I'm trying to store the response directly into the user var, but still not working
UI (Button Login)
login.loginProcess
? Center(child: CircularProgressIndicator())
: RaisedButton(
color: myPrimaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Login",
style: TextStyle(
color: Colors.black,
fontFamily: "NunitoSansBold")),
],
),
padding:
EdgeInsets.only(top: 16.0, bottom: 16.0),
onPressed: () {
print("clicked Button Login");
**login.authenticationUser(context);**
},
),
Service
class AuthService {
Future<User> authenticateUser(String id, String password) async {
var user;
try {
final resAuth = await http.post(
"${BaseUrl.auth}api/AuthenticateUser",
body: {"login": id, "password": password},
);
if (resAuth.statusCode == 200) {
user = resAuth.body;
}
} catch (e) {
return user;
}
return user;
}
}
Provider with ChangeNotifier (For handling the business logic and stuf)
authenticationUser(BuildContext context) async {
if (_controllerEmail.text != "" && _controllerPassword.text != "") {
loginProcess = true;
final ioc = new HttpClient();
ioc.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
//TODO StoredSharedPreference
SharedPreferences _preferences = await SharedPreferences.getInstance();
try {
AuthService authService = new AuthService();
var result = authService.authenticateUser(
_controllerEmail.text, _controllerPassword.text);
showSnackBar(result);
_preferences.setString("status", "seen");
} catch (e) {
print(e);
showSnackBar(result.toString());
}
loginProcess = false;
} else {
autoValidate = true;
}
}
Model
class User {
String status;
String id;
String userName;
User({
this.status,
this.id,
this.userName,
});
factory User.fromJson(Map<String, dynamic> json) =>
User(status: json["status"], id: json["id"], userName: json["userName"]);
Map<String, dynamic> toJson() =>
{"status": status, "id": id, "userName": userName};
}
=======UPDATE , Change the method authenticationUser (add await)=======
Service
class AuthService {
Future<User> authenticateUser(String id, String password) async {
var user;
try {
final resAuth = await http.post(
"${BaseUrl.auth}api/AuthenticateUser",
body: {"login": id, "password": password},
);
if (resAuth.statusCode == 200) {
user = resAuth.body;
}
} catch (e) {
return user;
// throw Exception(e.toString());
}
return user;
}
}
Provider
authenticationUser(BuildContext context) async {
if (_controllerEmail.text != "" && _controllerPassword.text != "") {
loginProcess = true;
final ioc = new HttpClient();
ioc.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
//TODO StoredSharedPreference
SharedPreferences _preferences = await SharedPreferences.getInstance();
try {
AuthService authService = new AuthService();
var result = await authService.authenticateUser(
_controllerEmail.text, _controllerPassword.text);
_preferences.setString("status", "seen");
showSnackBar(result.toString());
} catch (e) {
print(e);
/* showSnackBar(e.toString());*/
}
loginProcess = false;
} else {
autoValidate = true;
}
}
in catch (e) {
print(e);
value of e is
String' is not a subtype of type 'FutureOr<User>'
==Update Add Image Value from resAuth==
resAuthValue
You probably need to add await.
var result = await authService.authenticateUser(
_controllerEmail.text, _controllerPassword.text);
Edit
Change your authenticateUser method to this
Future authenticateUser(String id, String password) async {
var user;
try {
final resAuth = await http.post(
"${BaseUrl.auth}api/AuthenticateUser",
body: {"login": id, "password": password},
);
if (resAuth.statusCode == 200) {
return resAuth.body;
}
} catch (e) {
return false;
}
return false;
}
Change provider code as below
try {
AuthService authService = new AuthService();
var result = await authService.authenticateUser(
_controllerEmail.text, _controllerPassword.text);
_preferences.setString("status", "seen");
if(result!=false)
showSnackBar(result.toString());
} catch (e) {
print(e);
/* showSnackBar(e.toString());*/
}