Wednesday, June 6, 2012

PhoneGap Android Application Preferences Plugin

So I'm working on a side app that I get to touch about once a month for about 15 minutes and one of the things I ran into is that I need a way to store user preferences. Well I realized that I could build my own HTML page and store the data in localStorage but I wouldn't learn anything new that way. In the end I decided to do a plugin that would allow PhoneGap developers to use Android Application Preferences and I modelled it after the existing iOS plugin by originally by Tue Topholm (Randy McMillian ported it to the updated Plugin API on iOS).

First if you are unsure about how application preferences are setup on Android go read this tutorial and then come back I'll wait. Oh great, your back. Let's crack on with things.

Functionality

Okay, so the application preferences plugin will provide you with four methods that you can use to interact with the native Android preferences.

get(key, success, fail)

If the key exists in the preferences it will be returned as the single parameter of the success callback that you provide. If the key does not exist the failure callback will be executed with an error object with error.code set to 0 which means no property.

    window.plugins.applicationPreferences.get(key, function(value) {
        console.log("The value is = " + value);
    }, function(error) {
        console.log(JSON.stringify(error));
    });

set(key, value, success, fail)

If the key exists in the preferences then value will be saved and your the success callback will be executed. If the key does not exist the failure callback will be executed with an error object with error.code set to 0 which again means no property.

    window.plugins.applicationPreferences.set(key, value, function(value) {
        console.log("set correctly");
    }, function(error) {
        console.log(JSON.stringify(error));
    });



load(success, fail)

Calling load will have the native side loop through all the preferences creating a JSON object that will be returned as the single parameter of your success callback.

    window.plugins.applicationPreferences.load(function(value) {
        console.log("The object is = " + JSON.stringify(value));
    }, function(error) {
        console.log(JSON.stringify(error));
    });


show(activity, success, fail)

Calling show passing in the class name of your PreferenceActivity class will cause the native Android GUI to be shown so your user can interact with the preferences. If the class name you pass in doesn't exists your failure callback will be called with an error object with error.code set to 1 which means no preferences activity.

    window.plugins.applicationPreferences.show("com.simonmacdonald.prefs.QuickPrefsActivity");

which brings up a GUI that looks like this:


Installation

Installation of the Plugin follows along the common steps:

  1. Add the script tag to your html:
    <script type="text/javascript" charset="utf-8" src="applicationPreferences.js"/>
  2. Copy the Java code into your project to the src/com/simonmacdonald/prefs folder.
  3. Create a preferences file named res/xml/preferences.xml following the Android specification.  
  4. Finally you'll need to create a class that extends PreferenceActivity in order to be able to view/modify the preferences using the native GUI. Refer back to the tutorial I mentioned for more details. 
That's about it. Give the plugin a try if you need to store native preferences and let me know what you think.

Oh, and I'm pretty sure that Darren McEntee has already included this plugin in his Live Football on TV app which means this plugin is already in the wild.

Enjoy!

