Friday, 10 August 2012

Use jQuery Mobile to Build a Native Android News Reader App

Use jQuery Mobile to Build a Native Android News Reader App: Part 3

Converting Into A Native Android Application

The web application completed in Part 2 will now be converted into a native Android application. The discussion below applies to Android OS 2.2 & 2.3.
The Android application will use index.html as its UI component. We will write an android.app.Activity class to define the integration point between index.html and the native application. We will also write an android.webkit.WebViewClient class to make sure that the News Detail page is displayed inside the original android.webkit.WebView instance where the News application is launched.

Changes In index.html

We will update the NEWS_URI variable as follows:
  1. var NEWS_URI = 'http://rss.news.yahoo.com/rss/';  
We do not need bridge.php in the native Android application to forward AJAX calls to Yahoo! News. This is because the same-origin restriction does not apply here. When packaged as part of the native application, the index.html file is not downloaded from a web server. As such, it can make AJAX calls to remote URLs.
In addition, we add the following function:
  1. var EMPTY = '';  
  2. ...  
  3. function changeLocation(varURI){  
  4.   showProgress();     
  5.   $.get(EMPTY,function(data){  
  6.     window.location = varURI;  
  7.   });     
  8. }  
The changeLocation() function will be called from the android.webkit.WebViewClient, which will be shown momentarily. The purpose of the function is to show the progress page during transition from the News page to the News Detail page.
  • The first step in changeLocation() is to display the progress page.
  • Remember that the jQuery get() function is a specialized jQuery ajax() function. We call get() passing to it an empty URL and a callback handler, that sets the window.location variable to the input argument. The input argument is the URL in the <a href='...'> attribute enclosed within an a tag for a news item, as discussed in Part 2, “Going To The News Detail Page From The News Page”. When the URL loads, the progress page is replaced with contents from that URL.
  • As we point out below, the function changeLocation() is not an essential part of migrating the web application into a native one. It is only needed to display a progress page when transitioning from the News page to the News Detail page in the native application.
  • A progress page is not needed in the web application when transitioning from the News page to the News Detail page. This is because during the transition the web browser itself displays a progress indicator to the user. For example, in Android, both the native and Dolphin browsers display a spinning wheel and a progress bar in the navigation toolbar. In iOS, the Safari browser displays a similar progress indicator.

The Activity Class

The initial portion of our Activity class, named NewsActivity is shown below:
  1. package com.news;  
  2.   
  3. import android.app.Activity;  
  4. import android.webkit.WebView;  
  5. import android.os.Bundle;  
  6. ...  
  7. public class NewsActivity extends Activity {  
  8.     WebView mWebView;  
  9.   
  10.   public void onCreate(Bundle savedInstanceState) {  
  11.     super.onCreate(savedInstanceState);  
  12.     setContentView(R.layout.main);  
  13.   
  14.     mWebView = (WebView) findViewById(R.id.webview);  
  15.     mWebView.setWebViewClient(new NewsClient());  
  16.     mWebView.getSettings().setJavaScriptEnabled(true);  
  17.     mWebView.getSettings().setDomStorageEnabled(true);  
  18.     mWebView.loadUrl("android_asset/www/index.html");  
  19.   }  
  20.   ...  
  21. }  
  • In the onCreate() method, we first call the default implementation from the super class and then invoke setContentView() to load the layout file for this Activity. The input argument to setContentView() is R.layout.main which is a reference to main.xml in the res/layout folder.
  • We get a handle to the WebView via findViewById(R.id.webview). We set a custom WebViewClient on the WebView, named NewsClient (to be reviewed soon). Then, we configure the WebView to allow JavaScript execution and the DOM storage API (the latter is necessary to use HTML5 localStorage).
  • Finally, we ask the WebView to load the index.html page that has the UI code.
