CRUD Methods for single Model (not a list) with Provider - flutter

How can I define methods(functions) for a single model that is not a list with Flutter's Provider? For example, I have made 4 functions for a Model that is a list:
List<User> _userList = [];
List<User> get userList => _userList;
//method for getiing and setting the list of users
setUserList(List<User> list) {
_userList = list;
notifyListeners();
}
// method for removing a single user
deleteUser(User list) {
_userList.remove(list);
notifyListeners();
}
//adding a new user
addUser(User list) {
_userList.add(list);
notifyListeners();
}
//updating the specific user
updateUser(User user) {
_userList [_userList.indexWhere((element) => element.id == user.id)] = user;
notifyListeners();
}
These all work fine (at least I think they work when I tested them :D) when it's a list of users, but how can I define these methods when it is a single object/item (single User) and not a list? The .add(), remove(), are methods that are available when there is a list, but not when there is a single item. What is the best approach for these CRUD model methods? The 'Read' is similar when it is a list:
User get user => _user;
//method for getting the user data
setUser(User user) {
_user = user;
notifyListeners();
}
but how I define the rest of the CRUD model like create(add), update and delete for a single model and not a list?

There is really not much difference when you are managing a list or a single item - just you will have methods that work on the single item. You do not show it above, but you should wrap your methods in a class (a "service") that maintains the data.
Here is an example authentication service that creates and deletes a User:
class AuthService with ChangeNotifier {
User _user;
User get user => _user;
Future<void> _authenticate(String email, String password,
[String name]) async {
// This is where you authenticate or register the user, and update the state
_user = User("dummy");
return Future<void>(() {});
}
Future<void> register(String name, String email, String password) async {
return _authenticate(email, password, name);
}
Future<void> login(String email, String password) async {
return _authenticate(email, password);
}
Future<void> logout() async {
_user = null;
notifyListeners();
return Future<void>(() {});
}
}
If it is not clear please ask in the comments.

Related

show display name after signUp

I have a flutter firebase createUserWithEmailAndPassword function with the displayName update.
display name prints normally at the moment of fb user creation.
After signup MainPage loads with the users email and displayName. But displayName returns null value error. If I delete displayName from the MainPage - all works fine.
If I reload app, it works fine.
When I login, it works fine.
It can't pass the displayName at the moment of signup only.
Where I am wrong?
class AuthServiceProvider extends ChangeNotifier {
final auth.FirebaseAuth _firebaseAuth = auth.FirebaseAuth.instance;
final googleSingIn = GoogleSignIn();
UserModel? _userFromFirebase(auth.User? user) {
if (user == null) {
return null;
}
return UserModel(
user.displayName,
user.uid,
user.email,
);
}
Stream<UserModel?>? get user {
return _firebaseAuth.authStateChanges().map(_userFromFirebase);
}
Future<UserModel?> createUserWithEmailAndPassword(
String name,
String email,
String password,
) async {
try {
final userCred = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
auth.User? firebaseUser = _firebaseAuth.currentUser;
if (firebaseUser != null) {
await firebaseUser.updateDisplayName(name);
await firebaseUser.reload();
firebaseUser = _firebaseAuth.currentUser;
}
print('FIREBASE USER IS $firebaseUser');
return _userFromFirebase(firebaseUser);
} catch (e) {
print(e.toString());
return null;
}
}
}
If your class were to extend either StatelessWidget or StatefulWidget, then all you'd have to do is to pass the data (displayName) between the screens.
This is not an answer but a suggestion:
You should try changing the ChangeNotifier to a StatefulWidget
and pass the data between screens...
You could also setup an
Authentication class that will hold all these Future methods so that
these calls can be reusable in your code. With this method, all you have to do is to call the specific function and give its required parameters.
As usually the solution is very simple if you think a little bit.
As all this is through the firebase auth, at the main page loading I just grab the firebase user with its display name that is saved in FB both for GoogleSignIn and createUserWithEmailAndPassword (required at registration)
import 'package:firebase_auth/firebase_auth.dart' as auth;
final auth.FirebaseAuth _firebaseAuth = auth.FirebaseAuth.instance;
final String firebaseUser =
_firebaseAuth.currentUser!.displayName ?? 'Unknown user';

Updating State with Provider after API call

How do I update the state with Provider after I've called the API to update it to the BE? I pass the arguments from one screen to another, then after I edit the text, I trigger the API call to the BE and I get the return value of the response and I want to update the state with that response with Provider. How is that possible? Here is the code:
Here I call the API and pass the arguments I've edited in my text fields:
onPressed: () async {
final updatedUser = await await APICall.updateUser(
userID,
updateName,
updateEmail,
);
Provider.of<UserStore>(context, listen: false)
.updateUser(updatedUser);
Navigator.pop(context);
},
Here is the API call where I return the response of the updated User:
Future<User> updateUser(String userID, String name, String email) async {
final response =
await APICalls.apiRequest(Method.PATCH, '/users', this._jsonWebToken,
body: jsonEncode({
"id": userID,
"name": name,
"email": email,
}));
Map<String, dynamic> jsonDecodedResponse = jsonDecode(response.body);
return User(
id: jsonDecodedResponse['data']['id'],
name: jsonDecodedResponse['data']['name'],
email: jsonDecodedResponse['data']['email'],
);
}
Now I wanted to pass that response I've got from the API call to pass it to the providers state:
deleteUser(User list) {
_userList.remove(list);
notifyListeners();
}
addUser(User list) {
_userList.add(list);
notifyListeners();
}
updateUser(User ){//I'm not sure how do define the updateUser method...
notifyListeners();
}
The update works on the BE side, and on the FE only when I refresh the widget, not immediately after the response is returned, which is the way I want it to work.
class UserStore extends ChangeNotifier {
List<User> clientList = [];
void deleteUser(User list) {
clientList.remove(list);
notifyListeners();
}
void addClient(User list) {
clientList.add(list);
notifyListeners();
}
void updateUser(User user){
clientList[clientList.indexWhere((element) => element.id == user.id)] = user;
notifyListeners();
}
}
What you have to do now is to listen to this provider on your widget. When the user will be updated, the changes will be applied to any widget listening to the clientList.
Note I've changed the clientList variable to public, so it can be listened by any widget outside.

