Thursday, July 12, 2012

PhoneGap Android Plugins: Sometimes We Screw Up

So we dropped the ball in PhoneGap 1.9.0. We got rid of a number of methods from the CordovaInterface. This was part of the ongoing work to push CordovaWebView into master.

Aside: CordovaWebView allows you to use the PhoneGap/Cordova component in a larger Android application. Extending the DroidGap class is no longer necessary if you are providing your own Activity that embeds a CordovaWebView. However, you can continue to extend the DroidGap class to give yourself a leg up.

As a result of removing those methods Plugins who were dependent on them being implemented by CordovaInterface ctx member of the Plugin class were left wondering what do I need to do to get my Plugin to compile with PhoneGap 1.9.0.

Well we are sorry for that, it could have and should have been handled better. When PhoneGap 2.0.0 drops we are changing ctx from a CordovaInterface to a LegacyContext class. LegacyContext is a new class that we've introduced that bridges the old CordovaInterface API to the new CordovaInterface API. This means that any Plugin that worked in 1.8.1 should continue to work in 2.0.0 without modification.

This doesn't mean that LegacyContext will be around forever. In fact the class is already deprecated. We will be publishing an Plugin upgrade guide to help developers update their Plugins to the new API. I'll also be going through my plugins (ChildBrowser, TTS, VideoPlayer) and updating the repo to have 1.8.1 and 1.9.0 versions of the Plugins for people to reference. The ctx member from the Plugin class will be going away in a couple of point releases as it has been replaced by a cordova member which is an instance of CordovaInterface.

For those of you who want to get a jump start on updating your plugins here is a brief guide.

  • ctx.getContext() replaced with cordova.getContext() 
  • ctx.startActivity() replaced with cordova.getActivity().startActivity() 
  • ctx.getSystemService() replaced with cordova.getActivity().getSystemService() 
  • ctx.getAssets() replaced with cordova.getActivity().getAssets() 
  • ctx.runOnUiThread() replaced with cordova.getActivity().runOnUiThread() 
  • ctx.getApplicationContext() replaced with cordova.getActivity().getApplicationContext()
  • ctx.getPackageManager() replaced with cordova.getActivity().getPackageManager()
  • ctx.getSharedPreferences() replaced with cordova.getActivity().getSharedPreferences()
  • ctx.unregisterActivity() replaced with cordova.getActivity().unregisterActivity()
  • ctx.getResources() replaced with cordova.getActivity().getResources()
  • import com.phonegap.api.* replaced with import org.apache.cordova.api.*


59 comments:

Joan Grau said...

Hi, first post here.

I would like to thank you for phonegap, a easy way to make apps for android with no idea of java, yet...

Is extending DroidGap going to be depecrated?

PD: My english sux ;)

Subrahmanya said...

thanks Simon :) really helpful!

For video player in android only we can play the files from SDcard and Youtube, Not able to play the video file from Assets! May be I have to programmatically move the file to sdcard or any solutions for that?

Simon MacDonald said...

@Joan

No, DroidGap is not being deprecated. The LegacyContext class will be deprecated once we get the Plugins updated to the new API.

Simon MacDonald said...

@Subrahmanya

The latest version of the VideoPlayer copies the file out of assets and onto the SD Card so the video can be player. So, yes you should be able to package a video in the assets folder and have it played by the Plugin.

Unknown said...

Replacing ctx.getContext() with cordova.getContext() still gives deprecated warning - "The method getContext() from the type CordovaInterface is deprecated". Any suggestions? Thank you.

Simon MacDonald said...

@Darrin

Don't worry about that one. We put getContext back in the interface but the deprecation log needs to get removed.

Subrahmanya said...

