Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-24608] Android: respackage value in native module's manifest is ignored

GitHub Issuen/a
TypeBug
PriorityCritical
StatusClosed
ResolutionFixed
Resolution Date2017-06-19T20:30:09.000+0000
Affected Version/sRelease 6.0.3
Fix Version/sRelease 6.1.1
ComponentsAndroid
Labelsn/a
Reporter Ricardo Ramirez
AssigneeChristopher Williams
Created2017-04-20T18:08:43.000+0000
Updated2017-10-09T14:02:18.000+0000

Description

Hi ! We are attempting to upgrade an Android module to SDK 6.0. Here is our original source code:
/**
 * This file was auto-generated by the Titanium Module SDK helper for Android
 * Appcelerator Titanium Mobile
 * Copyright (c) 2009-2010 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 *
 */

package ias.cropper;

import java.io.File;

import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollFunction;
import org.appcelerator.kroll.KrollModule;
import org.appcelerator.kroll.annotations.Kroll;
import org.appcelerator.kroll.common.TiConfig;
import org.appcelerator.titanium.TiApplication;
import org.appcelerator.titanium.TiBlob;
import org.appcelerator.titanium.TiC;
import org.appcelerator.titanium.util.TiActivityResultHandler;
import org.appcelerator.titanium.util.TiActivitySupport;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.Toast;

import com.soundcloud.android.crop.Crop;
import com.soundcloud.android.crop.CropImageActivity;


@Kroll.module(name="Androidcropper", id="ias.cropper")
public class AndroidcropperModule extends KrollModule implements TiActivityResultHandler
{

	// Standard Debugging variables
		private static final String LCAT = "AndroidcropperModule";
		private static final boolean DBG = TiConfig.LOGD;

		private KrollFunction successCallback;
		private KrollFunction cancelCallback;
		private KrollFunction errorCallback;
		
		

		public AndroidcropperModule()
		{
			super();
		}

		@Kroll.onAppCreate
		public static void onAppCreate(TiApplication app)
		{
			Log.d(LCAT, "inside onAppCreate");
			// put module init code that needs to run when the application is created
		}

		public void onResult(Activity activity, int requestCode, int resultCode, Intent result) {
	        Log.i(LCAT, "onResult Called");
			if (requestCode == Crop.REQUEST_CROP) {
				if (resultCode == Activity.RESULT_OK) {
	                Bitmap bitmap = null;
	                try {
	                    bitmap = MediaStore.Images.Media.getBitmap(activity.getContentResolver(), Crop.getOutput(result));
	                } catch (Exception e) {}

					KrollDict event = new KrollDict();
					event.put("image", TiBlob.blobFromImage(bitmap));
	                event.put(TiC.EVENT_PROPERTY_SOURCE, this);
					successCallback.callAsync(getKrollObject(), event);

				} else if (resultCode == Crop.RESULT_ERROR) {
					Toast.makeText(activity, Crop.getError(result).getMessage(), Toast.LENGTH_SHORT).show();

	                if (errorCallback == null) return;
	                KrollDict event = new KrollDict();
	                event.put(TiC.EVENT_PROPERTY_ERROR, Crop.getError(result).getMessage());
	                event.put(TiC.EVENT_PROPERTY_SOURCE, this);
	                errorCallback.callAsync(getKrollObject(), event);
				}
			}
		}

	    public void onError(Activity activity, int requestCode, Exception e)
	    {
	        Log.i(LCAT, "onError Called");
	        if (errorCallback == null) return;
	        KrollDict event = new KrollDict();
	        event.put(TiC.EVENT_PROPERTY_ERROR, e.getMessage());
	        event.put(TiC.EVENT_PROPERTY_SOURCE, this);
	        errorCallback.callAsync(getKrollObject(), event);
	    }

		// Methods
		@Kroll.method
		public void open(KrollDict options) {
			 Activity activity = getTiContext().getTiApp().getCurrentActivity();
	                 TiActivitySupport support = (TiActivitySupport) activity;

			successCallback = getCallback(options, "success");
			cancelCallback = getCallback(options, "cancel");
			errorCallback = getCallback(options, "error");

			TiBlob originalImage = getImageData(options, "image");

			Uri source = Uri.parse(originalImage.getNativePath());
			Uri destination = Uri.fromFile(new File(activity.getCacheDir(), "cropped"));

	        Intent cropIntent = new Intent();

	        cropIntent.setData(source);
	        cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, destination);
	        //cropIntent.putExtra("aspect_x", 1);
	        //cropIntent.putExtra("aspect_y", 1);
	        
