Monday, February 23, 2015

In my previous post, Android WebView: Interacting with the Web Page from Java, I showed you how to call javascript functions from your native Java code. I also briefly mentioned that on Android KitKat and above you have the option of receiving the result directly from the javascript call using the evaluateJavascript method. This is an easy way to handle any result that may have been returned from the javascript call.

The evaluateJavascript method is very helpful but does not handle all use cases in which you may need to execute native Java code initiated by your web page. One use case would be handling web events (like a button click) or for returning data from a method on Android Jelly Bean or below.

One word of warning before we begin, allowing an HTML page to execute native Java code is inherently insecure. Web pages are isolated from the app they're running in by design. Creating a bridge between the web page and the app means that javascript on the page may have the ability to do anything on the device for which the app that's hosting the web page has permissions for. It is extremely important that you keep this in mind when creating the bridge from the web page to your app.

There are three main things that you need to do in order to call native Java methods from javascript:

Create a javascript interface object which serves as the bridge between javascript and Java.

Add the interface to the Web View so that any page that it hosts can use the bridge.

Update your javascript to use the bridge.

Creating a Javascript Interface Object

In this very basic class I am declaring a listener interface which I have the caller pass in via the constructor. This is one way to make sure that you only allow javascript to interact with your app in the way you expect. By using a well defined interface you are making it more difficult for malicious javascript to execute arbitrary code.

To make a method visible to javascript the method needs to be decorated with the @JavascriptInterface attribute.

Notice that I'm calling the listener back using a handler and runnable. This is because the bridge class methods aren't executing on the main thread. We need to execute the listener callback on the main thread.

// This code is not executed in the UI thread // so we must force it to happennew Handler(Looper.getMainLooper()).post(new Runnable()
{@Overridepublicvoid run()
{if(listener != null)
listener.onSomeChange(newText);
}
});
}
}

Add The Javascript Bridge Object To Your WebView

The way you wire up your native javascript bridge is to add the javascript interface to your Web View using the addJavascriptInterface method. This method takes in two parameters, your bridge object and the key word you want to use to access the bridge from within javascript.

Update The Javascript To Use The Native Bridge Object

The last step is to call into your native Java code from javascript. Using the NativeBridge object we registered with the Web View we can call the someChangeFromWeb method from our javascript. Note, the name of the object you use in javascript has to be exactly the same as the second parameter you passed into the addJavascriptInterface method.

It's not a good idea to try to mess with web page content for pages that you don't control because those pages can change and those changes may break your app or provide a poor user experience in your app. But there are cases when you do want to interact with web pages that you control the content of.

One example is if you provide content in your app via a web page whose content structure changes often. Using a web browser to display this data in your app provides the benefit to your users of being able to update that content without updating the app itself.

The way to accomplish this while providing a good user experience that's less likely to break when you update your web site is to define a Javascript contract for interacting with the web content. This contract is made up of a specific set of Javascript functions that your page offers the app which provides a known outcome. This allows the web page to change and add additional content while adhering to the predefined contract and gracefully failing the web page when the contract needs to change.

Executing Javascript

There are two main options you have to execute Javascript within a web page from native Java code. The option you choose will depend on what the minimum version of Android is that you are targeting in your app.

Executing Javascript on Android 4.4+

The preferred option is to use the evaluateJavascript method in the WebView class. This allows you to send some Javascript to the browser to execute and handle any expected result in native code. The evaluateJavascript method was introduced in Android 4.4 with a new WebView class based on the Chromium. Here's an example of how to scroll a web page to the top using this new method:

Executing Javascript on Android 4.3 or below

If you are targeting Android 4.3 or below you'll need to use the WebView's loadUrl method to execute your Javascript. Prefacing the your URL with "javascript:" lets the WebView know that you would like to execute Javascript in the context of the current page being displayed.

The first thing you may have noticed is that you don't get the ability to deal with the result of the Javascript method you just executed. If you are executing Javascript from which you expect a result you'll need to use the WebView's addJavascriptInterface method and explicitly call the callback you've registered. Next week's post will go into greater detail on using this method to allow your web page to trigger native Java code to deal with the result of your Javascript code execution.

Monday, February 9, 2015

The web has become a ubiquitous medium for information and communication. We use the web to disseminate information about ourselves, our companies, and our thoughts. We consume our news and other information about the world on the web. We purchase our groceries and gifts on the web. We even communicate with our friends, families, and loved ones over the web.

When we think about the web for native mobile applications we tend to think of the web as a data transfer mechanism. We talk about the cloud, JSON, XML, and RSS; all of which are important but often we need the web as more than the backbone of information. There are a couple of reasons you may want to display web content in your app instead of a native view:

Today I'm going to start a series on the Android WebView. In this post we'll talk about the basic's of setting up a WebView to display web content in your app. In other posts I'll explain how to interact with web content via Javascript from your app as well as interacting with your app from Javascript. Finally I'll go over some gotchas or hurdles that it's important to be aware of when using a web view in your app.

Initializing the WebView