On the News Detail page, pressing the back button of the device will take the user back to the Categories page. To be assured of that, we first need to handle the onKeyDown event in our NewsActivity. This is shown below:
  1. public class NewsActivity extends Activity {  
  2. WebView mWebView;  
  3.   
  4.  public void onCreate(Bundle savedInstanceState) {  
  5.    ...  
  6.  }  
  7.    
  8.  public boolean onKeyDown(int keyCode, KeyEvent event) {  
  9.    if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {  
  10.      mWebView.goBack();              
  11.      return true;  
  12.    }  
  13.    return super.onKeyDown(keyCode, event);  
  14.  }    
  15.  ...  
If the key event corresponds to the back button of the device and the WebView has history to go back to, we then ask the WebView to go back a single step in its history. In the News Detail page, this will correspond to index.html. When history goes one step back, the Categories page will be displayed following the steps described in Part 2, “Application Startup”.
Lastly, let us look at the custom WebViewClient which is implemented as an inner class of NewsActivity.
  1.  public class NewsActivity extends Activity {  
  2.     WebView mWebView;  
  3.   
  4.   public void onCreate(Bundle savedInstanceState) {  
  5.     ...  
  6.     mWebView.setWebViewClient(new NewsClient());  
  7.     ...  
  8.   }  
  9.     
  10.   public boolean onKeyDown(int keyCode, KeyEvent event) {  
  11.     ...  
  12.   }    
  13.     
  14.   private class NewsClient extends WebViewClient {  
  15.   
  16.     public boolean shouldOverrideUrlLoading(WebView view, String url) {  
  17.       view.loadUrl("javascript:changeLocation('" + url + "')");  
  18.       return true;  
  19.     }  
  20.   }  
  21. ...  
  22. }  
  23.    
The only operation we override from the parent class is shouldOverrideUrlLoading() where we instruct the WebView to call the JavaScript function changeLocation() in index.html.
  • Had we not defined a custom WebViewClient, the News Detail page would be displayed in a separate browser application, outside the News application. Therefore, defining a custom WebViewClient is essential to display the News Detail page as part of the News application (i.e. in the same WebView that hosts the index.html).
  • We could have written shouldOverrideUrlLoading()in a more simplified manner, as follows:
    1. public boolean shouldOverrideUrlLoading(WebView view, String url)  
    2. {  
    3.     view.loadUrl(url);  
    4.     return true;  
    5. }  
    That would be sufficient to display the News Detail page in the same WebView that hosts index.html. However, the transition from the News page to the News Detail page would not include showing the progress page.
Having reviewed the Activity class, let us look at other components of our application.

AndroidManifest.xml

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.   package="com.news" android:versionCode="1" android:versionName="1.0">  
  4.     <application android:icon="@drawable/icon" android:label="@string/app_name">  
  5.       <activity android:name=".NewsActivity" android:configChanges="orientation|keyboardHidden"  
  6.         android:label="@string/app_name">  
  7.           <intent-filter>  
  8.             <action android:name="android.intent.action.MAIN" />  
  9.             <category android:name="android.intent.category.LAUNCHER" />  
  10.           </intent-filter>  
  11.       </activity>  
  12.     </application>  
  13.     <uses-permission android:name="android.permission.INTERNET" />  
  14. </manifest>   
For a general discussion on the AndroidManifest.xml file refer to the official reference. In that file, there are two particular items worthy of commenting on.
  • As described in the android.app.Activity documentation, by default, a configuration change, including a change in orientation or keyboard accessibility, results in the current activity being destroyed. To prevent the default behavior, we configure our application by specifying the configuration changes that will be handled by the application itself. This is defined in the configChanges attribute where orientation corresponds to orientation change and keyboardHidden corresponds to a keyboard accessibility change (e.g. a user lays open the device keyboard). We are configuring the application so that if any of those changes occur, the current activity is not destroyed.
  • The element <uses-permission android:name="android.permission.INTERNET" /> allows the application to access the Internet.

strings.xml

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.   <string name="app_name">News</string>  
  4. </resources>  
This file defines the constant named app_name which is used to identify the News application. The value of that attribute is displayed in various places in our Android device, as shown below. From left to right: under the application launch icon, the application title bar, Settings – Manage applications.

No comments:

Post a Comment