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:

R Ramana said...

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

Simon MacDonald said...

@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

GreatarJun said...

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

Simon MacDonald said...

@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.

karan said...

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

Simon MacDonald said...

@Jaskaran

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

karan said...

Hi Simon,

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


thanks
jaskaran

Simon MacDonald said...

@karan

Sorry the XML did not come across in the comment.

karan said...

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

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

thanks
karan

karan said...

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

Simon MacDonald said...

@karan

By native controls do you mean the menu?

karan said...

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

Simon MacDonald said...

@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);

karan said...

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

Simon MacDonald said...

@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

karan said...

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

Wei Zhang said...

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.

Simon MacDonald said...

@Wei Zhang

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

Yinka Adediji said...

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?

Simon MacDonald said...

@Yinka Adediji

What about the instructions don't you understand?

supriya said...

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

Simon MacDonald said...

@supriya

Sorry, I don't understand your question.

sangeeth_LVS said...

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

Simon MacDonald said...

@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.

Jürgen Wahlmann said...

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

Simon MacDonald said...

@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.

cureorcurse said...

Hi, Does it work with Phonegap 3.0+?

Simon MacDonald said...

@cureorcurse

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