I'm implementing InApp Billing in my Android app. Everything works well, however, I'm trying to decouple broadcast receiver from an activity into manifest. Especially with this suggestion in Android's trivialdrive sample:
// Important: Dynamically register for broadcast messages about updated purchases.
// We register the receiver here instead of as a <receiver> in the Manifest
// because we always call getPurchases() at startup, so therefore we can ignore
// any broadcasts sent while the app isn't running.
// Note: registering this listener in an Activity is a bad idea, but is done here
// because this is a SAMPLE. Regardless, the receiver must be registered after
// IabHelper is setup, but before first call to getPurchases().
Currently there's a class that extends BroadcastReceiver
:
public class IabBroadcastReceiver extends BroadcastReceiver {
/**
* The Intent action that this Receiver should filter for.
*/
public static final String ACTION = "com.android.vending.billing.PURCHASES_UPDATED";
private final IabBroadcastListener mListener;
public IabBroadcastReceiver(IabBroadcastListener listener) {
mListener = listener;
}
@Override
public void onReceive(Context context, Intent intent) {
if (mListener != null) {
mListener.receivedBroadcast();
}
}
/**
* Listener interface for received broadcast messages.
*/
public interface IabBroadcastListener {
void receivedBroadcast();
}
}
And a class that implements IabBroadcastReceiver.IabBroadcastListener
:
public class Subscription extends AppCompatActivity implements
IabBroadcastReceiver.IabBroadcastListener {
IabHelper mHelper;
// Provides purchase notification while this app is running
IabBroadcastReceiver mBroadcastReceiver;
...
// Listener that's called when we finish querying the items and subscriptions we own
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d("IAB", "Query inventory finished.");
if (mHelper == null) return;
if (result.isFailure()) {
Log.d("IAB", "Failed to query inventory: " + result);
return;
}
if (inventory.getSkuDetails(SKU_MONTHLY_TTS) != null
&& inventory.getSkuDetails(SKU_YEARLY_TTS) != null) {
...
}
Log.d("IAB", "Query inventory was successful.");
/*
* Check for items we own. Notice that for each purchase, we check
* the developer payload to see if it's correct! See
* verifyDeveloperPayload().
*/
...
Log.d("IAB", "Initial inventory query finished; enabling main UI.");
}
};
// Callback for when a purchase is finished
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.d("IAB", "Purchase finished: " + result + ", purchase: " + purchase);
// if we were disposed of in the meantime, quit.
if (mHelper == null) return;
if (result.isFailure()) {
Log.d("IAB", "Error purchasing: " + result);
return;
}
if (!verifyDeveloperPayload(purchase)) {
Log.d("IAB", "Error purchasing. Authenticity verification failed.");
return;
}
Log.d("IAB", "Purchase successful.");
...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_subscription);
mHelper = new IabHelper(this, compiledKy);
mHelper.enableDebugLogging(true);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
@Override
public void onIabSetupFinished(IabResult result) {
Log.d("Subscription", "InSetUpFinished: " + result);
if (!result.isSuccess()) {
Log.d("Subscription", "Problem setting up In-app Billing: " + result);
return;
}
if (mHelper == null) return;
// Important: Dynamically register for broadcast messages about updated purchases.
// We register the receiver here instead of as a <receiver> in the Manifest
// because we always call getPurchases() at startup, so therefore we can ignore
// any broadcasts sent while the app isn't running.
// Note: registering this listener in an Activity is a bad idea, but is done here
// because this is a SAMPLE. Regardless, the receiver must be registered after
// IabHelper is setup, but before first call to getPurchases().
mBroadcastReceiver = new IabBroadcastReceiver(Subscription.this);
IntentFilter broadcastFilter = new IntentFilter(IabBroadcastReceiver.ACTION);
registerReceiver(mBroadcastReceiver, broadcastFilter);
// IAB is fully set up. Now, let's get an inventory of stuff we own.
Log.d("IAB", "Setup successful. Querying inventory.");
try {
List<String> additionalSkuList = new ArrayList<String>();
...
mHelper.queryInventoryAsync(true, null, additionalSkuList, mGotInventoryListener);
} catch (IabHelper.IabAsyncInProgressException e) {
Log.d("IAB", "Error querying inventory. Another async operation in progress.");
}
}
});
Button monthlySubButton = (Button) findViewById(R.id.monthlySubButton);
monthlySubButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!mHelper.subscriptionsSupported()) {
Log.d("IAB","Subscriptions not supported on your device yet. Sorry!");
return;
}
try {
...
mHelper.launchPurchaseFlow(Subscription.this, ..., IabHelper.ITEM_TYPE_SUBS,
oldSku, 10001, mPurchaseFinishedListener, "");
} catch (IabHelper.IabAsyncInProgressException e) {
Log.d("IAB", e.getMessage());
}
}
});
...
}
/** Verifies the developer payload of a purchase. */
boolean verifyDeveloperPayload(Purchase p) {
String payload = p.getDeveloperPayload();
...
return true;
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mBroadcastReceiver != null) {
unregisterReceiver(mBroadcastReceiver);
}
Log.d("IAB", "Destroying helper.");
if (mHelper != null) {
mHelper.disposeWhenFinished();
mHelper = null;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d("IAB", "onActivityResult(" + requestCode + "," + resultCode + "," + data);
if (mHelper == null) return;
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
else {
Log.d("IAB", "onActivityResult handled by IABUtil.");
}
}
@Override
public void receivedBroadcast() {
// Received a broadcast notification that the inventory of items has changed
Log.d("IAB", "Received broadcast notification. Querying inventory.");
try {
mHelper.queryInventoryAsync(mGotInventoryListener);
} catch (IabHelper.IabAsyncInProgressException e) {
Log.d("IAB", "Error querying inventory. Another async operation in progress.");
}
}
}
I'm trying to add a receiver in manifest, but it gives me an error:
</application>
...
<activity
android:name=".controller.Subscription"
android:label="Subscription"
android:parentActivityName=".controller.MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".controller.MainActivity" />
</activity>
<receiver android:name=".controller.Subscription" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>
</application>
Error Message:.controller.Subscription is not assignable to 'android.content.BroadcastReceiver'
The Subscription
class is in the right directory (under controller package).
Does my Subscription
class have to extend IabBroadcastReceiver class and not be implementing IabBroadcastReceiver.IabBroadcastListener
instead? I'll still like to extend AppCompactActivity
, will like to know if there's any way to resolve this. It seems like there's no samples online which shows how to implement inApp billing api with a broadcast receiver registered in the manifest. Thank you for your help in advance!
controller.Subscription is not assignable to 'android.content.BroadcastReceiver'
Which means, that Subscription
is not a descendant of a BroadcastReceiver
. You have registered Subscription
in manifest as a receiver
, but in fact it is not a subclass of BroadcastReceiver
.
Does my
Subscription
class have to extendIabBroadcastReceiver
class and not be implementingIabBroadcastReceiver.IabBroadcastListener
instead?
In order to register a class as a receiver
in manifest, it should be a descendant (direct or indirect) of BroadcastReceiver
. Thus, Subscription
should either extends BroadcastReceiver
or extends IabBroadcastReceiver
.
I'll still like to extend AppCompactActivity.
You cannot make a class both activity and a receiver (multiple inheritance is not supported in Java).
Still you can register IabBroadcastReceiver
through manifest as a <receiver>
.
But I wonder what's the reasoning behind that? Obviously, you would never receive any broadcast while your app is inactive simply because you should initiate the purchase flow within your app, that's why it makes more sense to register and unregister that BroadcastReceiver
dynamically. Note, that having registered receiver through manifest would make you receive broadcasts of purchases from other apps, which you most possibly are not interested in.
See docs:
Don't register this broadcast receiver in the app manifest. Declaring the receiver in the manifest can cause the system to launch the app to handle the intent if the user makes a purchase while the app isn't running. This behavior is not necessary and may be annoying to the user. To find out about any purchases the user made while the app wasn't running, call getPurchases() when the user launches the app.