- Layered functionality
- Graceful degradation
WebView
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView mWebView = new WebView(this);
setContentView(mWebView);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/hello.html");
mWebView.addJavascriptInterface(
new JavaScriptBridge(this), "MyAndroid");
}
private class JavaScriptBridge {
private Context mContext;
public JavaScriptBridge(Context context) {
mContext = context;
}
public void showToast(String msg) {
Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
}
}
<html>
<head>
<script type="text/javascript">
function sayHello() {
if (typeof MyAndroid === 'undefined') {
alert('Hello');
} else {
MyAndroid.showToast('Hello');
}
}
</script>
</head>
<body>
<a href="javascript:sayHello();">Say hello</a>
</body>
</html>
<html>
<head>
<script type="text/javascript">
function pickContact() {
if (typeof MyAndroid === 'undefined') {
return;
} else {
MyAndroid.pickContact();
}
}
</script>
</head>
<body>
<label id="emailLabel" onClick="pickContact();">Email</label>
<input id="email" type="text" />
</body>
</html>
private class JavaScriptBridge {
// Constructor omitted
public void pickContact() {
Intent intent = new Intent(Intent.ACTION_PICK,
ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(intent, R.id.request_code_pick_contact);
}
}
res/values/ids.xml
<resources> <item name="request_code_pick_contact" type="id" /> </resources>
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (resultCode != RESULT_OK) {
return;
}
switch (requestCode) {
case R.id.request_code_pick_contact:
fillEmail(data);
break;
}
}
private void fillEmail(Intent data) {
String id = String.valueOf(ContentUris.parseId(data.getData()));
Cursor cursor = getContentResolver().query(
Email.CONTENT_URI, null,
Email.CONTACT_ID + "=?",
new String[]{ id }, null);
if (cursor.moveToFirst()) {
int index = cursor.getColumnIndex(Email.DATA);
String email = cursor.getString(index);
mWebView.loadUrl("javascript:fillEmail('" + email + "')");
} else {
Toast.makeText(this, R.string.email_error, Toast.LENGTH_SHORT).show();
}
cursor.close();
}
function fillEmail(email) {
var input = document.getElementById('email');
input.value = email;
}
<label id="emailLabel" onClick="pickContact();">Email</label> <input id="email" type="text" />
function init() {
if (typeof MyAndroid === 'undefined') {
return;
}
// Make the email label look like a link in Android
var label = document.getElementById('emailLabel');
label.style.color = '#00f';
label.style.textDecoration = 'underline';
}
<body onLoad="init()"> <label id="emailLabel" onClick="pickContact();">Email</label> <input id="email" type="text" /> </body>
<body>
<label>Photo</label>
<input id="photoData" type="hidden" />
<div>
<img id="photo" style="display: none;" />
</div>
<input type="file" value="Pick photo"
onClick="return pickPhoto();" />
</body>
<script type="text/javascript">
function pickPhoto() {
if (typeof MyAndroid === 'undefined') {
return true; // Let the browser handle it
}
MyAndroid.pickPhoto();
return false;
}
</script>
<label>Photo</label> <input id="photoData" type="hidden" /> <img id="photo" style="display: none;" /> <input type="file" value="Add photo" onClick="return pickPhoto();" />
public void pickPhoto() {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, R.id.request_code_pick_photo);
}
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (resultCode != RESULT_OK) {
return;
}
switch (requestCode) {
case R.id.request_code_pick_photo:
fillPhoto(getPhotoFromGallery(data));
break;
}
}
private Bitmap getPhotoFromGallery(Intent data) {
Uri selectedImage = data.getData();
String[] filePathColumn = { MediaStore.Images.Media.DATA };
Cursor cursor = getContentResolver().query(
selectedImage, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String path = cursor.getString(columnIndex);
cursor.close();
return BitmapFactory.decodeFile(path);
}
private void fillPhoto(Bitmap bitmap) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
String encoded
= "data:image/png;base64," + Base64.encodeBytes(byteArray);
mWebView.loadUrl("javascript:fillPhoto('" + encoded + "');");
}
String is the only way to pass data to javascriptBase64 encodes binary data in an ASCII string
function fillPhoto(data) {
var photoData = document.getElementById('photoData');
photoData.value = data;
var photo = document.getElementById('photo');
photo.src = photoData.value;
photo.style.display = photoData.value == '' ? 'none' : 'block';
}
<input id="photoData" type="hidden" /> <img id="photo" style="display: none;" />
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mWebView.saveState(outState);
}
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mWebView.restoreState(savedInstanceState);
}
public class MainActivity extends Activity {
private WebView mWebView = null;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
setContentView(mWebView);
}
private void init() {
if (mWebView != null) {
return;
}
mWebView = new WebView(this);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/index.html");
mWebView.addJavascriptInterface(
new JavaScriptBridge(this), "MyAndroid");
}
}
<activity android:label="@string/app_name" android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation" >
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Check if the key event was the Back button and if there is
// history
if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
mWebView.goBack();
return true;
}
// If it wasn't the Back key or there's no web page history, bubble
// up to the default system behavior (probably exit the activity)
return super.onKeyDown(keyCode, event);
}
WebView has access to your native calls!WebView
private void init() {
// Other init omitted
mWebView.loadUrl(
"http://www.sqisland.com/talks/progressive-webview/sample");
mWebView.setWebViewClient(new MyWebViewClient());
}
private class MyWebViewClient extends WebViewClient {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (Uri.parse(url).getHost().equals("www.sqisland.com")) {
// This is my web site, OK to load page
return false;
}
// Otherwise, the link is not for a page on my site, so launch
// another Activity that handles URLs
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
}
}
http://community.phonegap.com/nitobi/topics/WebViewif (typeof MyAndroid === 'undefined') {
doSomethingInBrowser();
} else {
MyAndroid.doSomething();
}