Tuesday, February 11, 2014

How to develop an Android app using Apache Cordova and jQuery Mobile

http://www.openlogic.com/wazi/bid/332279/how-to-develop-an-android-app-using-apache-cordova-and-jquery-mobile


Apache Cordova is a platform for building native mobile applications using common web technologies, including HTML, CSS, and JavaScript. It offers a set of APIs that allow mobile application developers to access native mobile functions such as audio, camera, and filesystem using JavaScript. Another developer tool, jQuery Mobile, is one of the best mobile web application frameworks. It allows developers to create rich web applications that are mobile-friendly. You can use Apache Cordova with jQuery Mobile to create a complete Android application.
To create, develop, build, and test a Cordova application, you can use the Cordova command-line interface. From the Cordova CLI you can create new Cordova projects, build them on mobile platforms, and run them on real devices or within emulators.
Before you install the Cordova CLI, install the Android SDK and Node.js, and be sure you have Apache Ant installed, then use the Node.js command sudo npm install -g cordova to install Cordova. The latest Cordova version is 3.3.0.
Create a Cordova project by running the command cordova create voicememo com.xyz.voicememo VoiceMemo. The first parameter tells Cordova to generate a voicememo directory for the project. The directory will contain a www subdirectory that includes the application's home page (index.html), along with various resources under css, js, and img directories. The command also creates a config.xml file that contains important metadata Cordova needs to generate and distribute the application.
The second and the third parameters are optional. The second parameter, com.xyz.voicememo, provides a project namespace. For an Android project, such as the one we are building, the project namespace maps to a Java package with the same name. The last parameter, VoiceMemo, provides the application's display text. You can edit both of these values later in the config.xml file.
Now you have a Cordova project that you can use as a base for generating platform-specific code. Before you generate Android code, run the commands
cd voicememo
cordova platform add android
The cordova platform command depends on Apache Ant. After you run it, you will find a new platforms/android subdirectory under the voicememo directory.
To generate Android-specific code under platforms/android, build the project using the cordova build command under the voicememo directory. You can then run and test the generated Android project in the Cordova emulator by executing the command cordova emulate android.
Note: The Cordova project recommends you make your code changes in the root www directory, and not in the platforms/android/assets/www directory, because the platforms directory is overwritten every time you execute a cordova build command after you use Cordova CLI to initialize the project.
Figure 1
The home page of the VoiceMemo application, which represents the list of voice memos.
From the Voice Listing page, users can click on three buttons: New, to create a new recording; About, to open the application's About page; and Remove All Memos, to remove the saved voice memos.
When users click the New button, they are forwarded to the voice recording page:
Figure 2
Here users can enter the title and description of a voice memo, then click the Record button to invoke the voice recording application of the Android mobile device. When a recording is completed, users are returned to the voice recording page and can play back the recording or save it. To work with voice recording and playback in Cordova, you can install several plugins; the first two below are mandatory. Run the following commands from the voicememo directory:
  • Media Capture plugin, for media capture:
    cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture.git
  • Media plugin, for working with media:
    cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-media.git
  • Device plugin, for accessing device information:
    cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-device.git
  • Dialog plugin, for displaying native-looking messages:
    cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-dialogs.git
  • File plugin, for accessing the mobile filesystem:
    cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-file.git
Apply these plugins to your Cordova project by running the cordova build command.
At this point you're done with the preparation of the application. You can now start writing the application custom code.
Let's look at the VoiceMemo application directory hierarchy. The www directory contains the following subdirectories:
Figure3
  • css contains the custom application Cascading Style Sheet.
  • jqueryMobile contains the jQuery Mobile framework and plugins JStorage and Page Params.
  • js contains all the custom application JavaScript code. It has three subdirectories:
    • api contains the application managers (VoiceManager and CacheManager) and utility files.
    • model contains the application model. In this application we have a single object that represents a Voice item called VoiceItem.
    • vc contains the application view controllers, which include the application action handlers. Action handlers usually create model objects and populate them with UI data, pass them to the application APIs, and display the results on the application view or page.
Finally, also under www, index.html contains all of the application pages, which for this project comprise three jQuery pages:
  • Voice Listing (home page)
  • Voice Recording
  • About
The following code snippet shows the Voice Listing jQuery page code in the index.html file.

