There is a lot of discussion going on about Google not providing a Java (and other) API to access their GAE channels, which can currently only be accessed from JavaScript, as described here:
https://cloud.google.com/appengine/docs/java/channel/ https://cloud.google.com/appengine/docs/java/channel/javascript
Essentially, there are 2 possible approaches:
- Write Java classes which "simulate" the access, as done here: https://github.com/gvsumasl/jacc
- Use a hidden WebView and connect JavaScript to Java, as described here: http://developer.android.com/guide/webapps/webview.html
I tried option 1 and had this working (so I assume my server code is fine), but I rejected it because there is no guarantee that Google won't change their undocumented API in the future.
I went ahead and implemented the following code:
A Java class, which created the WebView and consumes JavaScript events:
public class ChannelService {
class ChannelListener {
@JavascriptInterface public void onOpen() {
System.out.println("open"); // PROBLEM: these lines are never called
}
@JavascriptInterface public void onMessage(String message) {
System.out.println("message");
}
@JavascriptInterface public void onError(Integer errorCode, String description) {
System.out.println("error");
}
@JavascriptInterface public void onClose() {
System.out.println("close");
}
}
public ChannelService(Context context) throws IOException {
final WebView webView = new WebView(context);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new ChannelListener(), "channelListener"); // the connection between Java and JavaScript
webView.setWebViewClient(new WebViewClient() {
@Override public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url); // this line is called after the page loads
}
@Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
}
});
String html = AssetsHelper.assetToString(context, "channels.html"); // loads the html file below
html = html.replaceAll("\\{\\{ channelurl \\}\\}", ApiService.channelUrl()); // channelurl and token are replaced fine and should be correct
html = html.replaceAll("\\{\\{ token \\}\\}", Session.getToken().getId());
webView.loadData(html, "text/html", null);
}
}
The channels.html file with JavaScript code:
<html>
<head>
<script src='{{ channelurl }}jsapi'></script>
</head>
<body>
<script type='text/javascript'>
onOpen = function() {
channelListener.onOpen();
}
onMessage = function(message) {
channelListener.onMessage(message);
}
onError = function(errorcode, description) {
channelListener.onError(errorcode, description);
}
onClose = function() {
channelListener.onClose();
}
openChannel = function() {
var token = '{{ token }}';
var channel = new goog.appengine.Channel(token);
var handler = {
'onopen': onOpen,
'onmessage': onMessage,
'onerror': onError,
'onclose': onClose
};
var socket = channel.open(handler);
socket.onopen = onOpen;
socket.onmessage = onMessage;
socket.onerror = onError;
socket.onclose = onClose;
}
</script>
</body>
</html>
Thanks to Google, the WebView does not report any errors with my code, which makes it very hard to debug. Thanks to myself I never actively wrote JavaScript code, so I am quite stupid here and you may laugh and spot my mistake immediately.
I have documented this in the code already, but let me state another time what's working and what not:
- 95% sure that the server-side code works, as I had option 1 working. So I am almost certain that my messages are sent to the client.
- I am pre-creating channels when the client signs on and pass the token to the client over REST endpoints. It gets there intact (compared it), and it is also embedded fine after replaceAll.
- My website seems to load as I receive onPageFinished() with the correct url. (FIXED: I actually receive that event several times, with something that looks like params, but I guess that's Android and normal.)
- Then nothing happens, no exception and the Java events aren't called.
- My Java code continues, and when I set a breakpoint the WebView can be retrieved and is alive I used a public variable in my Activity for it, which should be fine; and I made sure it's on the UI thread.
Any ideas welcome, either what I did wrong or how I can possibly debug this. Thanks!