Firebase documentation for flutter does not work for deleting user

The following documentation on deleting a user does not work:
try {
await FirebaseAuth.instance.currentUser.delete();
} catch on FirebaseAuthException (e) {
if (e.code == 'requires-recent-login') {
print('The user must reauthenticate before this operation can be executed.');
}
}
"delete()" is not a function recognized by Flutter. "FirebaseAuthException" is also not recognized by Flutter.
How do I delete a user? Where do I find this information?
Using flutter, if you want to delete firebase accounts together with the associated firestore user collection document, the following method works fine. (documents in user collection named by the firebase uid).
Database Class
class DatabaseService {
final String uid;
DatabaseService({this.uid});
final CollectionReference userCollection =
Firestore.instance.collection('users');
Future deleteuser() {
return userCollection.document(uid).delete();
}
}
Use Firebase version 0.15.0 or above otherwise, Firebase reauthenticateWithCredential() method throw an error like { noSuchMethod: was called on null }.
Authentication Class
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future deleteUser(String email, String password) async {
try {
FirebaseUser user = await _auth.currentUser();
AuthCredential credentials =
EmailAuthProvider.getCredential(email: email, password: password);
print(user);
AuthResult result = await user.reauthenticateWithCredential(credentials);
await DatabaseService(uid: result.user.uid)
.deleteuser(); // called from database class
await result.user.delete();
return true;
} catch (e) {
print(e.toString());
return null;
}
}
}
Then use the following code inside the clickable event of a flutter widget tree to achieve the goal:
onTap: () async {
await AuthService().deleteUser(email, password);
}

How do I print a value from an instance of 'User' in a flutter?

class User {
String token;
User({this.token});
}
class AuthService {
final String url = 'https://reqres.in/api/login';
final controller = StreamController<User>();
Future<User> signIn(String email, String password) async {
final response =
await post(url, body: {'email': email, 'password': password});
final data = jsonDecode(response.body);
final user = _userFromDatabaseUser(data);
// print(user.token);
controller.add(user);
return user;
}
//create user obj based on the database user
User _userFromDatabaseUser(Map user) {
return user != null ? User(token: user['token']) : null;
}
//user stream for provider
Stream<User> get user {
return controller.stream;
}
}
//in Sign in page
onPressed: () async {
if (_formKey.currentState.validate()) {
dynamic result = await _auth.signIn(email, password);
print(result); // Instance of 'User'
}
}
I am new to flutter and want to make an app that only authenticated users. I'm trying to read user token data from a stream. then check that token is not null if I got token then goto home page otherwise it will show error how do I print or store token value?
You can do is when you get the user after the sign In:
User result = await _auth.signIn(email, password);
Then to see the data you can do is
print(result.token);
which will give you the token, and then you can use the shared prefrences to store your token and access it.
Check out the docs for the it: https://pub.dev/packages/shared_preferences
You can override Object.toString method.
you can add this method in your User class to print the token instead of Instance of 'User'.
#override
String toString() {
// TODO: change the below return to your desired string
return "token: $token";
}
You can print using
print(userModel.toString());

How do I return to the user stream in flutter

I'm having an issue return a Stream to a StreamBuilder widget in a flutter. I'm trying to access a custom class that is stored token.
class User {
String token;
User({this.token});
}
===============================
class AuthService {
String url = 'https://reqres.in/api/login';
String token = '';
// {
// "email": "eve.holt#reqres.in",
// "password": "cityslicka"
// }
Map data;
Future signIn(String email, String password) async {
final response =
await post(url, body: {'email': email, 'password': password});
data = jsonDecode(response.body);
print(data['token']);
token = data['token'];
_userFromDatabaseUser(data);
return data;
}
//create user obj based on the database user
User _userFromDatabaseUser(Map user) {
return user != null ? User(token: user['token']) : null;
}
//user stream for provider
Stream<User> get user {
return .................. ;
}
You could use a stream controller:
class AuthService {
final String url = 'https://reqres.in/api/login';
final controller = StreamController<User>();
Future<User> signIn(String email, String password) async {
final response = await post(url, body: {'email': email, 'password': password});
final data = jsonDecode(response.body);
final user = _userFromDatabaseUser(data);
controller.add(user);
return user;
}
//create user obj based on the database user
User _userFromDatabaseUser(Map user) {
return user != null ? User(token: user['token']) : null;
}
//user stream for provider
Stream<User> get user {
return controller.stream;
}
Please note that this approach is a simplistic example that has some flaws, you should read up on it in the documentation.
If you use this for the purpose you describe, you may want to look into the bloc pattern and it's implementation as flutter-bloc. It might seem easier to do the user in this way by hand, but once you reach the point where you have multiple of those streams, you may want a more structured approach.
You can use
Stream<User> get user async*{
yield .................. ;
}
you can use yield keyword when you want to return stream object.
2nd way you can use a stream controller. You can add value in controller and
listen wherever you want to listen in your app there is no need to return stream