Thanks Simon :) I tried with updated VideoPlay.java got some Error !!!
More Info code: http://paste.org/51757
Full code: http://goo.gl/Zf4DV
droidRuntime(8734): FATAL EXCEPTION: Thread-21
07-16 06:30:36.993: E/AndroidRuntime(8734): java.lang.StackOverflowError
07-16 06:30:36.993: E/AndroidRuntime(8734): at android.util.Log.d(Log.java:137)
07-16 06:30:36.993: E/AndroidRuntime(8734): at org.apache.cordova.api.LOG.d(LOG.java:91)
07-16 06:30:36.993: E/AndroidRuntime(8734): at org.apache.cordova.DroidGap.getContext(DroidGap.java:943)
07-16 06:30:36.993: E/AndroidRuntime(8734): at org.apache.cordova.DroidGap.getContext(DroidGap.java:944)
07-16 06:30:36.993: E/AndroidRuntime(8734): at org.apache.cordova.DroidGap.getContext(DroidGap.java:944)

Simon MacDonald said...

@Subrahmanya

Yup, that is that is a bug in 1.9.0 that has been fixed in 2.0.0. Use getActivity in the mean time.

Subrahmanya said...

Hi Simon :)Error While running new android_asset VideoPlayer.java (updated to Phonegap 1.9) sample with android_asset videoplay!!
More Info code: http://paste.org/51757
Full code: http://goo.gl/Zf4DV

droidRuntime(8734): FATAL EXCEPTION: Thread-21
07-16 06:30:36.993: E/AndroidRuntime(8734): java.lang.StackOverflowError
07-16 06:30:36.993: E/AndroidRuntime(8734): at android.util.Log.d(Log.java:137)
07-16 06:30:36.993: E/AndroidRuntime(8734): at org.apache.cordova.api.LOG.d(LOG.java:91)
07-16 06:30:36.993: E/AndroidRuntime(8734): at org.apache.cordova.DroidGap.getContext(DroidGap.java:943)
07-16 06:30:36.993: E/AndroidRuntime(8734): at org.apache.cordova.DroidGap.getContext(DroidGap.java:944)
07-16 06:30:36.993: E/AndroidRuntime(8734): at org.apache.cordova.DroidGap.getContext(DroidGap.java:944)

Subrahmanya said...

K thanks

Unknown said...

Simon,
I tried to use ctx.getContext() and ignoring the deprecated warning but I get the following error:

at org.apache.cordova.DroidGap.getContext(DroidGap.java:944)

Simon MacDonald said...

@Subrahmanya and @Darren

Yes, that stack overflow exception is being caused by a circular reference in 1.9.0. Honestly, 2.0.0 RC1 is out now and 2.0.0 is out Friday and it'll be much better than trying to patch 1.9.0 right now.

Unknown said...

Okay, I will try RC1 and see what happens. Thank you!

Unknown said...

Hi Simon, I am now using 2.0.0 RC1 and I get the following error when I call window.plugins.childBrowser.showWebPage():


07-16 12:08:08.267: I/Web Console(11555): Error: Status=2 Message=Class not found at file:///android_asset/www/cordova-2.0.0rc1.js:994

Unknown said...

Simon,
I determined why I was getting the "Class not found at file..." error. It was because I named my childBrowser plugin with "cordova" instead of "phonegap". I thought I would need to change all name references to cordova but this was not the case here. Therefore, in my plugins.xml file I now have this and it works.

Unknown said...

phonegap 2.0.0 does not work with Android Barcodescanner plugin. Generates an error "barcodescanner module not found in cordova 2.0.0.

Any ideas? Worked on previous versions of IOS.

George said...

Simon,

Are you going to update the BarcodeScanner plugin too? I came over from your old post:

http://simonmacdonald.blogspot.ca/2011/12/installing-barcode-plugin-for-phonegap.html

Thanks,

George

Simon MacDonald said...

@George

My plan is to give all the plugins a look see on 2.0.0 as soon as possible. I can't think of a reason why it wouldn't work but I may have missed a backwards compatibility point on that plugin.

Simon MacDonald said...

@Howard

I'm confused are you talking about the Android Barcode Scanner or the iOS one? You mention both.

Unknown said...

I am talking about the Android Barcode Scanner plugin. I thought it waas important that you know that I have this code running on older versions on IOS. Sothat it is clear the problem is encountered on Android using 2.0.0. I can not use 1.9.0 because of another problem. Thanks!

Simon MacDonald said...

@Howard

Like I told George in an earlier comment I hope to get a chance to look at it this week.

Unknown said...

Thanks!

I would appredciate it if you let me kow when you have something that works on 2.0.0.

