Friday, April 20, 2012

Migrating your PhoneGap Plugins to Version 1.5+

One of the pain points when we had to rename PhoneGap to Apache Cordova was that it kinda/sorta broke the plugins. Wherever possible we tried to put a shim in to keep plugins from breaking but some restructuring of the code made it impossible for this transition to be totally seamless.

So let's take a look at a new plugin I just created that gets you the IMEI of your Android phone. First here is the 1.4.1 version of the code:

and now the 1.5+ version:
As you can see the code is very different. Really the only changes are that PhoneGap has been replaced with cordova. There is actually a shim in place so that you can still use the PhoneGap object but that is going away for the 2.0 release so we might as well get on that train now. As well the addConstructor/addPlugin methods won't really be needed come 2.0 but lets leave them in for now.

Alright, so the JavaScript code wasn't much different so let's dive into the Java code now. First the 1.4.1 version:

and now the 1.5+ version: again not too many changes. Simply changing the imports from:
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
to:
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
This shouldn't be necessary for your plugin as I added code in the cordova.jar file so that every class in the org.apache.cordova.api package has a sub-class in com.phonegap.api. Once again this will be going away in the 2.0 release so we should go ahead and make the change now. Easy right?

Well kinda, but I'm being a bit disingenuous as my example does not address a key change between versions 1.4.1 and 1.5.0. In PhoneGap 1.4.1 the member variable this.ctx was of type PhonegapActivity. If you walked up PhonegapActivity's inheritance chain you'd see that android.content.Context is one of it's super classes. This was particularly useful in a number of Plugins.

In Cordova 1.5+ the member variable this.ctx is a CordavaInterface. So plugins that are passing this.ctx into methods that are expecting a Context will complain. The fix for this in your Java code is to replace:
this.ctx
with:
this.ctx.getContext()
or
this.ctx.getIntent()
with:
((DroidGap)this.ctx).getIntent()
wherever required. These changes were predicated by refactoring of the code in order to enable Cordova to be an embeddable component. That is, at some point in the future you will be able to create an Android application that can embed the Cordova component. This will allow you to mix native and hybrid development more easily.

Update 2012/04/23: Paul Beusterien pointed out that I forgot to mention a couple of steps to get the IMEI plugin working. First add the following line to res/xml/plugins.xml:
<plugin name="imei" value="com.simonmacdonald.imei.IMEIPlugin"/>
and make sure you have the proper permissions setup in your AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