	        cropIntent.putExtra("default_no_crop", false);
	        

	        if (options.containsKeyAndNotNull("size")) {
	            Log.i(LCAT, "Setting size to:" + options.getInt("size").toString());
	            cropIntent.putExtra("max_x", options.getInt("size"));
	            cropIntent.putExtra("max_y", options.getInt("size"));
	        }

	        cropIntent.setClass(activity, CropImageActivity.class);

	        support.launchActivityForResult(cropIntent, Crop.REQUEST_CROP, this);
		}

		private KrollFunction getCallback(final KrollDict options, final String name){
			if (options.containsKey(name)) {
				return (KrollFunction) options.get(name);
			} else {
				Log.e(LCAT, "Callback not found:" + name);
				return null;
			}
		}

		private TiBlob getImageData(final KrollDict options, final String name){
			if (options.containsKey(name)) {
				return (TiBlob) options.get(name);
			} else {
				Log.e(LCAT, "Image not found: " + name);
				return null;
			}
		}
		
}

We changed the following line:
      Activity activity = getTiContext().getTiApp().getCurrentActivity();
to any one of the following substitutes:
     Activity activity = getActivity(); or
     Activity activity = TiApplication.getInstance().getCurrentActivity(); or 
    Activity activity = TiApplication.getAppRootOrCurrentActivity(); or 
    Activity activity = getActivityProxy().getActivity();
None of which worked and in every case we got the following exception:
[ERROR] :  TiApplication: (main) [8919,68592] Sending event: exception on thread: main msg:java.lang.RuntimeException: Unable to start activity ComponentInfo{com.ias.dispatchM/com.soundcloud.android.crop.CropImageActivity}: java.lang.NullPointerException: Attempt to write to field 'android.content.Context com.soundcloud.android.crop.CropImageView.context' on a null object reference; Titanium 6.0.2,2017/02/24 11:22,undefined
[ERROR] :  TiApplication: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.ias.dispatchM/com.soundcloud.android.crop.CropImageActivity}: java.lang.NullPointerException: Attempt to write to field 'android.content.Context com.soundcloud.android.crop.CropImageView.context' on a null object reference
[ERROR] :  TiApplication:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
[ERROR] :  TiApplication:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
[ERROR] :  TiApplication:     at android.app.ActivityThread.-wrap12(ActivityThread.java)
[ERROR] :  TiApplication:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
[ERROR] :  TiApplication:     at android.os.Handler.dispatchMessage(Handler.java:102)
[ERROR] :  TiApplication:     at android.os.Looper.loop(Looper.java:154)
[ERROR] :  TiApplication:     at android.app.ActivityThread.main(ActivityThread.java:6119)
[ERROR] :  TiApplication:     at java.lang.reflect.Method.invoke(Native Method)
[ERROR] :  TiApplication:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
[ERROR] :  TiApplication:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
[ERROR] :  TiApplication: Caused by: java.lang.NullPointerException: Attempt to write to field 'android.content.Context com.soundcloud.android.crop.CropImageView.context' on a null object reference
[ERROR] :  TiApplication:     at com.soundcloud.android.crop.CropImageActivity.setupViews(CropImageActivity.java:97)
[ERROR] :  TiApplication:     at com.soundcloud.android.crop.CropImageActivity.onCreate(CropImageActivity.java:75)
[ERROR] :  TiApplication:     at android.app.Activity.performCreate(Activity.java:6679)
[ERROR] :  TiApplication:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
[ERROR] :  TiApplication:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2618)
[ERROR] :  TiApplication:     ... 9 more

Attachments

FileDateSize
cropper.jar2017-05-16T19:43:15.000+000044898
Screen Shot 2017-06-08 at 11.59.15 AM.png2017-06-12T18:15:36.000+000072769
Screen Shot 2017-06-08 at 12.00.47 PM.png2017-06-12T18:15:50.000+0000108529