I aalso use the Barcodescanner and have similar problems which are probably related.

Thanks!

Ashwini said...

Hi Simon,

I am new to Android and I want to get childBrowser in my app.In my eclipse I have Android4.0.3 vesion.I installed MDS Applaud to the eclipse and followed the link http://www.youtube.com/watch?v=84jmuXS8GJI but the proble i when I run the project I am getting the error ctx cannot be resolved..Please help me simon to resolve this..
Thank you and regards
Ashwini

broody said...

Hi,

So with the switch to 2.0.0 from the CordovaCtx to LegacyContext did we lose the ability to start services in Plugins? Legacy ctx does not have the startService() or bindService() calls available...

Thanks.

Simon MacDonald said...

@broody

Crap, no that is missing from the interface. I can add it for 2.1.0 but in the meantime you'll need to use cordova.getActivity().startService/bindService.

Simon MacDonald said...

@Ashwini

Take a look at some of my other posts which include an updated ChildBrowser plugin for 2.0.0.

Mohammed Irfan Shirur said...

Hi Simon,

We have implemented the Phone Gap Android Push Notification using GCM, and referred from https://github.com/marknutter/GCM-Cordova
with Phone Gap 1.7.0 version. Now if I want to port it to 1.9.0 getting this below error, I read your blog where you mentioned that we need to change ctx. to cordova. but still the same error comes, Is the Phone gap 1.9.0 is stable version ?

What all modification we have to do for working in 1.9.0.

Error : “FATAL EXCEPTION : Thread-19, at org.apache.cordova.DroidGap.getContext(DroidGap.java)”

Warm Regards
MohammedIrfan

Simon MacDonald said...

@Zhakaasssss

Switch to getActivity. There is a bug in 1.9.0 that getContext will cause a stack overflow error.

Mohammed Sirajuddin said...

Hi, i am newbie to phonegap I am working on Plugins for android i was trying with the localNotification plugin...


Its having a class Alarm and with constructor
public AlarmHelper(Context context) {
this.ctx = context;
}

now from i am creating an object for the class and passing LegaacyContext of Cordova as an argument but the constructor of Alarm is having Context of Android API

alarm = new AlarmHelper(this.ctx);


Simon MacDonald said...

@MD SIRAJUDDIN NOORUDDIN

What's the question?

Mohammed Sirajuddin said...

Hi Simon Mac Donald,

my question is how to get the plugin result into our app web components such as Html ?

Simon MacDonald said...

@MD SIRAJUDDIN NOORUDDIN

You are not providing enough info. Regardless, check out the plugin development guide:

http://docs.phonegap.com/en/2.0.0/guide_plugin-development_index.md.html#Plugin%20Development%20Guide

Tarak said...

In phonegap 2.0 how to display a wait cursor for java file of my plugin.Previously i was doing that...
droidGap.spinnerStart("","Loading");.
But i think they have remove this method.

Simon MacDonald said...

@Disasters

Well that method is still there and it is public so I'm not sure why it wouldn't work for you as long as you have a reference to the DroidGap class. You could switch to using the JavaScript alternatives:

navigator.notification.activityStart(title, message);
navigator.notification.activityStop();

Unknown said...

I also noticed that ctx.getContentResolver() should be changed to cordova.getActivity().getContentResolver()

guitar99 said...

Android 4.0.3
Zebracrossing 2.1
Cordova 2.0.0

In BarcodeScanner I use Zxing
IntentIntegrator tintegrator = new IntentIntegrator(tempact);
Which works great but I have to pass in the Activity so that
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Method will execute on return from Zxing scanning
Problem is that BarcodeScanner is a Java class extends Plugin but it's not an Activity. I want the Zxing IntentIntegrator to come back to BarcodeScanner Class so that I can execute the Encode and this.success just like is in the Plugin Class. When I put in my MainActivity to IntentIntegrator, it works to come back there in onActivityResutls but can't get back to Plugin to execute success.

Simon MacDonald said...

@guitar99

You need to use the following code from your Plugin to get an activity:

this.cordova.getActivity();

but you can see how to call an activity from a plugin using:

this.cordova.startActivityForResult();

in:

