Hybrid Apps: Your own mini-Cordova
Ayman MahfouzVP of Engineering @ Webalo
November 2016
Agenda
- Problem- Hybrid apps- Android
- Java to JavaScript- JavaScript to Java
- iOS- Objective-C to JavaScript- JavaScript to Objective-C
- Talking Angular- Summary
Problem Context - Webalo Platform
Problem Context - Webalo App
Shared Java code allows for extensibility, but how to open the Webalo Client up for third party extension?
Solution - Hybrid App
HTML + JavaScript
Java / Obj-C
WebView
Android App
● Java / Objective-C: Proprietary code.● JavaScript: Third-party code.
AndroidJava to JavaScript
Java to JavaScript
API
void evaluateJavascript(String script, ValueCallback<String> resultCallback)
webView.evaluateJavascript(“someJavaScriptFunction();”, new ValueCallback<String>() {
public void onReceiveValue(String s) { JsonReader reader = new JsonReader(new StringReader(s));
JsonToken token = JsonReader.peek() ... }});
Usage
Java to JavaScript
Requirements
1. API level 19.2. WebSettings.setJavaScriptEnabled(true) // false by default3. Must be called on UI thread.
evaluateJavascript(...)
Properties
1. Asynchronous evaluation.2. Context of currently displayed page.3. Callback made on UI thread.4. Callback value is a JSON object. To pass String values use
JsonReader.setLenient(true).
Java to JavaScript - Pre 19
void loadUrl(String url)
Usage
webView.loadUrl(“javascript:someJsFunction();”);
API
Pitfall - Navigate away
webView.loadUrl(“javascript:someJsFunction();void(0);”);
No return value!
AndroidJavaScript to Java
JavaScript to Java
Inject objects into the displayed page context:
private final class Api {
public void showMessage(String message) { Log.d(TAG, message); }}webView.addJavascriptInterface(new Api(), "MyJavaObj");
Starting API 17
void addJavascriptInterface(Object serviceApi, String name)
Usage
API
MyJavaObj.showMessage(“Hello There!”);
Java
JS
@JavascriptInterface
JavaScript to Java - Notes
1. Injected objects will not appear in JavaScript until the page is next (re)loaded.
webView.loadData("", "text/html", null);
2. WebView interacts with Java object on a background thread. 3. Serialization of arguments
a. Simple types and stringsb. Serialize objects as JSON
4. Use WebChromeClient to handle callbacks
webView.setWebChromeClient(new WebChromeClient() { public boolean onJsAlert(...) { // display alert in OS theme } }); }
Troubleshooting
Use Chrome “Inspect” feature
chrome://inspectMust enable debugging
WebView.setWebContentsDebuggingEnabled(true);
[INFO:CONSOLE(1)] "Uncaught SyntaxError: Unexpected token ILLEGAL", source: (1)[INFO:CONSOLE(13)] "Uncaught ReferenceError: MyJavaObj is not defined"android.view.ViewRootImpl$CalledFromWrongThreadException
1. Run calls from JavaScript on UI Thread. 2. Wait till page loads WebViewClient.onPageFinished()3. Force page load using
webView.loadData("", "text/html", null);
Debugging
Common Causes
Common Errors
iOSObjective-C to JavaScript
Objective-C to JavaScript
WKWebView API (iOS 8+)
- (void)evaluateJavaScript : (NSString *)javaScriptString completionHandler : (void (^)(id, NSError *error))completionHandler;
[webView evaluateJavaScript : script completionHandler:^(id result, NSError *error) { if (error == nil) { if (result != nil) { NSString* resultString = [NSString stringWithFormat:@"%@", result]; ... } } else { NSLog(@"evaluateJavaScript error : %@", error.localizedDescription); } }];
Usage
Objective-C to JavaScript - Pre iOS 8
UIWebView API
- (NSString *)stringByEvaluatingJavaScriptFromString : (NSString *)script;
NSString *title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
NSString *ten = [webView stringByEvaluatingJavaScriptFromString:@"var x = 2; x * 5;"];
Usage
Objective-C to JavaScript - Notes
- (void) viewDidLoad {
// Send page load request to Web View
}
- (void) webViewDidFinishLoad : (UIWebView *)webView {
// Ask Web View to evaluate JavaScript
}
iOSJavaScript to Objective-C
JavaScript to Objective-C
Inject an object into WK
@interface MyHandler : NSObject<WKScriptMessageHandler> { … }
- (void)userContentController : (WKUserContentController *)userContentController didReceiveScriptMessage: (WKScriptMessage *)message { NSDictionary *dict = (NSDictionary*)message.body; NSString *str = [dict objectForKey:@"strField"]; NSNumber *num = [dict objectForKey:@"numField"]; ...}
[webView.configuration.userContentController addScriptMessageHandler : myHandlerObject
name : @"handlerNameInJs"];
Usage
API
window.webkit.messageHandlers.handlerNameInJs.postMessage( { ‘strField’ : “Some string value”, ‘numField’ : 3 } );
Obj
ectiv
e-C
JS
JavaScript to Objective-C - Pre iOS 8
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest : (NSURLRequest *)request navigationType : (UIWebViewNavigationType)navigationType {
NSURL* url = request.URL;
if ( ! [url.scheme isEqualToString:@"myapp"]) { return YES; }
// decode the invocation
NSString* methodName = [hostStrEncoded stringByRemovingPercentEncoding]; NSString* queryStr = [[url query] stringByRemovingPercentEncoding]; ... return NO;}
Usage - UIWebViewDelegate
Point the browser to the function you want to invoke!API
document.location.href = “myapp://methodName?param1=test¶m2=3
Obj
ectiv
e-C
JS
The Angular bit
Angular to Native
Wrap an Angular service around the injected Object.
angular.module('MyModule').service('WrapperService', function() { this.showMessage = function(message) { MyInjectedObj.showMessage(message); };
this.someOtherMethod = function() { MyInjectedObj.someOtherMethod(); };});
Java to Angular
// Get the element for the angular applicationvar app = angular.element('[ng-app]');
// Get the injector from the application var injector = app.injector();
// Get the service to be calledvar myService = injector.get("MyService");
// Invoke the servicemyService.someFunction();
What is the entry point from plain JavaScript to Angular?
Appreciate what PhoneGap does for you!
Summary
● Hybrid Apps● Conversing with the Web View● Extensions
○ Other platforms○ Callbacks to JavaScript○ Code generation
linkedin.com/in/amahfouz
aymanmahfouz.blogspot.com
slideshare.net/AymanMahfouz
github.com/amahfouz
Questions