I have been struggling to find a solution for this and it seems that i'm doing something in the wrong way due to my limited knowladge, so here is the breakdown of the problem:
public void RegisterNewUser()
{
FetchRegisterInputValues();
if (CheckRegisterDataIntegrity())
{
_auth.CreateUserWithEmailAndPasswordAsync(_email, _password).ContinueWith(task => {
if (task.IsCanceled) {
Debug.LogError("CreateUserWithEmailAndPasswordAsync was canceled.");
return;
}
if (task.IsFaulted)
{
HandleRegistrationErrors(task.Exception);
return;
}
// Firebase user has been created.
Firebase.Auth.FirebaseUser newUser = task.Result;
Debug.LogFormat("Firebase user created successfully: {0} ({1})",
newUser.DisplayName, newUser.UserId);
});
}
else if (!CheckRegisterDataIntegrity())
{
HandleRegistrationErrors(new AggregateException("passwords do not match"));
}
}
above is the Registration function that I got straight from Firebase docs, it's very straightforward
the FetchRegisterInputValues(); function gets the email and passwords, the CheckRegisterDataIntegrity() compares the password with the password conformation in the form, and finally HandleRegistrationErrors(task.Exception); is meant to fire a popup panel to show the error,
this is how HandleRegistrationErrors(task.Exception); looks
private void HandleRegistrationErrors(AggregateException errMsg)
{
print("its here from the errors method " + errMsg.Message);
registerErrorPopup.OpenNotification();
registerErrorPopup.description = errMsg.Message;
}
it's using a UI asset from the asset store, the .OpenNotification(); starts the animation and pops it up, and then im just showing the message.
Now, I got two problems, the first is when there is an error encountered by Firebase and the if (task.IsFaulted) Condition is true, the HandleRegistrationErrors function should be called, right?. well that's exactly what happens, except only the print("it's here from the errors method " + errMsg.Message); line gets called and the rest of the function does not execute, I thought at first that its a problem with asset, but I tried doing it manually (created a native UI with unity and used SetActive() method to start the popUp), but again only print method executed, I think its because of the
CreateUserWithEmailAndPasswordAsync is Asynchronous and I should handle errors accordingly, but I really don't know how to go about it and there is no documentation that I could find.
The second problem is how to get the correct Error Message because of the task.Exception.Message always returns me a "One or more errors occurred". while the task.Exception itself gives the right message but it's not formatted correctly.
The first question is the easiest. To update your code with the minimal amount of effort, just replace ContinueWith with ContinueWithOnMainThread will force logic onto the main thread. Also, you should avoid calling task.Result if task.Exception is non-null as it will just raise the exception (see the related documentation).
For the threading related stuff: I go into much more detail about threading with Firebase and Unity here and you can read about the ContinueWithOnMainThread extension here.
For your second issue, the issue you're running into is that task.Exception is an AggregateException. I typically just attach a debugger and inspect this when debugging (or let Crashlytics analyze it in the field), and my UI state is only concerned about success or failure. If you want to inspect the error, the documentation I linked for AggregateException recommends doing something like:
task.Exception.Handle((e) => Debug.LogError($"Failed because {e}"));
Although I would play with .Flatten() or .GetBaseException() to see if those are easier to deal with.
I hope this helps!
--Patrick
Related
My issue was, that with the default GetOptions (omitting the parameter), a request like the following could load seconds if not minutes if the client is offline:
await docRef.get()...
If I check if the client is offline and in this case purposefully change the Source to Source.cache, I have performance that is at least as good, if not better, than if the client was online.
Source _source = Source.serverAndCache;
try {
final result = await InternetAddress.lookup('example.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
_source = Source.serverAndCache;
}
} on SocketException catch (_) {
_source = Source.cache;
}
and then use this variable in the following way:
docRef.get(GetOptions(source: _source))
.then(...
This code works perfectly for me now, but I am not sure, if there are any cases in which using the code like this could raise issues.
Also it seems like a lot of boilerplate code (I refactored it into a function so I can use it in any Database methods but still...)
If there are no issues with this, why wouldn't this be the Firebase default, since after trying the server for an unpredictably long time it switches to cache anyways.
I'm trying to get all files from firebase's storage through listAll.
By the way..
storageReference.listAll().addOnSuccessListener { listResult ->
val image_task : FileDownloadTask
for (fileRef in listResult.items) {
fileRef.downloadUrl.addOnSuccessListener { Uri ->
image_list.add(Uri.toString())
println("size1 : " + image_list.size)
}
}
println("size2 : " + image_list.size)
}//addOnSuccessListener
enter image description here
Why is the execution order like this?
How do I solve it??
When you add a listener or callback to something, the code inside the listener will not be called until sometime later. Everything else in the current function will happen first.
You are adding listeners for each item using your for loop. No code in the listeners is running yet. Then your "size2" println call is made after the for loop. At some later time, all your listeners will fire.
If you want asynchronous code like this to be written sequentially, then you need to use coroutines. That's a huge topic, but your code would look something like this (but probably a little more involved than this if you want to properly handle errors). I'm using lifecycleScope from an Android Activity or Fragment for this example. If you're not on Android, you need to use some other CoroutineScope.
The calls to await() are an alternative to adding success and failure listeners. await() suspends the coroutine and then returns a result or throws an exception on failure.
lifecycleScope.launch {
val results = try {
storageReference.listAll().await()
} catch (e: Exception) {
println("Failed to get list: ${e.message}")
return#launch
}
val uris = try {
results.map { it.downloadUrl.await().toString() }
} catch (e: Exception) {
println("Failed to get at least one URI: ${e.message}")
return#launch
}
image_list.addAll(uris)
}
There is nothing wrong with the execution order here.
fileRef.downloadUrl.addOnSuccessListener { Uri ->
the downloadUrl is an asynchronous action which means it doesn't wait for the action to actually complete in order to move along with the code.
You receive the result with the success listener (at least in this case)
If you want to deal with it in a sequential way, look at coroutines.
I have an observer that tracks questions and answers of a command line interface. What I would like to do is inject an error into the observer given a certain event in my code in order to terminate the observer and its subscription downstream. It is unknown at what time it runs.
I've tried throwing errors from a merge of a subject and the observable but I cannot seem to get anything out of it.
Here is the relevant code:
this.errorInjector$ = new Subject<[discord.Message, MessageWrapper.Response]>();
....
this.nextQa$ = merge(
nextQa$,
this.errorInjector$.pipe(
tap((): void => {
throw (new Error('Stop Conversation called'));
}),
),
);
// start conversation
Utils.logger.trace(`Starting a new conversation id '${this.uuid}' with '${this.opMessage.author.username}'`);
}
getNextQa$(): Observable<[discord.Message, MessageWrapper.Response]> {
return this.nextQa$;
}
stopConversation(): void {
this.errorInjector$.next(
null as any
);
}
The this.nextQa$ is merged with the local nextQa$ and the errorInjector$. I can confirm that stop conversation is being called and downstream is receiving this.nextQa$ but I am not seeing any error propagate downstream when I try to inject the error. I have also tried the this.errorInjector.error() method and the map() operator instead of tap(). For whatever reason I cannot get the two streams to merge and to throw my error. To note: this.nextQa$ does propagate errors downstream.
I feel like I am missing something about how merge or subjects work so any help or explanation would be appreciated.
EDIT
Well I just figured out I need a BehaviorSubject instead of a regular subject. I guess my question now is why do I need a BehaviorSubject instead of a regular Subject just to throw an error?
EDIT 2
BehaviorSubject ALWAYS throws this error which is not what I want. It's due to the nature of its initial emission but I still don't understand why I can't do anything with a regular subject in this code.
First of all if you want subject to work you will have to subscribe before the error anyting is emitted. So there is a subscription sequence problem within your code. If you subscribe immediately after this.nextQa$ is created you shouldn't miss the error.
this.nextQa$ = merge(
nextQa$,
this.errorInjector$.pipe(
tap((): void => {
throw (new Error('Stop Conversation called'));
}),
),
);
this.nextQa$.subscribe(console.log,console.error)
The problem is getting the object with the stopConversation(): void from the dictionary object I have. The this object is defined and shows errorInjector$ is defined but the debugger tells me that errorInjector$ has become undefined when I hover over the value. At least that's the problem and I'll probably need to ask another question on that.
For one of my models, I have a simple ondelete event handler:
function validateStateDeletion(record){
if (record.Name===STATE_SUBMITTED || record.Name===STATE_CLOSED){
throw 'Cannot delete internal states '+STATE_SUBMITTED+' and '+STATE_CLOSED;
}
This does indeed work and prevents records meeting the condition from being deleted. I see the error is propagated back to the client (it is displayed in the dev console as an exception). However, capturing the exception to display something to the user, using window.onerror as part of the app initialization script, does not seem to have any effect (This may not be the correct Window object as window.onerror is undefined in the dev console, it may be some sandbox iframe where client side scripts are executed) .
window.onerror=function(message, url, line, column, error){
window.toastr.error("Error:" +(message||error));
return false;
};
Question: Any insight on global exception handling in AppMaker, or an alternative way to display server side validation errors?
>> global exception handling in AppMaker
afaik there is no such mechanism right now
>> or an alternative way to display server side validation errors?
Here we have at least 3 cases
1 Calling the server-side function
google.script.run
.withSuccessHandler(function(result) {
// TODO
})
.withFailureHandler(function(e) {
// TODO
})
.MyServerSideFunction();
2 Triggering any data-related action(createItem, saveChanges, deleteItem, load, reload... etc)
widget.datasource.createItem({
success: function (somethingThatDependsOnActionType) {
// TODO
},
failure: function (e) {
// TODO
}
});
3 Making a change to an item for a datasource in auto save mode
app.datasources.Employees.item.Name = 'Bob';
Afaik there is no good way to handle error in this case. Hope it will be fixed soon. For the time being as workaround you can switch datasource to manual save mode and pass success+failure handler to the saveChanges callback
I'm doing a simple insert into a meteor collection that appears work, but leaves the collection empty.
The collection is defined properly on the server:
Meteor.publish("comments", function () {
return Comments.find();
});
Subscribed to properly in the client.js:
Meteor.subscribe("commments");
And set up properly on the model.js:
Comments = new Meteor.Collection("comments");
The insert code is as follows:
Meteor.methods({
addComment: function (options) {
check(options.post_id, String);
check(options.comment, NonEmptyString);
if (! this.userId)
throw new Meteor.Error(403, "You must be logged in to comment.");
if (options.comment.length > 1000)
throw new Meteor.Error(413, "Comment is too long");
var post = Posts.findOne(options.post_id);
if (! post)
throw new Meteor.Error(404, "No such post");
// add new comment
var timestamp = (new Date()).getTime();
console.log('Comment: ' + options.comment);
console.log('Post: ' + options.post_id);
console.log('UserId: ' + this.userId);
var saved = Comments.insert({
owner: this.userId,
post_id: options.post_id,
timestamp: timestamp,
text: options.comment});
console.log('Saved: ' + saved);
}
});
Once the insert is called, the console prints out the following:
Comment: Something
Post: xRjqaBBEMa6qjGnDm
UserId: SCz9e6zrpcQrKXYWX
Saved: FCxww9GsrDsjFQAGF
> Comments.find().count()
0
I have inserts into several other collections that work just fine (Posts being one of them as you can see the post ID in the code). In the docs ist said that if the insert errors out it will print to the console, but as you can see it appears to be working, yet is actually empty.
Thanks.
UPDATE: I did find that the data is being put into the database, but for some reason is not showing up. I'm not sure why the data is not being published properly since there are no filters on the find().
I'm not sure exactly what's wrong, but there's a few things to check here.
• First, this:
Meteor.publish("comments", function () {
return Comments.find();
});
directs the server to publish the Collection, but doesn't actually establish the collection server side.
You should have Comments = new Meteor.Collection("comments"); available on both the client and the server. I tend to put in a file called model.js like the examples tend to do.
• Second possibility, you don't have a subscribe function shown above, such as Meteor.subscribe("comments"); If you don't have a subscribe function, your client isn't going to know about it, even though it does exist in the collection.
You can test this theory by typing meteor mongo in the shell (with your Meteor app running), and db.comments.find() to see if your comments are actually in the database but not subscribed to.
Verify you do not have an error in your client code. With Meteor.call, if you do not initialize a variable you can have an error condition that will block reactive updating in your templates but continue to write fine to your console just before hand.
I've made that mistake which I talk about here:
http://doctormehmet.blogspot.com/2013/07/revoltdc-hackathon-20130622-iteration-3.html
Specifically I had something like
Template.mytemplate.helpers({
somevar: function({
if (some_session_var_set_by_a_call.party){
//do something
}
}
Now the somevar function gets called on render, before the Meteor.call returns. Therefore the variable some_session_var_set_by_a_call isn't set yet. The whole thing stops client side on the undefined error.