Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Event Listener return value

Tags:

java

I am using Java8. I have an Listener that calls onSuccess when completed with a customToken.

@Override
public String getCustomToken(Person person) {
    FirebaseAuth.getInstance().createCustomToken(person.getUid()).addOnSuccessListener(new OnSuccessListener<String>() {
        @Override
        public void onSuccess(String customToken) {
            // I would like to return the customToken
        }
    });
    return null;
}

Question

How do I get this method to return the String customToken?

like image 263
Richard Avatar asked Mar 03 '17 12:03

Richard


2 Answers

Your question is intriguing, but the accepted answer unfortunately provides you with wrong means.

The problem with your question is that of API. You are trying to use callbacks in a way they are not designed to be used. A callback, by definition, is supposed to provide a means to do something asynchronously. It is more like a specification of what to do when something happens (in future). Making a synchronous method like getCustomToken() return something that is a result of an inherently asynchronous operation like onSuccess() implies a fundamental disconnect.

While dealing with callbacks, it is critical to understand the importance of continuations: taking actions when certain events of interest happen. Note that these events may not even happen. But you are specifying in the code the actions to take, if and when those events occur. Thus, continuation style is a shift from procedural style.

What adds to the data flow complexity is the syntax of the anonymous inner classes. You tend to think "oh, why can't I just return from here what onSuccess() returns? After all, the code is right here." But imagine that Java had no inner classes (and as you may know, (anonymous) inner class can easily be replaced by a class that is not an inner class). You'd have needed to do something like:

OnSuccessListener listener = new SomeImplementation();
FirebaseAuth.getInstance().createCustomToken(listener);

Now, the code that returned data (String) is gone. You can even visually reason that in this case, there is no way for your method to return a string -- it is simply not there!

So, I encourage you to think of what should happen if and when (in future) onSuccess() is called on the OnSuccessListener instance that you pass in. In other words, think twice if you really want to provide in your API, the getCustomToken() method (that returns a token string, given a Person instance).

If you absolutely must provide such a method, you

  • Should document that the returned token may be null (or something more meaningful like None) and that your clients must try again if they want a valid value.

  • Should provide a listener that updates a thread-safe container of tokens that this method reads.

Googling around, I found the Firebase documentation. This also seems to suggest taking an action on success (in a continuation style):

FirebaseAuth.getInstance().createCustomToken(uid)
    .addOnSuccessListener(new OnSuccessListener<String>() {
        @Override
        public void onSuccess(String customToken) {
            // **Send token back to client**
        }
    });

The other problem with trying to provide such API is the apparent complexity of the code for something trivial. The data flow has become quite complex and difficult to understand.

If blocking is acceptable to you as a solution, then perhaps you can use the Callable-Future style where you pass a Callable and then later do a get() on the Future that may block. But I am not sure if that is a good design choice here.

like image 105
Kedar Mhaswade Avatar answered Oct 06 '22 00:10

Kedar Mhaswade


This would work syntactically:

final List<String> tokenContainer = new ArrayList<>();   
FirebaseAuth.getInstance().createCustomToken(person.getUid()).addOnSuccessListener(new OnSuccessListener<String>() {
    @Override
    public void onSuccess(String customToken) {
        tokenContainer.add(customToken);
    }
});
return tokenContainer.get(0);

As said; this works syntactically. But if it really works would depend if the overall flow is happening in one thread; or multiple ones.

In other words: when the above code is executed in sequence, then that list should contain exactly one entry in the end. But if that callback happens on a different thread, then you would need a more complicated solution. A hackish way could be to prepend

return tokenContainer.get(0);

with

while (tokenContainer.isEmpty()) {
  Thread.sleep(50);
}
return tokenContainer.get(0);

In other words: have the "outer thing" sit and wait for the callback to happen. But the more sane approach would be to instead use a field of the surrounding class.

Edit: if the above is regarded a hack or not; might depend on your context to a certain degree. The only thing that really troubles me with your code is the fact that you are creating a new listener; which gets added "somewhere" ... to stay there?! What I mean is: shouldn't there be code to unregister that listener somewhere?

like image 31
GhostCat Avatar answered Oct 05 '22 23:10

GhostCat