Android-runtime: Cannot convert JavaScript object

Created on 25 May 2016  Â·  8Comments  Â·  Source: NativeScript/android-runtime

I'm looking to implement in-app purchases in my {N} app, and am trying to use android-checkout lib to do so. There is a specific class that I cannot initialize. Whenever I do try, I get the error "Cannot convert JavaScript object with id ######### at index 1". As can be seen, the constructor takes a Context instance, and a Configuration _interface_ implementation. I get the Context from the utils.ad {N} module, and get the Configuration implementation by instantiating DefaultConfiguration abstract class, and those two items appear to be working just fine. It's when I instantiate Billing with those that I get the error. I think that there's something about this class that prevents it from being converted to a JavaScript object.

Here's a snippet of my source:

import * as utils from 'utils/utils';
var cryptoJs = require('crypto-js');
var CheckoutLib = org.solovyev.android.checkout;
​
const BILLING_TOKEN: string = utils.ad.getApplicationContext().getResources().getString(utils.ad.resources.getStringId('billing_token')),
    BILLING_KEY: string = utils.ad.getApplicationContext().getResources().getString(utils.ad.resources.getStringId('billing_key')),
    ITEM_IDS = {
        PRO_VERSION: 'pro_version'
    };
​
class BillingConfiguration extends CheckoutLib.Billing.DefaultConfiguration {
    public getPublicKey(): string {
        return 'decrypted key here';
    }
}
var StoreBillingConfiguration = new BillingConfiguration();
console.log(`DefaultConfiguration: ${typeof CheckoutLib.Billing.DefaultConfiguration}`); //Function
console.log(`StoreBillingConfiguration: ${typeof StoreBillingConfiguration}`); //Object
​
/**** ERROR HAPPENS WHEN THE FOLLOWING LINE IS RAN ****/
var Billing = new CheckoutLib.Billing(utils.ad.getApplicationContext(), StoreBillingConfiguration);

In-app billing is such a huge part of mobile apps - it would be a shame if we cannot get this functionality into {N}. Any assistance with this greatly appreciated!

question

Most helpful comment

@bradleygore, I managed to reproduce the problem and it turns out that the error you are experiencing is for not returning the native (Java) object while constructing the TS object. See how the core-modules do that.. TypeScript supports extends syntax and the compiler does not append return global.__native(this) by default. We'll make sure that is well documented.

Moving on - the code crashes on the line following the instantiation of Billing, where you create the Products list - Objects mapping will return a javascript array which contains strings and will not be automatically marshalled to a List.
See this StackOverflow post about converting an array to List or copy the lines below.

var products = Object.keys(ITEM_IDS).map(k => ITEM_IDS[k]);
var productsListJ = new java.util.ArrayList(java.util.Arrays.asList(products));
var ProductsList = CheckoutLib.Products.create().add(CheckoutLib.ProductTypes.IN_APP, productsListJ);

Please let us know if this helped resolve the problems.

Edit:
I tinkered further with the sample and noticed that you try to create instance of an abstract class by providing implementation object, like you would when creating an interface implementation. Only it doesn't work that way, and classes, abstract or not, need to be extended, the same way you've done your BillingConfiguration.

var ListenerAdapter = CheckoutLib.Checkout.ListenerAdapter.extend({
    onReady(requests, product, billingSupported) {
        console.log(`Checkout.ListenerAdapter.onReady: ${product} supported = ${billingSupported}`);
    }
});

Checkout.start(new ListenerAdapter());

All 8 comments

Hello @bradleygore, it would appear at first glance that the problem can occur for one of two reasons - we either do not resolve to the correct constructor, or we are unable to match the JavaScript object to its Java instance counterpart.

Could you please enable verbose logging (type __enableVerboseLogging() at the beginning of your app.js) and send a Logcat report our way?

If you could also share a repository or a small complete code sample that would also assist us in reproducing and getting to the core of the problem.

Hey @Pip3r4o, sorry if this is a dumb question, but where does {N} save the logCat output? Is it just in the terminal, or saved to a file somewhere?

Regarding a complete gist - here you go: https://gist.github.com/bradleygore/2f27ad41c7532c765b487b1299d9f3fe

@bradleygore logcat allows you to dump the log to the console which you can then redirect to a file. Do the following:

$ adb logcat  -c (Clears the logcat)
- Start application, make it crash
$ adb logcat -d > "report.txt"

@Pip3r4o thanks, good to know. Here's the file: logcat.txt

@bradleygore, I managed to reproduce the problem and it turns out that the error you are experiencing is for not returning the native (Java) object while constructing the TS object. See how the core-modules do that.. TypeScript supports extends syntax and the compiler does not append return global.__native(this) by default. We'll make sure that is well documented.

Moving on - the code crashes on the line following the instantiation of Billing, where you create the Products list - Objects mapping will return a javascript array which contains strings and will not be automatically marshalled to a List.
See this StackOverflow post about converting an array to List or copy the lines below.

var products = Object.keys(ITEM_IDS).map(k => ITEM_IDS[k]);
var productsListJ = new java.util.ArrayList(java.util.Arrays.asList(products));
var ProductsList = CheckoutLib.Products.create().add(CheckoutLib.ProductTypes.IN_APP, productsListJ);

Please let us know if this helped resolve the problems.

Edit:
I tinkered further with the sample and noticed that you try to create instance of an abstract class by providing implementation object, like you would when creating an interface implementation. Only it doesn't work that way, and classes, abstract or not, need to be extended, the same way you've done your BillingConfiguration.

var ListenerAdapter = CheckoutLib.Checkout.ListenerAdapter.extend({
    onReady(requests, product, billingSupported) {
        console.log(`Checkout.ListenerAdapter.onReady: ${product} supported = ${billingSupported}`);
    }
});

Checkout.start(new ListenerAdapter());

@Pip3r4o Thanks for taking a look. However, I'm not following you on the return global.__native(this); statement. Where should I use that - where I'm extending the abstract class of DefaultConfiguration, or are you saying I should extend the CheckoutLib.Billing class with a new javascript class and then do that kind of return from that constructor?

Are there clear guidelines for when we need to use this global.__native(this); code? I've not ran into this, and have done a good bit of pulling in native stuff - for instance charting with MPAndroidChart java lib - but have never needed to use that until now.

Good catch on the ArrayList and the ListenerAdapter. I hadn't got that far yet.

@bradleygore have a look at that issue it fits your scenario.

class BillingConfiguration extends CheckoutLib.Billing.DefaultConfiguration {
    constructor() {
        super();
        return global.__native(this);
    }
    public getPublicKey(): string {
        return 'decrypted id...';
    }
}

@Pip3r4o Oh, I see - this makes sense. Thanks for the excellent help!!

Was this page helpful?
0 / 5 - 0 ratings