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:

  1. 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 ;)

    ReplyDelete
  2. 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?

    ReplyDelete
  3. @Joan

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

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

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

    ReplyDelete
  6. @Darrin

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

    ReplyDelete
  7. 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)

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

    ReplyDelete
  9. 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)

    ReplyDelete
  10. 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)

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

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

    ReplyDelete
  13. 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

    ReplyDelete
  14. 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.

    ReplyDelete
  15. 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.

    ReplyDelete
  16. 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

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

    ReplyDelete
  18. @Howard

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

    ReplyDelete
  19. 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!

    ReplyDelete
  20. @Howard

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

    ReplyDelete
  21. 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!

    ReplyDelete
  22. 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

    ReplyDelete
  23. 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.

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

    ReplyDelete
  25. @Ashwini

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

    ReplyDelete
  26. 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

    ReplyDelete
  27. @Zhakaasssss

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

    ReplyDelete
  28. 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);


    ReplyDelete
  29. @MD SIRAJUDDIN NOORUDDIN

    What's the question?

    ReplyDelete
  30. Hi Simon Mac Donald,

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

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

    ReplyDelete
  32. 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.

    ReplyDelete
  33. @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();

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

    ReplyDelete
  35. 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.

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

    ReplyDelete
  37. 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?

    ReplyDelete
  38. @reineskye25

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

    ReplyDelete
  39. 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 :)

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

    ReplyDelete
  41. 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..??

    ReplyDelete
  42. @Amit Kumar

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

    ReplyDelete
  43. 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 ?

    ReplyDelete
  44. 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

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

    ReplyDelete
  46. @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);

    ReplyDelete
  47. 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

    ReplyDelete
  48. 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.

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

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

    ReplyDelete
  51. 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

    ReplyDelete
  52. @Lori Azrad

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

    this.cordova.getActivity().getApplicationContext()

    ReplyDelete
  53. 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.

    ReplyDelete
  54. @Nitin Ajmani

    It's right there in the post.

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

    ReplyDelete
  55. 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.

    ReplyDelete
  56. @Nitin Ajmani

    Check out this blog post:

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

    ReplyDelete
  57. 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

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

    ReplyDelete