Comments

  1. Christopher Williams 2017-04-26

    It would be useful to have the actual module source to be able to get an idea of what's going on and try to reproduce this issue, but from the logs you posted it's complaining about this code here:
       [ERROR] :  TiApplication: Caused by: java.lang.NullPointerException: Attempt to write to field 'android.content.Context com.soundcloud.android.crop.CropImageView.context' on a null object reference
       [ERROR] :  TiApplication:     at com.soundcloud.android.crop.CropImageActivity.setupViews(CropImageActivity.java:97)
       [ERROR] :  TiApplication:     at com.soundcloud.android.crop.CropImageActivity.onCreate(CropImageActivity.java:75)
       [ERROR] :  TiApplication:     at android.app.Activity.performCreate(Activity.java:6679)
       
    which I found at: https://github.com/jdamcd/android-crop/blob/master/lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java#L96 Basically the library you're wrapping is expecting to get a CropImageView instance from the R.id.crop_image id, but is apparently getting a null pointer (meaning it can't find a view with that id). I see that defined in https://github.com/jdamcd/android-crop/blob/master/lib/src/main/res/layout/crop__activity_crop.xml So I'm not certain what exactly is the issue here. How are you wrapping this third-party android-crop library? Are the resources (like the xml files I'm linking to) getting included?
  2. Ricardo Ramirez 2017-04-26

    Ok let me check with the customer if they can share the module and if they can provide more details based on your response.
  3. Ricardo Ramirez 2017-05-01

    Hi [~cwilliams] ! PFA the module project here: https://drive.google.com/file/d/0BwTWo-c2a0b9T3dSVHVYVk10Vnc/view?usp=sharing Customer added below line to the manifest file to create auto generated R.java file but it's not creating any file: respackage: com.soundcloud.android.crop The initial issue was that we were getting 'java.lang.NoClassDefFoundError: com.example.mypackage.R$layout' Exception, so customer copied the R.class file manually to the jar file and it worked in 5.x but 6.x that doesn't work anymore, so we added 'respackage : com.soundcloud.android.crop' to auto generate R.java file but it's not creating any R.java file, this probably is the root cause of the whole issue.
  4. Christopher Williams 2017-05-16

    Sorry, Eric just reminded me of this ticket. I was able to reproduce locally after some effort, and am currently trying to get to the bottom of why this is happening. I think the repackage change is the correct way to go, rather than included the pre-compiled R* classes from the wrapped jar library. But I think there may be a bug in the module build CLI code which is ignoring that value when building the module.
  5. Christopher Williams 2017-05-16

    So there is a CLI bug in building android native modules. I'll open a PR for that shortly. Additionally, the cropper.jar they used had the R files already inside, and I don't think that'd work properly. Adding respackage: com.soundcloud.android.crop is the correct way to got here, but the CLI bug basically was that we ignored that value. I built the 3rd party library using cradle and pulled out the classes.jar from he release AAR file, renamed it to cropper.jar and am attaching it here. That's what they should use in android/lib.
  6. Christopher Williams 2017-05-16

    master/6.2.0: https://github.com/appcelerator/titanium_mobile/pull/9063
  7. Ricardo Ramirez 2017-05-19

    Hi ! Do you have any ETA to get this review finished ?
  8. Christopher Williams 2017-06-06

    cherry-pick back port for 6.1.1: https://github.com/appcelerator/titanium_mobile/pull/9113
  9. Jan Vennemann 2017-06-06

    BTW, the respackage stuff is obsolete when using .aar files directly instead of manually extracting them to obtain the contained .jar and resources since 6.1.0, see https://wiki.appcelerator.org/display/guides2/Android+Module+Project#AndroidModuleProject-UsingAndroidLibraries. Using this will properly rebuild the R files from third-party libraries without the need to specify a respackage value.
  10. Lokesh Choudhary 2017-06-06

    FR Passed for master & backport PR.
  11. Lokesh Choudhary 2017-06-08

    Verified the fix in SDK's : 6.1.1.v20170607112801 & 6.2.0.v20170607160354. Closing.
  12. Ricardo Ramirez 2017-06-12

    This is not working and affected other modules in the project
  13. Ricardo Ramirez 2017-06-12

    Please see the attached screen shots.
  14. Jan Vennemann 2017-06-12

    [~rramirez], please try the 6_1_X branch, there is a known issue with the master branch and modules compiled with older versions.
  15. Ricardo Ramirez 2017-06-12

    I see, sorry ! Let me check. Thanks
  16. Lokesh Choudhary 2017-06-13

    [~rramirez], Did you check it with 6_1_X branch ?
  17. Ricardo Ramirez 2017-06-15

    Yes This is the customer response: We were able to build a module using the version of SDK you provided. However, when we run our app, we still have the same problem as before. We get the following exception when we try to use the cropping functionality:
        [ERROR] : TiApplication: (main) [30641,338736] Sending event: exception on thread: main msg:java.lang.RuntimeException: Unable to start activity ComponentInfo{com.ias.dispatchM/com.soundcloud.android.crop.CropImageActivity}: java.lang.NullPointerException: Attempt to write to field 'android.content.Context com.soundcloud.android.crop.CropImageView.context' on a null object reference; Titanium 6.1.1,2017/06/09 15:31,undefined 
        
        [ERROR] : TiApplication: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.ias.dispatchM/com.soundcloud.android.crop.CropImageActivity}: java.lang.NullPointerException: Attempt to write to field 'android.content.Context com.soundcloud.android.crop.CropImageView.context' on a null object reference 
        
        [ERROR] : TiApplication: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2947) 
        
        [ERROR] : TiApplication: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3008) 
        
        [ERROR] : TiApplication: at android.app.ActivityThread.-wrap14(ActivityThread.java) 
        
        [ERROR] : TiApplication: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1650) 
        
        [ERROR] : TiApplication: at android.os.Handler.dispatchMessage(Handler.java:102) 
        
        [ERROR] : TiApplication: at android.os.Looper.loop(Looper.java:154) 
        
        [ERROR] : TiApplication: at android.app.ActivityThread.main(ActivityThread.java:6688) 
        
        [ERROR] : TiApplication: at java.lang.reflect.Method.invoke(Native Method) 
        
        [ERROR] : TiApplication: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1468) 
        
        [ERROR] : TiApplication: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1358) 
        
        [ERROR] : TiApplication: Caused by: java.lang.NullPointerException: Attempt to write to field 'android.content.Context com.soundcloud.android.crop.CropImageView.context' on a null object reference 
        
        [ERROR] : TiApplication: at com.soundcloud.android.crop.CropImageActivity.setupViews(CropImageActivity.java:97) 
        
        [ERROR] : TiApplication: at com.soundcloud.android.crop.CropImageActivity.onCreate(CropImageActivity.java:75) 
        
        [ERROR] : TiApplication: at android.app.Activity.performCreate(Activity.java:6912) 
        
        [ERROR] : TiApplication: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1126) 
        
        [ERROR] : TiApplication: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2900) 
        
        [ERROR] : TiApplication: ... 9 more 
        
    It seems like we are going in circles.
  18. Shuo Liang 2017-06-16

    Guys, Please check this asap. Customer need the fix.
  19. Ricardo Ramirez 2017-06-16

    The exception happens when the customer is attempting to crop a photo. here is the code of the module https://drive.google.com/open?id=0BwTWo-c2a0b9T3dSVHVYVk10Vnc
  20. Jan Vennemann 2017-06-16

    [~lchoudhary], the example in the module is a default one. Use the following code to actually test the module:
        var Cropper = require('ias.cropper');
        var imageFile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'image.jpg');
        var imageBlob = imageFile.read();
        var tmpFile = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, 'image.jpg');
        tmpFile.write(imageBlob);
        Cropper.open({
          image: tmpFile.read(),
          success: function () {
            Ti.API.debug('success');
          },
          cancel: function () {
            Ti.API.debug('cancel');
          },
          error: function (e) {
            Ti.API.debug('error');
            Ti.API.debug(e.error);
          }
        });
        
    Place any test image under app/assets/image.jpg. I tested with 6.1.1.v20170615113917 and the module from the above link and the crop view opens just fine.
  21. Lokesh Choudhary 2017-06-16

    [~jvennemann], thanks for the app.js code. [~rramirez],I ran the code & the image opened successfully & I was able to move the crop selection square over the image to the appropriate position to crop the image & hitting Done button calls success.
  22. Christopher Williams 2017-06-21

    Modules such as the one discussed here really are about wrapping a 3rd-party *.aar file to be consumed in a Titanium app. If we support AAR files in modules or directly in an Android app (and we do now), then you don't really need to make a module to wrap a 3rd-party library anymore.
  23. Hans Knöchel 2017-06-21

    See [this PR](https://github.com/appcelerator-modules/ti.facebook/pull/85/commits/88568b940ca809c916f4b16d7c933daef5fd10cf) for an update migrating the Ti.Facebook to use the new AAR-handling and official Facebook SDK v4.23.0 AAR.

JSON Source