I've been trying to figure out transactions all morning and I'm stuck. I keep going around in circles, and I know what I need to do(I think), but I'm not sure how to do it.
Here's my code:
Future<String> obtainUniqueUsername(
String requestedUsername,
String currentUserID,
) async {
var userID = currentUserID;
var userName = requestedUsername.toLowerCase();
try {
// Create a reference to a usernames collection .doc(id)
final userRef = FirebaseFirestore.instance.doc('usernames/$userName');
// Start a transaction to check if the username is assigned
// if assigned a .doc(userName) will exist.
FirebaseFirestore.instance.runTransaction((transaction) async {
var userSnapshot = await transaction.get(userRef);
if (userSnapshot.exists) {
// Username is assigned as .doc(userName) exists
return 'Username is already assigned';
} else {
// Assign username by created .doc(userName)
// insert document reference to user
transaction.set(userRef, {'uid': userID});
}
}).then((value) => 'Username assigned', onError: (e) => 'Transaction error ${e.toString()}');
} // endtry
catch (e) {
return e.toString();
} // endcatch
return 'Transaction has failed, please try again later';
} // end
My problem is that it keeps hitting the final return statement, even if it has created the document. My understanding is that the transaction will keep trying until it is successful and returns a value, or it times out and throws an error. I've read that using .then doesn't await a value, and the function continues uninterrupted until it hits the end, but shouldn't it be either a value or an error?
I feel like I'm missing the point somewhere, sorry if this is super basic, I've really been trying to get it to work.
I got it sorted out! It took forever though, lots of trial and error. I think my issue was that I didn't understand how to return the value from the future using the dart shorthand from the examples I was following.
My understanding now is that I'm awaiting usernameAssigned to complete, and the future is completed when I return 'success'.
If the transaction didn't go through, it would throw an exception, and be caught and I would get a message about what went wrong. I think the assignment of transactionStatus isn't required, but I kind of like having it there for my own understanding of what's happening. Maybe when I get more experience I wont need stuff like that.
I also didn't really know how to use a try/catch block, but I think I've improved how I do that as well. Now, I throw an exception if something weird happens instead of going right to return. I think that's better?
Also, the comment about letting firestore assign the unique uid: Yeah, you are right, but I want to have firestore enforce unique human readable usernames. The autogenerated uid is used for everything else.
The usecase is having a usernames collection built like: .doc(uniqueUsername).data({"uid:" uid})
Firestore won't let duplicate doc ID's happen in that collection, so it gives me what I want. My understanding is this is one of the "easier" ways to enforce unique usernames.
Anyways, thanks for the comments and please send me any feedback you may have!
Future<String> obtainUniqueUsername(
String requestedUsername,
String currentUserID,
) async {
Future<String> obtainUniqueMarkerUsername(
String requestedUsername,
String currentUserID,
) async {
var userID = currentUserID;
var userName = requestedUsername.toLowerCase();
var transactionStatus = '';
try {
// Start a transaction to check if the username is assigned
// if assigned a .doc(userName) will exist.
var usernameAssigned = await FirebaseFirestore.instance
.runTransaction((transaction) async {
// Create a reference to a usernames collection .doc(id)
var userRef = FirebaseFirestore.instance.doc('usernames/$userName');
var userSnapshot = await transaction.get(userRef);
if (userSnapshot.exists == true) {
// Username is assigned as .doc(userName) exists
throw Exception('Username already exists');
} else {
// Assign username by created .doc(userName)
// insert document reference to user
transaction.set(userRef, {"uid": userID});
return 'Success';
}
});
transactionStatus = usernameAssigned;
} // endtry
catch (e) {
return e.toString();
} // endcatch
userID = '';
userName = '';
return transactionStatus;
} // end
Related
how I can show DeliveryBoys in a specific location, in my realtime database I have a value that I need to compare drivers with which is "City" I would like to have all DeliveryBoys that are in a specific city. How can I do that? Using flutter
Am only able to get all drivers without a conditional statement
**This is my Function that i want to modify **
retrieveOnlineDriversInformation(List onlineNearestDriversList) async {
DatabaseReference ref =
FirebaseDatabase.instance.ref().child("DeliveryBoys");
for (int i = 0; i < onlineNearestDriversList.length; i++) {
await ref
.child(onlineNearestDriversList[i].driverId.toString())
.once()
.then((dataSnapshot) {
var driverKeyInfo = dataSnapshot.snapshot.value;
dList.add(driverKeyInfo);
});
}
}
Database Structure
Based on your responses and as far as I can see, you don't need the loop where you have it. Therefore, I am going to ignore it and simply show you the code that will return the list of driver ids of all drivers for city 'Lusaka'.
Future<List<String>> retrieveOnlineDriversInformation() async {
final driverIds = <String>[];
DatabaseReference ref = FirebaseDatabase.instance.ref().child("drivers");
try {
await ref.orderByChild("city")
.equalTo("Lusaka")
.once()
.then(
(event) {
if (event.snapshot.value != null) {
final driverListData =
Map<String, dynamic>.from(event.snapshot.value! as Map);
driverListData.forEach((key, value) {
driverIds.add(key);
});
}
},
} on FirebaseException catch (error, stackTrace) {
// < Some code here to print database error details or otherwise deal with it >
} catch (error, stackTrace) {
// < Some code here to print other error details or otherwise deal with it >
}
return driverIds;
}
You could instead modify this to just return the Map 'driverListData' which contains each driver's id and associated driver data.
A couple of other points:
You don't stick to a standard naming convention for your database node and field names. I suggest that you always use lowerCamelCase as the standard (so for example, change DriverLicense to driverLicense) as it will match what you typically name the variables within the Flutter/Dart code.
You don't need to hold the driver id as a separate field in the driver node. It is a duplicate (and therefore wastes space on the database) of the driver record key, which is already accessible to you.
As you see, you should always wrap your database call logic in a try / catch clauses in order to handle any errors that the call to the database may return. There are specific exceptions that can be tested for with the on clause.
I am trying to create a REST API. I am pretty new to the back end and am just practicing on my own for the time being. For my code, I know it's bad practice to store the plain text password but again, this is completely for practice and will never go live. I will also add encryption at a later point for practice.
My issue is I am not sure why my API does not work. I see where it fails, it fails in the catch block when I try to save a user but I do not get any error to tell me what is wrong exactly, besides the once I force. I have another part on this website that follows almost the exact same logic and it works perfectly but for this one it does not. I have no idea how to solve my issue but after googling I still cannot figure it out. It looks perfectly fine too me, but as mentioned I am pretty new to the backend.
This is my controller function:
const signup = async (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return next(new HttpError('Invalid inputs passed, please check your data', 422));
}
const { name, email, password, places } = req.body;
let existingUser;
try {
existingUser = await User.findOne({email: email}) // finds one document matching our criteria we set
} catch(err) {
const error = new HttpError('Signing up failed, please try again later', 500);
return next(error);
}
if (existingUser) {
const error = new HttpError('User exists already, please login instead', 422);
return next(error);
}
const createdUser = new User({
name,
email,
image: 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260',
password,
places
});
try {
await createdUser.save();
} catch(err) {
const error = new HttpError(
'Signing up failed', 500
);
return next(error);
}
res.status(201).json({user: createdUser.toObject({ getters:true })});
};
I use Postman to send the request to my API endpoint with all of the correct information. Based on what I recieve back it is failing in the try catch block of await createdUser.save()
For anyone who finds this from google this was my solution:
First I suggest you add this into your save method to try and diagnose the problem
await createdUser.save(function(err){
if(err){
console.log(err);
return;
}
});
This help me greatly as it gave me more information on how to solve it.It turns out my problem was because I misspelled a field in my Schema. So extremely simple solution!
I have a firebase cloud function to create a user document with user data whenever a user registers. How would I return an error when the set() fails? Since this is not an http request (an I don't want to use an http request in this case) I have no response. So how would I catch errors?
export const onUserCreated = functions.region('europe-west1').auth.user().onCreate(async user => {
const privateUserData = {
phoneNumber: user.phoneNumber
}
const publicUserData = {
name: 'Nameless'
}
try
{
await firestore.doc('users').collection('private').doc('data').set(privateUserData);
}catch(error)
{
//What do I put here?
}
try
{
await firestore.doc('users').collection('public').doc('data').set(publicUserData);
}catch(error)
{
//What do I put here?
}
});
You can't "return" an error, since the client doesn't even "know" about this function running, there is nobody to respond to.
You can make a registration collection, and in your function make a document there for the current user (using the uid as the document id). In that document, you can put any information you'd like your user to know (status, errors, etc).
So your clients would have to add a listener to this document to learn about their registration.
In your particular code, I think the error is in doc('users'). I guess you meant doc('users/'+user.uid).
Your catch -block will receive errors that occur on your set -call:
try {
await firestore.doc('users').collection('public').doc('data').set(publicUserData);
} catch (error) {
// here you have the error info.
}
I'm working on an app and I'm relatively new to this scene, but I'm running into some troubles when I'm looking to simply query some data.
I'm calling a function after someone logs in. The login function only sends the necessary information and I want to do a secondary ping to my server to get a little more user information, more than just verifying the login info.
getUser (userid) async{
List<Map<String, dynamic>> user=[] ;
var client = new http.Client();
try {
var req = await client.post(urlPath+'mobileGetUser', body: {'thisUserID': userid'});
var jsonResponse = convert.jsonDecode(req.body);
//print('Here: '+jsonResponse.toString());
var id = jsonResponse['id'] ?? '';
var joinDate = jsonResponse['joinDate'] ?? '';
var userEmail = jsonResponse['userEmail'] ?? '';
var displayName = jsonResponse['displayName'] ?? '';
var timezone = jsonResponse['timezone'] ?? '';
var verified = jsonResponse['verified'] ?? '';
user = [{'id': id}];
user = [{'joinDate': joinDate}];
user = [{'userEmail': userEmail}];
user = [{'displayName': displayName}];
user = [{'timezone': timezone}];
user = [{'verified': verified}];
return user
} finally {
client.close();
}
}
I'm used to working in PHP. In that language, I'd pass the JSON object or associative array back to the calling function and accessing the individual fields would be as simple as
$displayName = $user['displayName'];
But this isn't PHP. List types are a little strange to me still.
From my calling function, I try to test this with:
thisUser = getUser(userid);
print('Successful Login: Display name: '+thisUser.toString());
And I get a message of:
Successful Login: Display name: Instance of 'Future<dynamic>'
How can I access this data? I've tried a few ways to get it. Also, is there a better way to create my List? I'm definitely going through a few steps that feel unnecessary. I'm honestly like to just pass it the entire JSONresponse. I'm just a bit out of my depth.
You could do this:
thisUser = await getUser(userid);
print('after'); // this will print after getUser() finishes
If you don't await for getUser() to finish, then you'll receive a Future.
Another way would be to use that Future and add a listener to it, like this:
getUser(userid).then((value) {
print('after'); // this will print after getUser() finishes
thisUser = value;
});
print('before'); // this will print before getUser() finishes
This is assuming you aren't using this on widgets. Otherwise you could use FutureBuilder to detect when the Future finishes and show something.
I've got a mySql db with non-standard IDs and field names, so I was trying to use both jsonResultsAdapterProvider and setRestangularFields. Here's the code in my app.config file:
RestangularProvider.setBaseUrl(remoteServiceName);
RestangularProvider.setRestangularFields({id: 'personID'});
RestangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
if (data.error) {
return data.error;
}
var extractedData = data.result;
return jsonResultsAdapterProvider.$get().camelizeKeys(extractedData);
});
RestangularProvider.addRequestInterceptor(function(elem, operation, what, url) {
return jsonResultsAdapterProvider.$get().decamelizeKeys(elem);
});
It's all good until I try to do a put/save. When I look at the request payload within the browser dev tools, it's: {"undefined":12842} (but the url is correct, so I know the id is set) If I don't use the ResultsAdapter and change the id field to Person_ID, payload looks good, so I know I'm making the right calls to Get and Save the Restangular objects. But for what it's worth, here's the code:
$scope.tests = Restangular.all('members').getList().$object;
vm.testEdit = function () {
$scope.test = Restangular.one('members', 12842).get().then(function(test) {
var copy = Restangular.copy(test);
copy.title = 'xxxx';
copy.put(); // payload was: undefined: 12842
});
}
// I also tried customPUT...
// copy.customPUT(copy, '', {}, {'Content-Type':'application/x-www-form-urlencoded'});
I tried "fixing" the id other ways too, too. like this:
Restangular.extendModel('members', function(model) {
model.id = model.personID;
return model;
});
but that messed up the urls, causing missing ids. And I tried getIdFromElem, but it only got called for my objects created with Restangular.one(), not with Restangular.all()
Restangular.configuration.getIdFromElem = function(elem) {
console.log('custom getIdFromElem called');
if (elem.route === 'members') { // this was never true
return elem[personID];
}
};
It seems like Restangular needs to substitute 'personID' most of the time, but maybe it needs 'Person_ID' at some point during the Save? Any ideas on what I could try to get the Save working?
I finally figured it out! The problem was in my config code and in the way I was decamelizing. Because of inconsistencies in my db field names (most use underscores, but some are already camelCase), I was storing the server's original elem names in an array within the jsonResultsAdapterProvider. But since I was calling jsonResultsAdapterProvider.$get().camelizeKeys(extractedData); within the interceptors, I was reinstantiating the array each time I made a new request. So, the undefined in the PUT request was coming from my decamelizeKeys() method.
My updated config code fixed the problem:
RestangularProvider.setBaseUrl(remoteServiceName);
RestangularProvider.setRestangularFields({id: 'personID'});
var jsonAdapter = jsonResultsAdapterProvider.$get();
RestangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
if (data.error) {
return data.error;
}
var extractedData = data.result;
// return extractedData;
return jsonAdapter.camelizeKeys(extractedData);
});
RestangularProvider.addRequestInterceptor(function(elem, operation, what, url) {
return jsonAdapter.decamelizeKeys(elem);
});