https://github.com/phonegap/phonegap-plugins/blob/master/Android/BarcodeScanner/2.0.0/src/com/phonegap/plugins/barcodescanner/BarcodeScanner.java

reineskye25 said...

I tried the barcode demo app and it is not working. this is the error "Cannot read property 'barcodeScanner' of undefined"

Its working on samsung galaxy y running android gingerbread but not working in samsung galaxy tab running android froyo.

What should I do to make it work in android froyo?

Simon MacDonald said...

@reineskye25

I don't have a 2.2 device but it is working in the froyo emulator.

Unknown said...

Hi Simon :)

I am new to phonegap development. I m trying to implement a video module using cordova 2.1.0.. Here i can hear the sound from my video but not able to view it. Can u plss help me out.


here is the code which i m using..





var video = document.getElementById('example_video_1'); video.addEventListener('click',function(){
video.play(); },false);

Thanks in advance :)

Simon MacDonald said...

@vinutha v

What version of Android are you using? There are many problems with the video tag on Android. Did you read my many posts on the subject on this blog? Also, if you hear the audio but not the video you are probably trying to play an unsupported video type. See this table:

https://developer.android.com/guide/appendix/media-formats.html#core

Unknown said...

hi Simon,
In phonegap local notification plugin for android cancelAll is not working.
https://github.com/phonegap/phonegap-plugins/tree/master/Android/LocalNotification

i am using cordovo 2.1
any idea..??

Simon MacDonald said...

@Amit Kumar

Sorry, I haven't used it in awhile. You should ask the plugin owner/originator.

Unknown said...

Hi Simon, Thanks a lot for your very useful Tutorials and great efforts.

I'm using the Android Local Notifaction plugin now, but I have a problem with this line:

public PluginResult execute(String action, JSONArray optionsArr, CallbackContext callBackId) {

I can't change it to public boolean execute(String action, JSONArray data, CallbackContext callbackContext) {

as it shows an error with return type If I did that, so is there a solution ?

Vasu Nanguluri said...

Hi Simon, I need your help on get phone number from Andorid PhoneGap, I have tried some options on your blog, but no luck, Plz find sending code and Error,

Let me know your Suggestions,

Thanks in Advance.

==========================
This is my CODE
==========================

package com.vasu.sms;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.telephony.TelephonyManager;
import android.util.Log;

import org.apache.cordova.api.*;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.apache.cordova.api.PluginResult.Status;

/**
* @author Guy Vider
*
*/
public class MyPhoneNumberPlugin extends Plugin {

@Override
public PluginResult execute(String action, JSONArray data, String callbackId) {
Log.d("MyPhoneNumberPlugin", "Plugin called");
PluginResult result = null;
try {
JSONObject number = getMyPhoneNumber();
Log.d("MyPhoneNumberPlugin", "Returning "+ number.toString());
result = new PluginResult(Status.OK, number);
}
catch (Exception ex) {
Log.d("MyPhoneNumberPlugin", "Got Exception "+ ex.getMessage());
result = new PluginResult(Status.ERROR);
}
return result;
}

private JSONObject getMyPhoneNumber() throws JSONException {
Log.d("MyPhoneNumberPlugin", "at getMyPhoneNumber");
JSONObject result = new JSONObject();
TelephonyManager tm = (TelephonyManager) cordova.getSystemService(Context.TELEPHONY_SERVICE);
String number = tm.getLine1Number();
if(number.equals("") || number == null) {
Log.d("MyPhoneNumberPlugin", "We're on a non-phone device. Returning a hash of the UDID");
number = md5(tm.getDeviceId()).substring(0, 10);
}
Log.d("MyPhoneNumberPlugin", "Phone number=" + number);
result.put("phoneNumber", number);
return result;
}

private String md5(String s) {
try {
// Create MD5 Hash
MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
digest.update(s.getBytes());
byte messageDigest[] = digest.digest();

// Create Hex String
StringBuffer hexString = new StringBuffer();
for (int i=0; i<messageDigest.length; i++)
hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
return hexString.toString();

} catch (NoSuchAlgorithmException e) {
Log.d("MyPhoneNumberPlugin", e.getMessage());
}
return "";
}
}

======================
This is Error
======================

The method getSystemService(String) is undefined for the type CordovaInterface

Simon MacDonald said...

@Rana Mahdy

It is not sufficient to just change the method signature. You need to go through and replace everywhere you return a PluginResult with a call to sendPluginResult. For example:

callbackContext.sendPluginResult(new PluginResult(status, result));

Then you need to return a boolean variable that in most cases should be true to say that the action was valid.

Simon MacDonald said...

@Vasu Nanguluri

Lots of people seem to be asking for this functionality so I'm going to do a full blog post on it.

To answer your question, change:

TelephonyManager tm = (TelephonyManager) cordova.getSystemService(Context.TELEPHONY_SERVICE);

to:

TelephonyManager tm = (TelephonyManager) cordova.getActivity().getSystemService(Context.TELEPHONY_SERVICE);

Pozz said...

Hi, i'm trying to install the LibraryProject of Barcode Plugin for PhoneGap Android.

eclipse give an error in the BeepManager.java file

"beep cannot be resolved or is not a field" line 97 Java Problem

how to solve it?

Thanks

detoxnet said...

Hi. I wrote a simple plugin to call a function in my MainActivity. But it never gets called. I use this to do it from my plugin.java file

MainActivity ma = (MainActivity)this.cordova.getActivity();
ma.customFunctionCalled();

Is that the right way to get a handle on the MainActivity objext? I debugged it and it doesnt get past the first line above. Using cordova 2.1.

Simon MacDonald said...

@detoxnet

There is no reason why that shouldn't work. I just did a quick test with 2.3.0rc2 and it worked great for me.

Simon MacDonald said...

@Pozz

I haven't ever seen that error and there is no field called "beep" in BeepManager.java so I'm having a hard time figuring out where it could be coming from.

Unknown said...

Hi simon ,
I am using 2.4.0 , trying to implement the PushPlugin via Eclipse for Andriod. When I build the project I get the following error :

The method getContext() is undefined for the type CordovaInterfac PushPlugin.java

Any suggestion would be very much appriciated

Simon MacDonald said...

@Lori Azrad

The API has changed somewhat. I think you are looking for:

this.cordova.getActivity().getApplicationContext()

Unknown said...

Hi Simon,

I am creating a share plugin with the help of this url:
https://github.com/libbybaldwin/phonegap-plugins/tree/master/Android/ChildBrowser

I am using phonegap 2.9 version and have made changes according to that in ChildBrowser.java file however still getting errorslike one of in this line
intent = new Intent().setClass(this.ctx, org.apache.cordova.DroidGap.class);

Error: ctx cannot be resolved or is not a field

What should I use to replace this.ctx .

Please reply soon, thanks in advance.

Simon MacDonald said...

@Nitin Ajmani

It's right there in the post.

this.ctx becomes this.cordova or this.cordova.getContext() depending on what you need.

Unknown said...

Hi Simon,

I have implemented this.cordova and this.cordova.getContext() in place of this.ctx however it is not working.

I am a beginner to Android Applications and I want to implement Share button functionality to my Phonegap Android App however I am not successful in many attempts.

I am using Phonegap 2.9.0 and Android Developer Tools version Build: v21.1.0-569685 .

Can you please help me to add share button functionality for Facebook, Twitter, Gmail, SMS etc using phonegap.

Your help is really appreciated.

Thank You.

Simon MacDonald said...

@Nitin Ajmani

Check out this blog post:

http://teusink.blogspot.com/2013/04/phonegap-android-share-plugin.html

Unknown said...

Hello Simon,
I started using PhoneGap 3 days back and I am so liking it. I am planning to integrate my PhoneGap app with Databse.com and luckily they have some very nice blogs out on there on the web for the same.
I am using cordova 1.7.0 and as I got childBrowser.java in my source folder, I get several errors reading 'cordova cannot be resolved or is not a
field'. Wondering why would this happen and no substantial posts for these on the web. Please help

Simon MacDonald said...

@supriya hirurkar

Make sure you are using the right version of ChildBrowser.java with your version of PhoneGap. We broke the plugin api along the way so different versions of the ChildBrowser were needed.