Let's walk through the code. The page has a header (the div whose data-role="header") that contains two navigation buttons to the About page and to the Voice Recording page. It has content (the div whose data-role="content") that contains the list of the voice recordings that are populated when the page is shown. Finally, it has a footer (the div whose data-role="footer") that contains a button to remove all of the voice memos from the list.
Now let's look at the page view controller JavaScript object, which includes the action handlers of the page (voiceList.js). voiceList.js is included in the index.html page, so when it is loaded by the browser the script is automatically executed to register the JavaScript event handlers for this jQuery page.
(function() {
 
    var voiceManager = VoiceManager.getInstance();
 
    $(document).on("pageinit", "#voiceList", function(e) {
     
     $("#removeAllVoices").on("tap", function() {
      e.preventDefault();
      
      voiceManager.removeAllVoices();
      
      updateVoiceList();
     });      
    });
    
    $(document).on("pageshow", "#voiceList", function(e) {
        e.preventDefault();
        
        updateVoiceList();
    });
    
    function updateVoiceList() {
        var voices = voiceManager.getVoices();

        $("#voiceListView").empty();
                
        if (jQuery.isEmptyObject(voices)) {
            $("
  • No Memos Available
  • ").appendTo("#voiceListView"); } else { for (var voice in voices) { $("
  • " + voices[voice].title + "
  • ").appendTo("#voiceListView"); } } $("#voiceListView").listview('refresh'); } })();
    The "pageinit" event handler, which is called once in the page initialization, registers the voice recordings removal tap event handler. The voice recordings removal tap event handler removes the list of voice recordings by calling the removeAllVoices() method of the VoiceManager object, then updates the voice listview.
    The "pageshow" event handler, which is called every time the page is shown – it is triggered on the "to" transition page, after the transition animation completes – updates the voice list view with the current saved voice recordings. To do this it retrieves the current saved voice recordings by calling the getVoices() method of VoiceManager, then adds the voice items to the list view so that if any voice item in the list is clicked, its ID will be passed to the voice recording page to display the voice item details.
    Note: In a jQuery Mobile list view you must call listview('refresh') to see list view updates.
    The next code snippet shows the Voice Recording page code in the index.html file:
    Home

    Record Voice

    Back

    The Voice Recording page has header and content sections. The content div contains the voice recording elements (title, description, and the voice recording file) and a Save button. Let's see the page view controller JavaScript object, which includes the action handlers of the page (recordVoice.js). Like the event handler JavaScript we just looked at, recordVoice.js is included in the index.html page and is automatically executed when it is loaded by the browser.
    (function() {
        
        var voiceManager = VoiceManager.getInstance();
        
        $(document).on("pageinit", "#voiceRecording", function(e) {
            e.preventDefault();
            
            $("#saveVoice").on("tap", function() {
                e.preventDefault();
    
                var voiceItem = new VoiceItem($("#title").val() || "Untitled", 
                                              $("#desc").val() || "", 
                                              $("#location").val() || "",
                                              $("#vid").val() || null);
                
                voiceManager.saveVoice(voiceItem);
                
                $.mobile.changePage("#voiceList");
            });        
            
            $("#recordVoice").on("tap", function() {
                e.preventDefault();
            
                var recordingCallback = {};
                
                recordingCallback.captureSuccess = handleCaptureSuccess;
                recordingCallback.captureError = handleCaptureError;
                
                voiceManager.recordVoice(recordingCallback);
             }); 
            
            $("#playVoice").on("tap", function() {
                e.preventDefault();
            
                var playCallback = {};
                
                playCallback.playSuccess = handlePlaySuccess;
                playCallback.playError = handlePlayError;
                
                voiceManager.playVoice($("#location").val(), playCallback);
            });           
            
        });
        
        $(document).on("pageshow", "#voiceRecording", function(e) {
            e.preventDefault();
            
            var voiceID = ($.mobile.pageData && $.mobile.pageData.voiceID) ? $.mobile.pageData.voiceID : null;
            var voiceItem = new VoiceItem("", "", "");
            
            if (voiceID) {
                
                //Update an existing voice
                voiceItem = voiceManager.getVoiceDetails(voiceID);
            } 
            
            populateRecordingFields(voiceItem);
            
            if (voiceItem.location.length > 0) {
                $("#playVoice").closest('.ui-btn').show();
            } else {
                $("#playVoice").closest('.ui-btn').hide();    
            }        
        });
        
        $(document).on("pagebeforehide", "#voiceRecording", function(e) {
            voiceManager.cleanUpResources();
        });
        
        function populateRecordingFields(voiceItem) {
            $("#vid").val(voiceItem.id);
            $("#title").val(voiceItem.title);
            $("#desc").val(voiceItem.desc);
            $("#location").val(voiceItem.location);
        }
        
        function handleCaptureSuccess(mediaFiles) {
            if (mediaFiles && mediaFiles[0]) {        
                currentFilePath = mediaFiles[0].fullPath;
                
                $("#location").val(currentFilePath);
                
                $("#playVoice").closest('.ui-btn').show();  
            }
        }
        
        function handleCaptureError(error) {
         displayMediaError(error);
        }  
        
        function handlePlaySuccess() {
            console.log("Voice file is played successfully ...");
        }
        
        function handlePlayError(error) {
         displayMediaError(error);
        }        
        
        function displayMediaError(error) {
            if (error.code == MediaError.MEDIA_ERR_ABORTED) {
                AppUtil.showMessage("Media aborted error");
            } else if (error.code == MediaError.MEDIA_ERR_NETWORK) {
                AppUtil.showMessage("Network error");
            } else if (error.code == MediaError.MEDIA_ERR_DECODE) {
                AppUtil.showMessage("Decode error");
            } else if (error.code ==  MediaError.MEDIA_ERR_NONE_SUPPORTED) {
                AppUtil.showMessage("Media is not supported error");
            } else {
             console.log("General Error: code = " + error.code);
            }        
        }
    })();
    
    The "pageinit" event handler registers the voice save, record, and play tap event handlers. The voice saving tap event handler saves a voice recording by calling the saveVoice() method of the VoiceManager object. The user is then forwarded to the voice listing page using $.mobile.changePage("#voiceList"). This page can be used either to create a new voice recording or update an existing one. In the second case, the voice ID is passed from the list view of the Voice Listing page and is saved in a hidden field "vid" to be used by VoiceManager for updating the existing voice recording. In the first case, the "vid" hidden field value is empty, which signals VoiceManager that this is a new voice recording and not an update to an existing one.
    The voice ID is retrieved on the "pageshow" event of the voice recording page. If there is a passed voice ID from the Voice Listing page then the code retrieves the full voice recording information using voiceManager.getVoiceDetails(voiceID) and populates the form elements using the retrieved information. Finally, in the "pageshow" handler, if there is an existing voice recording (that is, voiceItem.location.length > 0) then the program displays the Play button to allow users to play the voice recording.
    Note that the application automatically passes parameters between the two pages thanks to the jQuery Mobile Page parameters plugin, which is included on the index.html page.
    In the voice recording tap event handler, the code starts voice recording by calling voiceManager.recordVoice(recordingCallback). The recording callback contains two attributes: VoiceManager calls captureSuccess if the voice capture process succeeds, and captureError it fails. With the captureSuccess callback (handleCaptureSuccess), the full voice recording path is stored in a hidden field to be used later to play the voice. With the captureError callback (handleCaptureError), an error message is displayed.
    The voice playing tap event handler starts voice playback by calling voiceManager.playVoice(voiceLocation, playCallback). The playing callback contains two attributes: playSuccess and playError. VoiceManager calls playSuccess if voice play succeeds, and playError if it fails. The handlePlaySuccess callback prints a statement in the console log to indicate that voice play succeeded, while the handlePlayError callback displays an error message to the user.
    The jQuery Mobile framework calls the "pagebeforehide" event before a page is hidden. This event handler calls voiceManager.cleanUpResources() to stop any recording that is currently playing and to clean up the media object.
    So much for the two main pages of the application. Here's the complete code of the index.html file:
    
        
            
            
            
            
            
                  
              
                           
                                                 
                  
                              
                        
                         
            Voice Memo
        
                
               
    
    Home

    Record Voice

    Back
    Home

    About

    Back
    This sample is developed for education purposes
    Let's look at the application APIs – that is, the scripts listed at the end of index.html, which are placed before the view controller objects so they can be used by them. The following code snippet shows the VoiceManager object, which is the main API object used by the view controller objects (voiceList and recordVoice):
    var VoiceManager = (function () {     
      var instance;
     
      function createObject() {
          var cacheManager = CacheManager.getInstance();
          var VOICES_KEY = "voices";
          var voiceMap;
          var audioMedia;
          
          return {
              getVoices: function () {
                  voiceMap = cacheManager.get(VOICES_KEY) || {};
                  
                  return voiceMap;
              }, 
              getVoiceDetails: function (voiceID) {
                  voiceMap = cacheManager.get(VOICES_KEY) || {};
                  
                  return voiceMap[voiceID];
              },
              saveVoice: function (voiceItem) {  
                  voiceMap = cacheManager.get(VOICES_KEY) || {};
                  
                  voiceMap[voiceItem.id] = voiceItem;
                  
                  cacheManager.put(VOICES_KEY, voiceMap);
              }, 
              removeAllVoices: function() {
                  cacheManager.remove(VOICES_KEY);
              },
              recordVoice: function (recordingCallback) {
                  navigator.device.capture.captureAudio(recordingCallback.captureSuccess, 
                                                        recordingCallback.captureError, 
                                                        {limit: 1});
              }, 
              playVoice: function (filePath, playCallback) {
                  if (filePath) {
                   
                   //You have to make this in order to make this working on Android ...
                      filePath = filePath.replace("file:/","file://");
                      
                   this.cleanUpResources();
                      
                   audioMedia = new Media(filePath, playCallback.playSuccess, playCallback.playError);
                 
                   // Play audio
                   audioMedia.play();
                  }            
              }, 
              cleanUpResources: function() {
                  if (audioMedia) {
                   audioMedia.stop();
                      audioMedia.release();
                      audioMedia = null;
                  } 
              }
        };
      };
     
      return {
        getInstance: function () {
          if (!instance) {
              instance = createObject();
          }
     
          return instance;
        }
      }; 
    })();
    
    As you can see, VoiceManager is a singleton object that has seven methods:
    Method NameDescription
    getVoices() Get all of the saved voices from the mobile local storage using the CacheManager object.
    getVoiceDetails(voiceID) Get the voice details using the voice ID from the mobile local storage using the CacheManager object.
    saveVoice(voiceItem) Save the voice item object in the mobile local storage using the CacheManager object.
    RemoveAllVoices() Remove all the voice items from the mobile local storage using CacheManager object.
    recordVoice(recordingCallback) Use Cordova navigator.device.capture.captureAudio to capture the voice recording. Call recordingCallback.captureSuccess if the operation succeeds and recordingCallback.captureError if it fails.
    playVoice(filePath, playCallback) Uses Cordova Media object to play the voice recording, whose full location is specified in the filePath parameter. Call playCallback.playSuccess if the operation succeeds and playCallback.playError if it fails.
    CleanUpResources() Stop any playing recording and clean up media resources.
    VoiceManager uses CacheManager to persist, update, delete, and retrieve the voice items. The next code snippet shows the CacheManager object.
    var CacheManager = (function () {     
      var instance;
     
      function createObject() {   
        return {
            put: function (key, value) {
                $.jStorage.set(key, value);
            },
            get: function (key) {
             return $.jStorage.get(key);
            },
            remove: function (key) {
             return $.jStorage.deleteKey(key);
            }
        };
      };
     
      return {
        getInstance: function () {
     
          if (!instance) {
            instance = createObject();
          }
     
          return instance;
        }
      }; 
    })();
    

    CacheManager is a singleton object that uses jStorage to access local storage. CacheManager has three methods:
    Method NameDescription
    put(key, value) Add an entry in the local storage with (key, value) pairs.
    get(key) Get the entry value whose key is specified as a parameter.
    remove(key) Remove the entry whose key is specified as a parameter.
    The final code snippet shows the VoiceItem object, which represents the voice item with the attributes of title, description, location, and ID.
    var VoiceItem = function(title, desc, location, id) {
     this.title = title || "";
     this.desc = desc || "";
     this.location = location || "";
     this.id = id || "Voice_" + (new Date()).getTime();
    };
    
    VoiceItem.prototype.toString = function () {
     return "Title = " + this.title + ", " +
         "Description = " + this.desc + ", " +
         "Location = " + this.location + ", " +
         "ID = " + this.id;
    };
    
    At this point I've walked you through the application logic and what happens when users interact with each screen. Now let's see how it works. You can run the application from the command line under the voicememo directory with the command cordova emulate android. If you want to try it yourself you can download the complete code.
    After you've run the cordova build command, you should find the generated Android APK file under the platforms/android/bin directory, and you can deploy it on your Android phone or tablet.

    Conclusion

    At this point I hope you can see how to design and implement a complete native Android mobile application that uses Apache Cordova as a platform for accessing mobile native features and jQuery Mobile as a powerful mobile application framework. Armed with this knowledge, you can start developing your own native Android applications using your HTML, CSS, and JavaScript skills.

    No comments:

    Post a Comment