Initializing the WebView should be done when your Activity or Fragment is created. While the default settings the WebView uses will probably work for most basic web pages, you can be more explicit about how your WebView acts. You specify your preferences using the WebSettings object on your WebView.

// Used to handle external interactions. More details below.browser.setWebChromeClient(this.getWebChromeClient());

// Used to handle page events. More details below.browser.setWebViewClient(this.getWebViewClient());
}

Loading A Web Page

Once you have a WebView added to your app displaying web pages is as simple as calling loadUrl on the WebView. For example:

privatevoidloadWebPage(String url)
{/** make sure you have a WebView in your layout with the id: browser **/WebView browser = (WebView)getView().findViewById(R.id.browser);
browser.loadUrl(url);

}

Loading HTML Directly

Loading web data into your page doesn't have to be done using a remote URL. If you already have the HTML content you'd like to display (possibly from an RSS feed or offline content), you can do so very easily by first converting the content to Base64 and then creating a data URI to display the content. For example, you can generate a url for HTML content you already have that the WebView can display as follows:

Advanced Browser Features

Handling Web Page Life-cycle and Other Events

There are several events associated with the web page life-cycle. For example there are events associated with web page loading being started/stopped. There are also events for web page errors as well as a login request from the web page. These web page events are handled via a WebViewClient. Your browser should, at a minimum, provide feedback to the user when a web page starts loading and when it stops loading. Here's an example of how you can handle those events:

protectedWebViewClientgetWebViewClient()
{ returnnewWebViewClient()
{@Overridepublicvoid onPageStarted(WebView view, String url, Bitmap favicon)
{/** provide feedback using a spinner or some other dialog to the user to let them know that you're doing some work **/
}

@Overridepublicvoid onPageFinished(WebView view, String url)
{

/** hide the feedback that was provided to the user **/
}
};
}

External Browser Interactions

One of the more nuanced aspects of displaying web content in your app is when the web content wants to do something external to the current browser. In order to interact with the browser for some of these more advanced features you need to use a WebChromeClient. For example, playing back HTML5 video fullscreen is done using the WebChromeClient's onShowCustomView method. Other examples of advanced external browser interactions are reporting debug messages to the Javascript console, opening/closing additional browser windows, displaying messages from Javascript, and etc.

It's important to note that there is only one WebChromeClient. Because of this you'll likely want to subclass WebChromeClient if you plan to provide many of these advanced features in your app. Here's an example of creating a WebChromeClient that can update the progress indicator of a Fragments Activity.

Monday, February 2, 2015

If there was one thing that I wish college had prepared me for, that it didn't, it's to understand how important failure is to success. I know, that sounds like an oxymoron but it's absolutely true. You cannot be successful, truly successful for the long haul, without being able to embrace failure.

As a professional there are several reasons why you'll want to be able to embrace failure.

Without failure, there are no real risks

The airplane was a risk.

The automobile was a risk.

The internet was a risk.

Risks allow us to expand, to push the boundaries, to get better. Not all risks pan out the way we want but the important ones do.

Failure allows you to be a real person

I know it's hard to admit but you're human. Humans are fallible people. We make mistakes. Failure allows you to be a bit more humble and remember that you have flaws.

It's not until you are able to admit you have flaws that you can start to learn from them. Understanding our flaws makes us more resilient.

Failure builds trust

Plain and simple, there is not a person who has not walked this earth that has not made a mistake. There are only people that hide their mistakes. Attempting to hide our flaws is a barrier to earning trust.

People that are able to admit their mistakes are more trustworthy. It shows that you're concerned about more than just your outward appearance. It shows that you are able to look at yourself more objectively.

If you're able to look past your own mistakes, people trust that you're able to look past theirs.

Failure allows you to learn from your mistakes

Every failure is an opportunity to learn. The most successful people I know are people who are constantly learning. It's a curiosity about the world around them that drives them.

As a key part of the world you should be curious about yourself. Being curious about what makes you tick will help you to understand how to navigate around and beyond your failures.

Failure gives you the ability to change

By far the most important part of embracing failure that it gives us an opportunity to change, to get better. Are you impatient, do you not listen well, are you quick to anger, are you defensive? What are the failures that are getting in the way of you being successful today?

Taking the time to reflect on the situations where you've failed allows you to understand why you've failed and gives you an opportunity to succeed next time the situation arises.

About Me

I’ve been in the technology industry for 14+ years mostly as a Software Developer. I've worked on projects ranging from a Ruby on Rails Portal, a high performance/low latency .NET search stack, R&D for building infrastructure in the cloud including automated server builds using Chef, to architecting and building iOS and Android Mobile platforms and frameworks.I've also spent some time building Android, iOS, Windows Phone, and Bada mobile applications that focused on the presentation of long form audio/video.The middle of my career was spent at MSNBC working on their content management publishing platform, their video syndication system, as well as being a co-creator of their iOS iPad framework from which the Rachel Maddow, Today Show, and Nightly News iPad apps were built.Finally, the beginning of my career was spent writing software on government contracts with a few years spent writing criminal investigation software.