28 comments:

  1. Does it works on iPhone also? Can we get IMEI from iOS devices? what all other device information we can get (like phonenumber)?

    ReplyDelete
  2. @R Ramana

    No, this plugin will not work on iOS. Android plugins are written in Java which does not run on iOS. You'd need to re-implement this in Objective-C. Check out this StackOverflow answer:

    http://stackoverflow.com/questions/823181/how-to-get-imei-on-iphone

    ReplyDelete
  3. hi how to find mobile number using phonegap..in android please help me..

    ReplyDelete
  4. @GreaterJun

    You would do:

    TelephonyManager tMgr =(TelephonyManager)mAppContext.getSystemService(Context.TELEPHONY_SERVICE);
    mPhoneNumber = tMgr.getLine1Number();

    but there is no guarantee that the SIM will return the number. A lot of times you'll get null.

    ReplyDelete
  5. Hi Simon,

    I am able to install condova application on my android phone to access GPS, camera etc. But i am not able to get the IMEI plugin to work.

    This is what i am using:
    1) Android version 2.3.3
    2) Cordova 18
    3) IMEI Plugin version 1.5
    4) I added the plugin name to the plugin xml.
    5) I added the script inclusion to index.html.

    On calling the function window.plugins.imei.get it always goes into the failure function.
    Please help.

    Thanks
    Jaskaran

    ReplyDelete
  6. @Jaskaran

    Did you add read phone state permission to your manifest.xml? That is the only thing you didn't mention.

    ReplyDelete
  7. Hi Simon,

    Thanks for replying.
    I added the below permission to the manifest:


    thanks
    jaskaran

    ReplyDelete
  8. @karan

    Sorry the XML did not come across in the comment.

    ReplyDelete
  9. Hi, Sorry about that.
    I gave this permissiong(not including the angular brackets):

    uses-permission android:name="android.permission.READ_PHONE_STATE"

    thanks
    karan

    ReplyDelete
  10. Hi Simon,

    I needed some more help :-)
    I needed to embedd native controls along with the html(index.html) for android.

    Is there any wau to do this?

    Thanks alot
    karan

    ReplyDelete
  11. @karan

    By native controls do you mean the menu?

    ReplyDelete
  12. Hi Simon,

    Yes native controls like:

    1) Menu
    2) Custom menu
    3) Toolbar etc

    I need to do this in Android.

    I really appreaciate your help and time.
    Thanks
    karan

    ReplyDelete
  13. @karan

    You can use the native implementation which is detailed here:

    https://developer.android.com/guide/topics/ui/menus.html

    and when you want to call from Java to JavaScript you do a:

    this.sendJavascript(myJScode);

    ReplyDelete
  14. Hi Simon,

    This is GREAT. Thank you very much! But needed to ask something....
    I have been able to make a mixed UI from the Activity Class itself. Half in native controls and half in Phonegap html. My code is :

    public class Jaskaran823Activity extends DroidGap {
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);


    View header = View.inflate(getContext(), R.layout.main, null);
    root.addView(header);
    super.loadUrl("file:///android_asset/www/index.html");
    this.sendJavascript("javascript:yourFunction('load')");

    super.appView.getSettings().setJavaScriptEnabled(true);
    super.appView.addJavascriptInterface(this, "MyCls");
    }

    }


    I am now able to communicate both ways from java to javascript and vice versa.

    Now can i do the same using phonegap plugins? I mean can i put the native controls in a plugin? And embedd native-UI inside the phone-gap html from within the plugins?
    Something like this seems to be done for iPhone. But i need to do this in android...
    Is there any exmaple of puttinh native UI from within a plugin in android?

    Please help.
    Thanks
    Karan

    ReplyDelete
  15. @karan

    I would avoid using addJavascriptInterface() as there are some problems with that code working on all versions of Android. You should switch to using a PhoneGap Plugin which works around these issues if they exist.

    So use a Plugin to communicate from JavaScript to Java and return a Plugin result or alternatively you can use sendJavaScript. That is what I do when I implement a native menu.

    You can check out Joe's new project which shows how to embed a CordovaWebView in an Android project:

    https://github.com/infil00p/CordovaActionView

    or look at Michael Brooks menu plugin:

    https://github.com/mwbrooks/cordova-plugin-menu

    ReplyDelete
  16. Thanks alot Simon.
    This is super.
    You have been extremely helpful...

    ReplyDelete
  17. Hi,Simon. I am migrating the phonegap plugin to cordova 2.0. Could you tell me how to modify this line:
    ((DroidGap)this.ctx).getIntent() to cordova 2.0?
    Thanks in advance.

    ReplyDelete
  18. @Wei Zhang

    That would be "cordova.getActivity().getIntent()"

    ReplyDelete
  19. Hi guyz. pls i need to apply the "getting android IMEI" stuff on my work. But this is the issue:
    It's an html/css/js code, using cordova to compile it into an android app via eclipse.
    The Question:
    How do I put in the codes; where exaclty and if possible can i have verbatim code?

    ReplyDelete
  20. @Yinka Adediji

    What about the instructions don't you understand?

    ReplyDelete
  21. with cordova 2.0.0,
    how is "File fp = new File(this.cordova.getActivity().getFilesDir() + "/" + filename);"

    ReplyDelete
  22. @supriya

    Sorry, I don't understand your question.

    ReplyDelete
  23. Hi simon

    in all the android twitter OAUth applications they are using On resume method get the verifier for the Authorizing the Application .

    similarly for Phonegap if i use :

    cordova.getActivity().startActivity(
    new Intent(Intent.ACTION_VIEW, Uri.parse(requestToken
    .getAuthenticationURL())));

    for starting the intent . i am not getting the intent URI as a result in OnResume method of the PluginResult.

    but i am getting a call to the Onresume method after the Application authentication .

    Thanks and regards

    Sangeeth Kumar V

    ReplyDelete
  24. @sangeeth_LVS

    I believe you want to start your activity for result. You are just doing a startActivity. So I'd do:

    this.cordova.startActivityForResult((Plugin)this,
    new Intent(Intent.ACTION_VIEW, Uri.parse(requestToken
    .getAuthenticationURL())));

    and implement the onActivityResult method in your Plugin.

    ReplyDelete
  25. With Cordova 2.2 the class Plugin is deprecated, one should use CordovaPlugin from now on.

    Since I have problems getting this to run I would appreciate an update to the blog showing how it's done with Cordova 2.1/2.2.

    Thanks,
    Juergen

    ReplyDelete
  26. @Jürgen Wahlmann

    The Plugin class will continue to work for quite some time. I do like your suggestion and I will make it a future blog post so stay tuned.

    ReplyDelete
  27. Hi, Does it work with Phonegap 3.0+?

    ReplyDelete
  28. @cureorcurse

    The same Android code will work but it would need to be updated to 3.0.0 style plugin.

    ReplyDelete