Reputation: 392
I'm trying to interact with a JS api, once a video is loaded/fails to load/is viewed I'd like to send back an event using EventSystem. I got the first part working, where I call the native JS apis:
@JsType(namespace = JsPackage.GLOBAL, name = "ApiRequestHandler", isNative = true)
public class ApiRequestHandler {
public native void loadVideo();
public native void showVideo();
}
ApiRequestHandler= function () {
var preloadedVideo = null;
}
ApiRequestHandler.prototype.loadVideo = function () {
Api.getVideoAsync().then(function(video) {
// Load asynchronously
preloadedVideo = video;
return preloadedVideo.loadAsync();
}).then(function() {
console.log('Video preloaded');
}).catch(function(err){
console.error('Video failed to preload: ' + err.message);
});
}
ApiRequestHandler.prototype.showVideo = function () {
preloadedVideo.showAsync()
.then(function() {
// Perform post-ad success operation
console.log('Video watched successfully');
})
.catch(function(e) {
console.error(e.message);
});
}
My problem is with calling Java objects from JS, I came with a response handler class, which has a dependency on EventSystem. How can I initialize the EventSystem in this class, but then let JS promise resolution call sendEvent
? (to make this clear, the above JS code console calls, need to instead call the eventSystem, to report back success or error).
@JsType(isNative = true)
public class ApiResponseHandler {
EventSystem eventSystem;
public void sendEvent(int msg) {
final Event event = new Event();
event.message = msg;
eventSystem.dispatch(event);
}
@JsOverlay
public void setEventSystem(EventSystem eventSystem) {
this.eventSystem = eventSystem;
}
}
Upvotes: 0
Views: 282
Reputation: 18331
While your answer should work properly (though I think your usage of Function.call(..)
is incorrect, please see the comment I added), the other option is to just model the Api
type and its returned video. Going only by the information in your question and answer, it looks like the api is something like this:
A class with a single static method, getVideoAsync()
, which returns a promise of a Video
.
This type is never actually named, so I'm guessing about the name. As such I've defined it as an interface for Java's purposes, so that no type check happens in JS. This type appears to support two methods that we're interested in, loadAsync()
which returns a Promise of some kind, and showAsync()
, which also returns a Promise of some. For both of these, I'll model it as holding some Object, just in case you need to clarify it better later.
We model these with jsinterop to let us make Java calls that look more or less like what your JS might be. We will use elemental2-promise
to be able to perform async Promise operations in Java. This code assumes that there is no extra setup required to access the Api
object (which from Java's perspective is probably a class with a single static method) from the global scope.
@JsType(namespace = JsPackage.GLOBAL, isNative = true)
public class Api {
public native static Promise<Video> getVideoAsync();
}
@JsType(namespace = JsPackage.GLOBAL, isNative = true)
public interface Video {
public native Promise<Object> loadAsync();
public native Promise<Object> showAsync();
}
With these types defined in your Java code, there is no need for the extra "glue" js type ApiRequestHandler
with its jsinterop wrapper - though you certainly could write such a Java wrapper over the above types, but implement it purely in Java rather than switching to JS for the details. Instead though, I've modeled just ApiRequestHandlerWrapper
, but I think you'll see that you could write this in other ways, including directly exposing promises instead of events to the rest of your application.
(Quick side note: Promises and Events have very different semantics - a promise will only resolve or reject exactly once, rather than possibly go off multiple times, so in some cases it can be much neater to keep track of the promise itself instead of subscribing to events. That said, if you only need to know that the thing happened, you might well not care about the specifics or lifecycle of a given promise.)
public class ApiRequestHandlerWrapper {
// Same event system, you can implement that as you see fit
private EventSystem eventSystem;
// Instead of holding the stateful ApiRequestHandler, we'll
// just track the state it would have tracked - the current
// video instance, if any
private Video preloadedVideo;
public void loadVideo() {
Api.getVideoAsync().then(video -> {
// NOTE: assigning before load, as the other code did, is
// this a bug? Perhaps assign in the next then().
preloadedVideo = video;
// Returning a promise from a then() means we wait for it
// automatically.
return video.loadAsync();
}).then(success -> {
//Handle event creation, then dispatch it
eventSystem.dispatch(event);
}, fail -> {
//Handle event creation, then dispatch it
eventSystem.dispatch(event);
//logging errors
});
}
@Override
public void showVideo() {
if (preloadedVideo == null) {
throw new IllegalStateException("No preloaded video");
}
preloadedVideo.showAsync().then(success -> {
//Handle event creation, then dispatch it
eventSystem.dispatch(event);
}, error -> {
//Handle event creation, then dispatch it
eventSystem.dispatch(event);
//logging errors
});
}
}
Upvotes: 1
Reputation: 392
I came with a solution, but I'm not sure if it is optimal, so I'll leave the question if someone has a better one.
First, an interface with a single method, which serves as a callback function in JS 'world'
@JsFunction
public interface ResponseCallback {
void call();
}
I actually included another interface for fail callbacks, the only difference is the method takes String of the error: void call(String error)
Then the ApiRequestHandler changes to:
@JsType(namespace = JsPackage.GLOBAL, name = "ApiRequestHandler", isNative = true)
public class ApiRequestHandler {
public native void loadVideo(ResponseCallback success, FailCallback fail);
public native void showVideo(ResponseCallback success, FailCallback fail);
}
I have a wrapper class for this object, that simply calls the native JS functions, but also provides implementation for callbacks (in form of a lambda) and has a reference to EventSystem, so I can send my events from the callback:
public class ApiRequestHandlerWrapper {
private EventSystem eventSystem;
private ApiRequestHandler apiRequestHandler = new ApiRequestHandler();
@Override
public void loadVideo() {
apiRequestHandler.loadVideo(() -> {
//Handle event creation, then dispatch it
eventSystem.dispatch(event);
}, error -> {
//Handle event creation, then dispatch it
eventSystem.dispatch(event);
//logging errors
});
}
@Override
public void showVideo() {
apiRequestHandler.showVideo(() -> {
//Handle event creation, then dispatch it
eventSystem.dispatch(event);
}, error -> {
//Handle event creation, then dispatch it
eventSystem.dispatch(event);
//logging errors
});
}
}
Finally, the changes to JS code:
ApiRequestHandler = function () {
var preloadedVideo = null;
}
ApiRequestHandler.prototype.loadVideo = function (success, fail) {
Api.getVideoAsync().then(function(video) {
// Load asynchronously
preloadedVideo = video;
return preloadedVideo.loadAsync();
}).then(function() {
success.call();
}).catch(function(err){
fail.call(err.message);
});
}
ApiRequestHandler.prototype.showVideo = function (success, fail) {
preloadedVideo.showAsync()
.then(function() {
success.call();
})
.catch(function(e) {
fail.call(err.message);
});
}
Upvotes: 0