50 comments:

  1. @Tue

    Sorry for the incorrect attribution. I looked at the iOS directory when I should have been looking at the iPhone directory. I just fixed up the post to give you credit.

    ReplyDelete
  2. Hi,
    I'm trying to install this plugin, but I can't run it because of 1 error:

    "The method startActivity(Intent) is undefined for the type CordovaInterface"

    There are also several warnings:

    "The value of the field AppPreferences.LOG_TAG is not used"

    The method getContext() from the type CordovaInterface is deprecated

    "Map is a raw type. References to generic type Map should be parameterized"

    "Iterator is a raw type. References to generic type Iterator should be parameterized"

    I'm using Eclipse 4.2 on Fedora

    Am I missing anything?
    Thanks in advance,
    Rafael

    ReplyDelete
  3. @Rafa

    Upgrade to 2.0.0 and the error should go away. Don't worry about the warnings though.

    ReplyDelete
  4. Hi, Simon,

    I can´t get started this AppPrefs plugin. I changed to cordova 2.0, set all things like the readme, create a preferences.xml and a QuickPrefsActivity like

    package my.package;

    import android.content.Intent;
    import android.os.Bundle;
    import android.preference.PreferenceActivity;


    public class QuickPrefsActivity extends PreferenceActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
    addPreferencesFromResource(R.xml.preferences);
    }

    Eclipse don´t show me any error, but the emulator shows me nothing, too :(

    Is there a working example?

    Sorry for bad english

    ReplyDelete
  5. Hi, Simon ... it´s running ;) anyway, thanks

    ReplyDelete
  6. Hi Simon,
    First of all, and knowing that I'm very belated in saying it, thanks a lot for your previous response. Because of several issues (including having to change my OS), I couldn't check it until a few minutes ago.

    The error got fixed as you said. But now I'm having another error, this time related with de javascript code. The error is
    Result of expression 'window.plugins' [undefined] is not an object at file ...js:196
    The referred line is:
    function dime_pref(key,defecto) {
    window.plugins.applicationPreferences.get(key, function(value) {
    console.log("El valor de la preferencia es = " + value);
    return value;
    }, function(error) {
    console.log(JSON.stringify(error));
    return defecto
    });
    }

    Of course, your code (cordova.define(...) is previous to that line.

    Once again, thank you very much in advance.

    ReplyDelete
  7. @Rafa

    Someone did a pull request on my plugin. You should just be able to call appPreferences.get() now. No need for the window.plugins bit.

    ReplyDelete
  8. Simon, thanks for your quick response.

    I must be doing something wrong. Now I get the error "ReferenceError: Can't find variable: appPreferences"

    I'm not much experienced on javascript, so I'm not sure how this works, but I notice that appPreferences is defined inside (more precisely, at the end of them) the following lines:
    cordova.define("cordova/plugin/applicationpreferences", function(require, exports, module) {
    ...
    ...
    var appPreferences = new AppPreferences();
    module.exports = appPreferences;
    });


    Am I missing anything?
    Thanks again

    ReplyDelete
  9. @Rafa

    Sorry, I made a mistake. here is the kinda code you want to add to your deviceready handler so you don't need to modify the rest of your html.


    if (!window.plugins) {
    window.plugins = {};
    }
    if (!window.plugins.applicationPreferences) {
    window.plugins.applicationPreferences = cordova.require("cordova/plugin/applicationpreferences");
    }

    ReplyDelete
  10. It worked! Great!

    Thanks a lot.

    ReplyDelete
  11. Using 2.0.0, I put the next code in the ondebiceReady event:

    if (!window.plugins) {
    window.plugins = {};
    }
    if (!window.plugins.applicationPreferences) {
    window.plugins.applicationPreferences = cordova.require("cordova/plugin/applicationpreferences");
    }


    applicationPreferences.get("enterTimes", function(value) {
    console.log("The value is = " + value);
    }, function(error) {
    console.log(JSON.stringify(error));
    });

    And i got the error:
    module cordova/plugin/applicationpreferences not found at undefined:0

    Could you help me please??

    Thank you very much!! :D

    ReplyDelete
  12. @52AMANTES

    Make sure you are using the 2.0.0 applicationPreferences.js file and you are including it as one of your script tags.

    ReplyDelete
  13. Hi, Simon,
    Here I'm again

    I don't know if I declared victory too soon, or made something wrong afterwards...

    I have this function:
    function dime_pref(key,defecto) {
    var valor = ''
    window.plugins.applicationPreferences.get(key, function(value) {
    console.log("El valor de la preferencia "+ key + " es = " + value);
    valor = value;
    }, function(error) {
    console.log(JSON.stringify(error));
    valor = defecto;
    });

    return valor
    }


    I call it in this way:
    var avance = dime_pref('avance','100')

    I see that it seems to work properly because the console log shows the proper value. But that value never gets the variable "avance". What may be wrong in what I'm doing?

    I can add that I'm getting also a previous error in console.log:
    TypeError: Result of expression 'cordova.exec' [undefined] is not a function. And it points at the line:
    cordova.exec(success,fail,"applicationPreferences","get",[key]);

    As always, thank you very much.

    ReplyDelete
  14. @Rafa

    You are trying to call an asynchronous method in a synchronous way. What is happening is that the get method does not execute before you return valor. You'll need to set the value of avance in the success method of the get.

    ReplyDelete
  15. Hi Simon,
    Thanks for your great information regarding the app prefs.
    i want to open the application preferences from the ios app. i need more info about how to implement this method,
    the last phongap plugin is not updated with the methods of show and load,
    i only can use the set/get methods.
    can you please tell me additional information?

    thanks

    ReplyDelete
  16. @zaherrrr

    Sorry I did not write the iOS plugin. When I was doing the Android version show/load seemed like good additions. You should add them to the iOS plugin and do a pull request.

    ReplyDelete
  17. You give very useful information iphone android application with that useful function. it is very good stuff but

    I have this function:
    function dime_pref(key,defecto) {
    var valor = ''
    window.plugins.applicationPreferences.get(key, function(value) {
    console.log("El valor de la preferencia "+ key + " es = " + value);
    valor = value;
    }, function(error) {
    console.log(JSON.stringify(error));
    valor = defecto;
    });

    ReplyDelete
  18. Simon, I've been traveling out of town and so far I have not been able to get back in front of the computer, so I don't remember well if I told you that I needed to get preferences values synchronously, because I would use them the page is being constructed (font size, background-color,...)

    How should I do it?
    Thank you very much in advance.

    ReplyDelete
  19. @Rafa

    Sorry you are going to need to restructure your code to work with the async call. If you need these values for page construction you may want to:

    1) Show a splash screen
    2) wait for device ready
    3) make a call to get the preferences
    4) refresh the page with the preference values
    5) hide the splash screen

    ReplyDelete
  20. Im new with phonegap and plugin and I try to use the appPreferences plugin but it keep giving me error: Class Not found.
    Im new with phonegap and plugin and I try to use the appPreferences plugin but it keep giving me error: Class Not found

    I even put the in the config.xml folder :S so I don't know what I'm missing to make it work

    ReplyDelete
  21. @Daniel Caymo

    Hey, blogger won't accept xml in the comment so if you could post your manifest.xml and config.xml to a site like pastebin or gist then link back to it I could probably help.

    ReplyDelete
  22. this is the link: http://pastebin.com/Ks2vyUzD
    http://pastebin.com/6uJnTpjz
    Thx for replying and sorry If i send a lot of msg in the sametime because I don't know if I send it D: srry I'm really new to asking people in blog for help

    ReplyDelete
  23. @Daniel Caymo

    The plugin line in your config.xml is wrong. The value of the plugin line should be:

    "com.simonmacdonald.prefs.AppPreferences"

    ReplyDelete
  24. i try that alreadybut it still wouldnt.work ;S that why i change it n i thought it wouldnt matter as long it the path where the.file.is.located

    ReplyDelete
  25. @Daniel Caymo

    Oh, it matters. The plugin line, the location of the file in the src folder and the package line in the the Java class must all match.

    ReplyDelete
  26. ah ok I try changing it tomorrow and see what happen thx you very for fast reply i let u know tomorrow if there any.change

    ReplyDelete
  27. I try to make the changes you told me and it still doesn't work and anw this is the whole application it a small one hope you be able to tell me what I'm doing wrong https://github.com/dandan28/test/down

    ReplyDelete
  28. This is my last question when i compile with eclipse through a virtual machine android the plugin works but when I try compiling it with https://build.phonegap.com/
    builds the plugin doesn't work

    ReplyDelete
  29. @Daniel Caymo

    Yeah, that is kinda essential information. You realize that PhoneGap Build does not support a ton of plugins and it does not support the AppPreferences plugin.

    ReplyDelete
  30. Hi,sorry I have another question again, when I try loading the ShowPreferenceAll automatically on body onload"ShowPreferenceAll();" it doesn't work?? because I want to show my Application preferences when my app load right away

    ReplyDelete
  31. @Daniel Caymo

    That's because you are not waiting for the deviceready event. You won't be able to call and PhoneGap plugin or core API until after that event is fired. So listen for the event then show preferences.

    ReplyDelete
  32. Thx Simon it work now :D Thx for all those fast reply :D

    ReplyDelete
  33. Hi Simon,

    I am using the 2.2.0 version of your application Preference plugin on Cordova 2.2 version but unfortunately I wasn't able to make it work. Though I don't get any error, I can't go through the success and error callback function after calling "get" method. Do I miss something on this? Thanks.

    ReplyDelete
  34. @Jake Monton

    Probably. What do you see in "adb logcat"?

    ReplyDelete
  35. Hey Simon,

    how to get rid of the second application icon, which appears when using that plugin ?

    BR
    Ray

    ReplyDelete
  36. @ramonbln

    You should not see two application icons when you use this plugin. I suspect that you have two LAUNCHER items in your AndroidManifest.xml.

    ReplyDelete
  37. Thanks! After removing the second LAUNCHER-entry, which I copied from your tutorial, the second LauncherIcon is gone :)

    BR
    Ray

    ReplyDelete
  38. Hi Simon,

    In my previous posts I had sent you links to codebin pastes that seem to be unreliable. As I mentioned before, thanks for your great preference plugin. It is absolutely helpful in my project. So far I have been able to load the preference activity. But when I try the load function I get the error that it is undefined. I was hoping that you would be able to help me correct this. I am uncertain as to how to reference the preference file if that is the problem. I have included links to pastes on pastebin this time hoping that they are more reliable for your viewing. These are the links:

    This is the applicationPreference script:
    http://pastebin.com/v8JM6HUP

    This is the HTML file:
    http://pastebin.com/2RFVZA57

    This is the error log after succesfully seen the preferences:
    http://pastebin.com/DCeAsgYE

    This is the undefined message for the load function:
    http://pastebin.com/MCrRC12H

    Thanks for your assistance.

    Best Regards,

    Edward Hanna
    hanna.edwardo@gmail.com

    ReplyDelete
  39. @Edwardo Hanna

    It is a class cast exception where an Integer is being cast as a String. Try declaring one of your numbers as a string in preferences and it should be okay.

    ReplyDelete
  40. Hi Simon,

    I got "undefined" result after executing "cordova.exec" at my plugin's javascript file (at "resultTemp" variable).
    here is my js file:

    (function(cordova){
    var DeviceInfo = function() {
    };

    DeviceInfo.prototype.imeiNumber = function(params, success, fail) {
    var resultTemp;

    alert("executing ImeiNumber");
    resultTemp = cordova.exec(function(args) {
    success(args);
    }, function(args) {
    fail(args);
    }, 'DeviceInfo', 'imeiNumber', [params]);

    alert(resultTemp);

    return resultTemp;
    };

    DeviceInfo.prototype.phoneNumber = function(params, success, fail) {
    return cordova.exec(function(args) {
    success(args);
    }, function(args) {
    fail(args);
    }, 'DeviceInfo', 'phoneNumber', [params]);
    };

    DeviceInfo.prototype.imsi = function(success, fail) {
    return cordova.exec(function(args) {
    success(args);
    }, function(args) {
    fail(args);
    }, 'DeviceInfo', 'imsi', []);
    };

    window.deviceInfo = new DeviceInfo();

    // backwards compatibility
    window.plugins = window.plugins || {};
    window.plugins.deviceInfo = window.deviceInfo;

    })(window.PhoneGap || window.Cordova || window.cordova);

    Here is my Java plugin:

    import org.apache.cordova.CallbackContext;
    import org.apache.cordova.CordovaPlugin;
    import org.apache.cordova.PluginResult;
    import org.json.JSONArray;
    import org.json.JSONException;

    public class DeviceInfo extends CordovaPlugin
    {
    public DeviceInfo()
    {

    }

    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException
    {
    PluginResult.Status status = PluginResult.Status.ERROR;
    String result = "";

    if (action.equals("imeiNumber"))
    {
    status = PluginResult.Status.OK;
    result = this.DeviceImeiNumber();
    }

    callbackContext.sendPluginResult(new PluginResult(status, result));
    return true;
    }

    public String DeviceImeiNumber(){
    //TelephonyManager tManager = (TelephonyManager)cordova.getActivity().getSystemService(Context.TELEPHONY_SERVICE);
    //return tManager.getDeviceId();
    return "Success1";
    }
    }

    and this in my html page, I use this js code to call my plugin:

    function onLoad()
    {
    alert("I've been loaded");
    document.addEventListener("deviceready", onDeviceReady, true);
    }

    function onDeviceReady()
    {
    window.plugins.deviceInfo.imeiNumber(function(imei) {
    if(imei !== "")
    {
    alert("success, IMEI: " + imei);
    }
    else
    {
    alert("Failed :(");
    }
    });

    Am I doing something wrong?

    Many thanks before :)

    ReplyDelete
  41. @Unknown

    Yeah, if you are using a later version of PhoneGap you need to do a require in order to pull in the exec module. Check out:

    https://git-wip-us.apache.org/repos/asf?p=cordova-plugin-camera.git;a=blob;f=www/Camera.js;h=b2da5da95bfe5755cab4ef1029745467ae9140d5;hb=HEAD

    for an example. Also, don't expect cordova.exec to return you anything useful. Use the success and failure callbacks.

    ReplyDelete
  42. I might be a bit thick, but where do we create the class that extends PreferenceActivity from step #4. I'm using Icenium if that makes a difference. I though the purpose of a plugin is to save you from writing native code. Your .java file already contains the native code, so where do I extend PreferenceActivity?

    ReplyDelete
  43. @David Silveria

    Follow the link earlier in the blog post "go read this tutorial" to learn how to setup the Java side.

    ReplyDelete
  44. are there any plans to get this working with the cloud build system ?

    ReplyDelete
  45. I did read the article, but the part I do not get is where to place that code, it is never said in plain text in the article. I can only use native code in the plugins folder, which I thought already contains all needed code for the plugin.

    ReplyDelete
  46. @Classified

    I don't have time to work on it right now but I will gladly take pull request to my repo that provide that support. Someday I will have time to revisit all of my plugins.

    ReplyDelete
  47. @David Silveria

    It is up to you where you put the code. It all depends on what the package name of your app is. For instance if I create the class SimonsPrefs and it is in the package com.simonmacdonald I should put the code in src/com/simonmacdonald/SimonsPrefs.java

    ReplyDelete