It seems that it is quite complicated to implement In-App Billing in an Android app. How could I do this? The sample app from the SDK only has one Activity, which kind of over-simplifies it for an application like mine that has multiple Activities.
Well, I'll try to explain what I experienced. I don't consider myself an expert on this but I broke my head several days.
For starters, I had a very bad time trying to understand the workflow of the example and the application. I thought it should be better to start with a simple example however its much difficult to separate the code in small pieces and not knowing if you are breaking anything. I'll tell you what I have and what I changed from the example to make it work.
I have a single Activity where all my purchases come from. It's called Pro.
First, you should update the variable base64EncodedPublicKey in your Security class with your public Market developer key or you will see a nice Exception.
Well, I bind my Activity to my BillingService like so:
public class Pro extends TrackedActivity implements OnItemClickListener {
private BillingService mBillingService;
private BillingPurchaseObserver mBillingPurchaseObserver;
private Handler mHandler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pro);
//Do my stuff
mBillingService = new BillingService();
mBillingService.setContext(getApplicationContext());
mHandler = new Handler();
mBillingPurchaseObserver = new BillingPurchaseObserver(mHandler);
}
}
#Override
protected void onStart() {
//Register the observer to the service
super.onStart();
ResponseHandler.register(mBillingPurchaseObserver);
}
#Override
protected void onStop() {
//Unregister the observer since you dont need anymore
super.onStop();
ResponseHandler.unregister(mBillingPurchaseObserver);
}
#Override
protected void onDestroy() {
//Unbind the service
super.onDestroy();
mBillingService.unbind();
}
That way, all the purchases talk to this service, that will then, send the JSON requests to the market. You might think that the purchases are made on the same instant but no. You send the request and the purchase might come minutes or hours later. I think this is mainly to server overload and approval of the credit cards.
Then I have a ListView with my items, and I open a AlertDialog on each one, inviting them to buy the item. When they click on an item, I do this:
private class BuyButton implements DialogInterface.OnClickListener {
private BillingItem item = null;
private String developerPayload;
public BuyButton(BillingItem item, String developerPayload) {
this.item = item;
this.developerPayload = developerPayload;
}
#Override
public void onClick(DialogInterface dialog, int which) {
if (GeneralHelper.isOnline(getApplicationContext())){
//I track the buy here with GA SDK.
mBillingService.requestPurchase(this.item.getSku(), this.developerPayload);
} else {
Toast.makeText(getApplicationContext(), R.string.msg_not_online, Toast.LENGTH_SHORT).show();
}
}
}
Alright, you should see that the Market opens and the user either finishes or cancels the buy.
Whats then important is my PurChaseObserver, which handles all the events that market sends. This is a stripped version of it but you should get the point (See my comments throught the code):
private class BillingPurchaseObserver extends PurchaseObserver {
public BillingPurchaseObserver(Handler handler) {
super(Pro.this, handler);
}
#Override
public void onBillingSupported(boolean supported) {
if (supported) {
//Enable buy functions. Not required, but you can do stuff here. The market first checks if billing is supported. Maybe your country is not supported, for example.
} else {
Toast.makeText(getApplicationContext(), R.string.billing_not_supported, Toast.LENGTH_LONG).show();
}
}
#Override
public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
int quantity, long purchaseTime, String developerPayload) {
//This is the method that is called when the buy is completed or refunded I believe.
// Here you can do something with the developerPayload. Its basically a Tag you can use to follow your transactions. i dont use it.
BillingItem item = BillingItem.getBySku(getApplicationContext(), itemId);
if (purchaseState == PurchaseState.PURCHASED) {
if (item != null){
//This is my own implementation that sets the item purchased in my database. BillingHelper is a class with methods I use to check if the user bought an option and update the UI. You should also check for refunded. You can see the Consts class to find what you need to check for.
boolean resu = item.makePurchased(getApplicationContext());
if (resu){
Toast.makeText(getApplicationContext(), R.string.billing_item_purchased, Toast.LENGTH_LONG).show();
}
}
}
}
private void trackPurchase(BillingItem item, long purchaseTime) {
//My code to track the purchase in GA
}
#Override
public void onRequestPurchaseResponse(RequestPurchase request,
ResponseCode responseCode) {
//This is the callback that happens when you sent the request. It doesnt mean you bought something. Just that the Market received it.
if (responseCode == ResponseCode.RESULT_OK) {
Toast.makeText(getApplicationContext(), R.string.billing_item_request_sent, Toast.LENGTH_SHORT).show();
} else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
//The user canceled the item.
} else {
//If it got here, the Market had an unexpected problem.
}
}
#Override
public void onRestoreTransactionsResponse(RestoreTransactions request,
ResponseCode responseCode) {
if (responseCode == ResponseCode.RESULT_OK) {
//Restore transactions should only be run once in the lifecycle of your application unless you reinstalled the app or wipe the data.
SharedPreferences.Editor edit = PreferencesHelper.getInstance().getDefaultSettings(getApplicationContext()).edit();
edit.putBoolean(Consts.DB_INITIALIZED, true);
edit.commit();
} else {
//Something went wrong
}
}
}
And I believe you shouldn't need to edit anything else. The rest of the code "works".
You can try using the sample SKU at first in your own items "android.test.purchased". So far I have tested this and it works however I still need to cover everything like the refunded state. In this case, I am letting the user keep the features but I want to make sure it works perfect before modyfing it.
I hope it helps you and others.
V3: here is an tutorial for a quick start.. He´s using the helper-classes from the google example (Trivial Drive) ... Good as first "Hello Billing" ..
http://www.techotopia.com/index.php/Integrating_Google_Play_In-app_Billing_into_an_Android_Application_%E2%80%93_A_Tutorial
There is a full example of Android In-App Billing v3 step by step is given here with screenshot. Please check the tutorial:
Android In-App Billing v3 using ServiceConnection Class
Hope it will help.
For more clarification, go through this tutorial: Implementing In-app Billing in Version 3 API
Steps to follow to Integrate In-app Billing library in our project
Update your AndroidManifest.xml file.
Create a ServiceConnection and bind it to IInAppBillingService.
Send In-app Billing requests from your application to IInAppBillingService.
Handle In-app Billing responses from Google Play.
Update AndroidManifest.xml
<uses-permission android:name="com.android.vending.BILLING" />
Add the permissions in Manifest.xml file
Adding the AIDL file to your project
Build your application. You should see a generated file named IInAppBillingService.java in the /gen directory of your project.
Update Dependencies in build.gradle file
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.0"
defaultConfig {
applicationId "com.inducesmile.androidinapppurchase"
minSdkVersion 14
targetSdkVersion 24
versionCode 2
versionName "1.1"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.1.1'
compile 'com.intuit.sdp:sdp-android:1.0.3'
compile 'com.android.support:support-annotations:24.1.1'
compile 'org.jetbrains:annotations-java5:15.0'
}
InAppPurchaseActivity.java and activity_in_app_purchase.xml
This is where will offer our app users the opportunity to to make in-app purchase. In the layout file, we will give the user the opportunity to make purchase in different denominations.
InAppPurchaseActivity.java
Note: getAllUserPurchase() and itemPurchaseAvailability() methods should be called in non UI Thread to avoid app crashing.
public class InAppPurchaseActivity extends AppCompatActivity {
private static final String TAG = InAppPurchaseActivity.class.getSimpleName();
private IInAppBillingService mService;
private CustomSharedPreference customSharedPreference;
String[] productIds = new String[]{Helper.ITEM_ONE_ID, Helper.ITEM_TWO_ID, Helper.ITEM_THREE_ID};
private ImageView buyOneButton, buyTwoButton, buyThreeButton;
private static final char[] symbols = new char[36];
static {
for (int idx = 0; idx < 10; ++idx)
symbols[idx] = (char) ('0' + idx);
for (int idx = 10; idx < 36; ++idx)
symbols[idx] = (char) ('a' + idx - 10);
}
private String appPackageName;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_in_app_purchase);
appPackageName = this.getPackageName();
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
customSharedPreference = new CustomSharedPreference(InAppPurchaseActivity.this);
buyOneButton = (ImageView)findViewById(R.id.buy_one);
buyOneButton.setVisibility(View.GONE);
assert buyOneButton != null;
buyOneButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(!isBillingSupported()){
Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));
return;
}
purchaseItem(Helper.ITEM_ONE_ID);
}
});
buyTwoButton = (ImageView)findViewById(R.id.buy_two);
buyTwoButton.setVisibility(View.GONE);
assert buyTwoButton != null;
buyTwoButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(!isBillingSupported()){
Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));
return;
}
purchaseItem(Helper.ITEM_TWO_ID);
}
});
buyThreeButton = (ImageView)findViewById(R.id.buy_three);
buyThreeButton.setVisibility(View.GONE);
assert buyThreeButton != null;
buyThreeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(!isBillingSupported()){
Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));
return;
}
purchaseItem(Helper.ITEM_THREE_ID);
}
});
}
ServiceConnection mServiceConn = new ServiceConnection() {
#Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IInAppBillingService.Stub.asInterface(service);
AvailablePurchaseAsyncTask mAsyncTask = new AvailablePurchaseAsyncTask(appPackageName);
mAsyncTask.execute();
}
};
private void purchaseItem(String sku){
String generatedPayload = getPayLoad();
customSharedPreference.setDeveloperPayLoad(generatedPayload);
try {
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), sku, "inapp", generatedPayload);
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
try {
startIntentSenderForResult(pendingIntent.getIntentSender(), Helper.RESPONSE_CODE, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == Helper.RESPONSE_CODE) {
int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
if (resultCode == RESULT_OK) {
try {
JSONObject purchaseJsonObject = new JSONObject(purchaseData);
String sku = purchaseJsonObject.getString("productId");
String developerPayload = purchaseJsonObject.getString("developerPayload");
String purchaseToken = purchaseJsonObject.getString("purchaseToken");
//the developerPayload value is better stored in remote database but in this tutorial
//we will use a shared preference
for(int i = 0; i < productIds.length; i++){
if(productIds[i].equals(sku) && developerPayload.equals(customSharedPreference.getDeveloperPayload())){
customSharedPreference.setPurchaseToken(purchaseToken);
//access to private content
Intent contentIntent = new Intent(InAppPurchaseActivity.this, PrivateContentActivity.class);
startActivity(contentIntent);
}
}
}
catch (JSONException e) {
e.printStackTrace();
}
}
}
}
private String getPayLoad(){
RandomString randomString = new RandomString(36);
String payload = randomString.nextString();
return payload;
}
public class RandomString {
private final Random random = new Random();
private final char[] buf;
public RandomString(int length) {
if (length < 1)
throw new IllegalArgumentException("length < 1: " + length);
buf = new char[length];
}
public String nextString() {
for (int idx = 0; idx < buf.length; ++idx)
buf[idx] = symbols[random.nextInt(symbols.length)];
return new String(buf);
}
}
public final class SessionIdentifierGenerator {
private SecureRandom random = new SecureRandom();
public String nextSessionId() {
return new BigInteger(130, random).toString(32);
}
}
private class AvailablePurchaseAsyncTask extends AsyncTask<Void, Void, Bundle> {
String packageName;
public AvailablePurchaseAsyncTask(String packageName){
this.packageName = packageName;
}
#Override
protected Bundle doInBackground(Void... voids) {
ArrayList<String> skuList = new ArrayList<String>();
skuList.add(Helper.ITEM_ONE_ID);
skuList.add(Helper.ITEM_TWO_ID);
skuList.add(Helper.ITEM_THREE_ID);
Bundle query = new Bundle();
query.putStringArrayList(Helper.ITEM_ID_LIST, skuList);
Bundle skuDetails = null;
try {
skuDetails = mService.getSkuDetails(3, packageName, "inapp", query);
} catch (RemoteException e) {
e.printStackTrace();
}
return skuDetails;
}
#Override
protected void onPostExecute(Bundle skuDetails) {
List<AvailablePurchase> canPurchase = new ArrayList<AvailablePurchase>();
int response = skuDetails.getInt("RESPONSE_CODE");
if (response == 0) {
ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST");
if(responseList != null){
for (String thisResponse : responseList) {
JSONObject object = null;
try {
object = new JSONObject(thisResponse);
String sku = object.getString("productId");
String price = object.getString("price");
canPurchase.add(new AvailablePurchase(sku, price));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
if(checkIfPurchaseIsAvailable(canPurchase, productIds[0])){
buyOneButton.setVisibility(View.VISIBLE);
}else{
buyOneButton.setVisibility(View.GONE);
}
if(checkIfPurchaseIsAvailable(canPurchase, productIds[1])){
buyTwoButton.setVisibility(View.VISIBLE);
}else{
buyTwoButton.setVisibility(View.GONE);
}
if(checkIfPurchaseIsAvailable(canPurchase, productIds[2])){
buyThreeButton.setVisibility(View.VISIBLE);
}else{
buyThreeButton.setVisibility(View.GONE);
}
}
}
#org.jetbrains.annotations.Contract("null, _ -> false")
private boolean checkIfPurchaseIsAvailable(List<AvailablePurchase> all, String productId){
if(all == null){ return false;}
for(int i = 0; i < all.size(); i++){
if(all.get(i).getSku().equals(productId)){
return true;
}
}
return false;
}
public boolean isBillingSupported(){
int response = 1;
try {
response = mService.isBillingSupported(3, getPackageName(), "inapp");
} catch (RemoteException e) {
e.printStackTrace();
}
if(response > 0){
return false;
}
return true;
}
public void consumePurchaseItem(String purchaseToken){
try {
int response = mService.consumePurchase(3, getPackageName(), purchaseToken);
if(response != 0){
return;
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
public Bundle getAllUserPurchase(){
Bundle ownedItems = null;
try {
ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);
} catch (RemoteException e) {
e.printStackTrace();
}
return ownedItems;
}
public List<UserPurchaseItems> extractAllUserPurchase(Bundle ownedItems){
List<UserPurchaseItems> mUserItems = new ArrayList<UserPurchaseItems>();
int response = ownedItems.getInt("RESPONSE_CODE");
if (response == 0) {
ArrayList<String> ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
ArrayList<String> purchaseDataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
ArrayList<String> signatureList = ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
String continuationToken = ownedItems.getString("INAPP_CONTINUATION_TOKEN");
if(purchaseDataList != null){
for (int i = 0; i < purchaseDataList.size(); ++i) {
String purchaseData = purchaseDataList.get(i);
assert signatureList != null;
String signature = signatureList.get(i);
assert ownedSkus != null;
String sku = ownedSkus.get(i);
UserPurchaseItems allItems = new UserPurchaseItems(sku, purchaseData, signature);
mUserItems.add(allItems);
}
}
}
return mUserItems;
}
#Override
public void onDestroy() {
super.onDestroy();
if (mService != null) {
unbindService(mServiceConn);
}
}
}
Create Helper Package Directory
Create a new package folder and name it helpers. Inside the package, create a new java file Helper.java.
Helper.java
public class Helper {
public static final String ITEM_ID_LIST = "ITEM_ID_LIST";
public static final String ITEM_ONE_ID = "productone";
public static final String ITEM_TWO_ID = "producttwo";
public static final String ITEM_THREE_ID = "productthree";
public static final int RESPONSE_CODE = 1001;
public static final String SHARED_PREF = "shared_pref";
public static final String DEVELOPER_PAYLOAD = "developer_payload";
public static final String PURCHASE_TOKEN = "purchase_token";
public static void displayMessage(Context context, String message){
Toast.makeText(context.getApplicationContext(), message, Toast.LENGTH_LONG).show();
}
}
Testing In-App Billing Purchase
Create a Google+ account(don't use main account)
Add the users that will test the app in your group or community.
Errors You might encounter during In-App purchase testing
the item you requested is not available for purchase
Solution – According to AndreiBogdan in Stackoverflow,
All credit goes to Inducesmile for his tutorial
Android Developer Blog also recommends a training class on Selling In-app Products. To see a complete implementation and learn how to test the application, Please check this tutorial: Selling In-app Products
Okay this is one of those things that doesn't have very much documentation available online, so I'm going to do my best to explain everything step by step. Taken from my blog post, which is a more detailed version of this (with screenshots), here on The Millibit. Without further ado,
Step One: Permissions
This is the easiest step. Navigate to your manifest.xml file and add the following line under your tag:
<uses-permission android:name="com.android.vending.BILLING" />
This will give your app the permissions to access In-App Billing. If you are targetting versions above API 22, you will need to make sure that this permission is granted at runtime.
Step Two: Play Console
Now you need to upload your app to the Google Play Console. We are not publishing our app to the public yet (don’t worry), we are just uploading it to the BETA RELEASE section, which will allow us to test In-App Purchases. The reason we need to do this is that Google needs to have some version of your APK uploaded for the billing processes to actually work.
Go to https://play.google.com/apps/publish/
Create the Application
Follow the steps to set up your app
Go to App Releases
Navigate to Beta
Create an APK of your app in Android studio and upload it to the Beta production in the Play Console
(before releasing make sure that you have already filled out the Store Listing ,Content Rating and Pricing and Distribution)
Hit the magic button (publish!)
Step Three: Setup Project
Okay this is the part where you have to copy and paste a bunch of files.
First, grab this file, download it, and place it under src/mainIt should build itself into a folder
Next, grab this entire util folder and paste it into src/java folder. Then rebuild your project to resolve errors.
The Util Folder Contains The Following Classes:
IabBroadcastReceiver
IabException
IabHelper
IabResult
Inventory
Purchase
Security
SkuDetails
Step Four: Create Products
Create Managed Product
Click save and make a “pricing template”
Here, you will select the price of this product. You can choose the price for different countries, or have it automatically adjust if you just select all countries under your price:
Make sure the in-app product is activated and linked with the correct application in the console one last time.
Finally, note the ID of your product. We will use this ID in the next few steps.
Get your Base64EncodedString
Head over to “Services & APIs” and grab your Base64EncodedString. Copy and paste this to a notepad somewhere so that you have access to it. Do not share this with anyone, they will be able to do malicious things with it.
Step Five: Finally! We can start coding:
We will first bind to the in-app billing library, and query for what the user has/hasn’t bought. Then, we will buy the product that we set up earlier.
First, import everything we set up earlier:
import util.*;
Now we will use an IabHelper object called mHelper, and we will do everything with this.
base64EncodedPublicKey = ""; //PUT YOUR BASE64KEY HERE
mHelper = new IabHelper(this, base64EncodedPublicKey);
mHelper.enableDebugLogging(false); //set to false in real app
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
// Oh no, there was a problem.
if (result.getResponse() == 3) {
new AlertDialog.Builder(MainActivity.this)
.setTitle("In app billing")
.setMessage("This device is not compatible with In App Billing, so" +
" you may not be able to buy the premium version on your phone. ")
.setPositiveButton("Okay", null)
.show();
}
Log.v(TAG, "Problem setting up In-app Billing: " + result);
} else {
Log.v(TAG, "YAY, in app billing set up! " + result);
try {
mHelper.queryInventoryAsync(mGotInventoryListener); //Getting inventory of purchases and assigning listener
} catch (IabHelper.IabAsyncInProgressException e) {
e.printStackTrace();
}
}
}
});
Okay, let me break down what’s going on here. Basically, we are calling “startSetup” to initialize our “IabHelper”. If the setup is successful, we query what purchases the user already has and store the responses in mGotInventoryListener, which we will code next:
IabHelper.QueryInventoryFinishedListener mGotInventoryListener
= new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result,
Inventory inventory) {
i = inventory;
if (result.isFailure()) {
// handle error here
Log.v(TAG, "failure in checking if user has purchases");
} else {
// does the user have the premium upgrade?
if (inventory.hasPurchase("premium_version")) {
premiumEditor.putBoolean("hasPremium", true);
premiumEditor.commit();
Log.v(TAG, "Has purchase, saving in storage");
} else {
premiumEditor.putBoolean("hasPremium", false);
premiumEditor.commit();
Log.v(TAG, "Doesn't have purchase, saving in storage");
}
}
}
};
The above code is pretty self-explanatory. Basically, it just checks what purchases the user already has. Now that we know whether or not the user has already purchased our product, we know whether or not to ask them to purchase our item! If they’ve never bought our product before, let’s start a purchase request:
public void buyPremium() {
try {
mHelper.flagEndAsync();//If any async is going, make sure we have it stop eventually
mHelper.launchPurchaseFlow(this, "premium_version", 9, mPurchaseFinishedListener, "SECURITYSTRING"); //Making purchase request and attaching listener
} catch (Exception e) {
e.printStackTrace();
mHelper.flagEndAsync();//If any async is going, make sure we have it stop eventually
new AlertDialog.Builder(MainActivity.this)
.setTitle("Error")
.setMessage("An error occurred in buying the premium version. Please try again.")
.setPositiveButton("Okay", null)
.show();
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
}
else
Log.d(TAG, "onActivityResult handled by IABUtil.");
}
}
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener
= new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.v(TAG, "purchase finished");
if (purchase != null) {
if (purchase.getSku().equals("premium_version")) {
Toast.makeText(MainActivity.this, "Purchase successful!", Toast.LENGTH_SHORT).show();
premiumEditor.putBoolean("hasPremium", true);
premiumEditor.commit();
}
} else {
return;
}
if (result.isFailure()) {
return;
}
}
};
Here we purchase the item (with the ID we generated in the play console earlier) with the following:
mHelper.launchPurchaseFlow(this, "premium_version", 9, mPurchaseFinishedListener, "SECURITYSTRING"); //Making purchase request and attaching listener
Notice that we passed mPurchaseFinishedListener into the parameters. This means that the result of the purchase will be returned to this listener. Then, we simply check if the purchase is null, and if not, award the user with whatever feature they bought.
Don’t let the listeners leak! We must destroy them when the app destroys.
#Override
public void onDestroy() {
super.onDestroy();
if (mHelper != null)
try {
mHelper.dispose();
mHelper = null;
} catch (IabHelper.IabAsyncInProgressException e) {
e.printStackTrace();
}
}
Finally, if you’d like to consume your purchase, making it available for purchase again, you can do so easily. An example of this is if a user bought gas for a virtual car, and it ran out. They need to purchase the same product again, and you can make it available for a second purchase by consuming it:
public void consume(){
//MAKING A QUERY TO GET AN ACCURATE INVENTORY
try {
mHelper.flagEndAsync(); //If any async is going, make sure we have it stop eventually
mHelper.queryInventoryAsync(mGotInventoryListener); //Getting inventory of purchases and assigning listener
if(i.getPurchase("gas")==null){
Toast.makeText(this, "Already consumed!", Toast.LENGTH_SHORT).show();
}
} catch (IabHelper.IabAsyncInProgressException e) {
e.printStackTrace();
Toast.makeText(this, "Error, try again", Toast.LENGTH_SHORT).show();
mHelper.flagEndAsync();//If any async is going, make sure we have it stop eventually
}
//ACTUALLY CONSUMING
try {
mHelper.flagEndAsync();//If any async is going, make sure we have it stop eventually
this.mHelper.consumeAsync(this.i.getPurchase("gas"), new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase paramAnonymousPurchase, IabResult paramAnonymousIabResult) {
//resell the gas to them
}
});
return;
} catch (IabHelper.IabAsyncInProgressException localIabAsyncInProgressException) {
localIabAsyncInProgressException.printStackTrace();
Toast.makeText(this, "ASYNC IN PROGRESS ALREADY!!!!" +localIabAsyncInProgressException, Toast.LENGTH_LONG).show();
Log.v("myTag", "ASYNC IN PROGRESS ALREADY!!!");
mHelper.flagEndAsync();
}
}
That’s it! You can now start making money. It’s really that simple!
Again, if you want a more detailed version of this tutorial, with screenshots and pictures, visit the original post here. Let me know in the comments if you have any more questions.
For better understanding of how in-app billing works using google play billing library, refer to the flow chart below:
You can follow the integration step by step that I have explained in this article :
https://medium.com/#surabhichoudhary/in-app-purchasing-with-google-play-billing-library-6a72e289a78e
If you need demo on this, this is the project link : https://github.com/surabhi6/InAppPurchaseDemo
If you want to use an easy library to publish across Google Play and the Amazon Appstore, you could go with RoboBillingLibrary. It abstracts the details of both into one easy to use library. Detailed instructions are on the Github page.
I have developed Android In app billing library which uses "com.android.billingclient:billing:2.1.0"
Here are its properties:
Library is supported for "INAPP"
Subscription will be supported later!
Library use Roomdb for your products, You don’t need implementation to check status of your products
Library use Shared dependency. Your app will be less sized and no multidex needed
Library checks your products status on every time app starts. You can get status(bought or not)!
Every product bought by client need to be " Acknowledged" in SUCCES State. Library is making this for you!
Library support (immediate buy, response late purchase succus, response late purchase reject, user canceled purchase)
library source
Basically you need purchase code at two places
Firstly at your MainActivity where
your app opens every time, so check purchase statuses there. Need to implement purchasesupdated listener.
Secondly when button is clicked to initiate purchase. So where your button reside its activity need to
implement purchasesupdated listener.
If your button reside under MainActivity then you just need purchase code at one place i.e MainActivity.
For more you can follow my working tutorial here:
https://programtown.com/how-to-make-multiple-in-app-purchase-in-android-using-google-play-billing-library/
Related
As I know that the ViewModel should be secluded from the UI/View and contains only the logic that observes the data that's coming from the server or database
In my App, I used REST API "retrofit" and blogger API and I tried to migrate/upgrade the current code to MVVM but there are a few problems, let's go to the code
BloggerAPI Class
public class BloggerAPI {
private static final String BASE_URL =
"https://www.googleapis.com/blogger/v3/blogs/4294497614198718393/posts/";
private static final String KEY = "the Key";
private PostInterFace postInterFace;
private static BloggerAPI INSTANCE;
public BloggerAPI() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
postInterFace = retrofit.create(PostInterFace.class);
}
public static String getBaseUrl() {
return BASE_URL;
}
public static String getKEY() {
return KEY;
}
public static BloggerAPI getINSTANCE() {
if(INSTANCE == null){
INSTANCE = new BloggerAPI();
}
return INSTANCE;
}
public interface PostInterFace {
#GET
Call<PostList> getPostList(#Url String url);
}
public Call<PostList>getPosts(String url){
return postInterFace.getPostList(url);
}
}
this getData method I used in the Mainctivity to retrieve blog posts
public void getData() {
if (getItemsByLabelCalled) return;
progressBar.setVisibility(View.VISIBLE);
String url = BloggerAPI.getBaseUrl() + "?key=" + BloggerAPI.getKEY();
if (token != "") {
url = url + "&pageToken=" + token;
}
if (token == null) {
return;
}
final Call<PostList> postList = BloggerAPI.getINSTANCE().getPosts(url);
postList.enqueue(new Callback<PostList>() {
#Override
public void onResponse(#NonNull Call<PostList> call, #NonNull Response<PostList> response) {
if (response.isSuccessful()) {
progressBar.setVisibility(View.GONE);
PostList list = response.body();
Log.d(TAG, "onResponse: " + response.body());
if (list != null) {
token = list.getNextPageToken();
items.addAll(list.getItems());
adapter.notifyDataSetChanged();
for (int i = 0; i < items.size(); i++) {
items.get(i).setReDefinedID(i);
}
if (sqLiteItemsDBHelper == null || sqLiteItemsDBHelper.getAllItems().isEmpty()) {
SaveInDatabase task = new SaveInDatabase();
Item[] listArr = items.toArray(new Item[0]);
task.execute(listArr);
}
}
} else {
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
int sc = response.code();
switch (sc) {
case 400:
Log.e("Error 400", "Bad Request");
break;
case 404:
Log.e("Error 404", "Not Found");
break;
default:
Log.e("Error", "Generic Error");
}
}
}
#Override
public void onFailure(#NonNull Call<PostList> call, #NonNull Throwable t) {
Toast.makeText(MainActivity.this, "getData error occured", Toast.LENGTH_LONG).show();
Log.e(TAG, "onFailure: " + t.toString());
Log.e(TAG, "onFailure: " + t.getCause());
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
}
});
}
I created the PostsViewModel to trying to think practically how to migrate the current code to use MVVM
public class PostsViewModel extends ViewModel {
public MutableLiveData<PostList> postListMutableLiveData = new MutableLiveData<>();
public void getData() {
String token = "";
// if (getItemsByLabelCalled) return;
// progressBar.setVisibility(View.VISIBLE);
String url = BloggerAPI.getBaseUrl() + "?key=" + BloggerAPI.getKEY();
if (token != "") {
url = url + "&pageToken=" + token;
}
if (token == null) {
return;
}
BloggerAPI.getINSTANCE().getPosts(url).enqueue(new Callback<PostList>() {
#Override
public void onResponse(Call<PostList> call, Response<PostList> response) {
postListMutableLiveData.setValue(response.body());
}
#Override
public void onFailure(Call<PostList> call, Throwable t) {
}
});
}
}
and it's used thus in MainActivity
postsViewModel = ViewModelProviders.of(this).get(PostsViewModel.class);
postsViewModel.postListMutableLiveData.observe(this, postList -> {
items.addAll(postList.getItems());
adapter.notifyDataSetChanged();
});
now there are two problems using this way of MVVM "ViewModel"
first in the current getData method in the MainActivity it's contains some components that should work only in the View layer like the items list, the recyclerView needs to set View.GONE in case of response unsuccessful, progressBar, emptyView TextView, the adapter that needs to notify if there are changes in the list, and finally I need the context to used the create the Toast messages.
To solve this issue I think to add the UI components and other things into the ViewModel Class and create a constructor like this
public class PostsViewModel extends ViewModel {
Context context;
List<Item> itemList;
PostAdapter postAdapter;
ProgressBar progressBar;
TextView textView;
public PostsViewModel(Context context, List<Item> itemList, PostAdapter postAdapter, ProgressBar progressBar, TextView textView) {
this.context = context;
this.itemList = itemList;
this.postAdapter = postAdapter;
this.progressBar = progressBar;
this.textView = textView;
}
but this is not logically with MVVM arch and for sure cause memory leaking also I will not be able to create the instance of ViewModel with regular way like this
postsViewModel = ViewModelProviders.of(this).get(PostsViewModel.class);
postsViewModel.postListMutableLiveData.observe(this, postList -> {
items.addAll(postList.getItems());
adapter.notifyDataSetChanged();
});
and must be used like this
postsViewModel = new PostsViewModel(this,items,adapter,progressBar,emptyView);
so the first question is How to bind these UI components with the ViewModel?
second in the current getata I used the SaveInDatabase class use the AsyncTask way to save all items in the SQLite database the second question is How to move this class to work with ViewModel? but it also needs to work in the View layer to avoid leaking
the SaveInDatabase Class
static class SaveInDatabase extends AsyncTask<Item, Void, Void> {
#Override
protected Void doInBackground(Item... items) {
List<Item> itemsList = Arrays.asList(items);
// runtimeExceptionDaoItems.create(itemsList);
for (int i = 0 ; i< itemsList.size();i++) {
sqLiteItemsDBHelper.addItem(itemsList.get(i));
Log.e(TAG, "Size :" + sqLiteItemsDBHelper.getAllItems().size());
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
}
}
Actually the question is too broad to answer because there are many ways to implement for this case. First of all, never pass view objects to viewModel. ViewModel is used to notify changes to ui layer with LiveData or rxJava without retaining the view instance. You may try this way.
class PostViewModel extends ViewModel {
private final MutableLiveData<PostList> postListLiveData = new MutableLiveData<PostList>();
private final MutableLiveData<Boolean> loadingStateLiveData = new MutableLiveData<Boolean>();
private String token = "";
public void getData() {
loadingStateLiveData.postValue(true);
// if (getItemsByLabelCalled) return;
// progressBar.setVisibility(View.VISIBLE);
String url = BloggerAPI.getBaseUrl() + "?key=" + BloggerAPI.getKEY();
if (token != "") {
url = url + "&pageToken=" + token;
}
if (token == null) {
return;
}
BloggerAPI.getINSTANCE().getPosts(url).enqueue(new Callback<PostList>() {
#Override
public void onResponse(Call<PostList> call, Response<PostList> response) {
loadingStateLiveData.postValue(false);
postListLiveData.setValue(response.body());
token = response.body().getNextPageToken(); //===> the token
}
#Override
public void onFailure(Call<PostList> call, Throwable t) {
loadingStateLiveData.postValue(false);
}
});
}
public LiveData<PostList> getPostListLiveData(){
return postListLiveData;
}
public LiveData<Boolean> getLoadingStateLiveData(){
return loadingStateLiveData;
}
}
and you may observe the changes from your activity like this.
postsViewModel = ViewModelProviders.of(this).get(PostsViewModel.class);
postsViewModel.getPostListLiveData().observe(this,postList->{
if(isYourPostListEmpty(postlist)) {
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
items.addAll(postList.getItems());
adapter.notifyDataSetChanged();
}else {
recyclerView.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE);
}
});
postsViewModel.getLoadingStateLiveData().observe(this,isLoading->{
if(isLoading) {
progressBar.setVisibility(View.VISIBLE);
}else {
progressBar.setVisibility(View.GONE);
}
});
For my personal prefer, I like using Enum for error handling, but I can't post here as it will make the answer very long. For your second question, use Room from google. It will make you life a lot easier. It work very well with mvvm and it natively support liveData. You can try CodeLab from google to practise using room.
Bonus: You don't need to edit the url like this:
String url = BloggerAPI.getBaseUrl() + "?key=" + BloggerAPI.getKEY();
if (token != "") {
url = url + "&pageToken=" + token;
}
You can use #Path or #query based on your requirements.
As your question is bit broad , I am not giving any source code for the same, Rather mentioning samples which clearly resolves issues mentioned with MVVM.
Clean Code Architecture can be followed which will clearly separate the responsibilities of each layer.
First of all application architecture needs to be restructured so that each layer has designated role in MVVM. You can follow the following pattern for the same.
Only View Model will have access to UI layer
View model will connect with Use Case layer
Use case layer will connect with Data Layer
No layer will have cyclic reference to other components.
So now for Database, Repository will decide, from which section the data needs to be fetched
This can be either from Network or from DataBase.
All these points (except Database part) are covered over Medium Article, were each step is covered with actual API's .
Along with that unit test is also covered.
Libraries used are in this project are
Coroutines
Retrofit
Koin (Dependency Injection) Can be replaced with dagger2 is required
MockWebServer (Testing)
Language: Kotlin
Full Source code can be found over Github
Edit
Kotlin is the official supported language for Android Development now. I suggest you should lean and migrate your java android projects to Kotlin.
Still for converting Kotlin to Java, Go to Menu > Tools > Kotlin > Decompile Kotlin to Java Option
I'm trying to use Google's Saved Games feature with Google Play Games Services in my Android app. Google provides sample code how to do so:
private static final int RC_SAVED_GAMES = 9009;
private String mCurrentSaveName = "snapshotTemp";
private void showSavedGamesUI() {
SnapshotsClient snapshotsClient =
Games.getSnapshotsClient(this, GoogleSignIn.getLastSignedInAccount(this));
int maxNumberOfSavedGamesToShow = 5;
Task<Intent> intentTask = snapshotsClient.getSelectSnapshotIntent(
"See My Saves", true, true, maxNumberOfSavedGamesToShow);
intentTask.addOnSuccessListener(new OnSuccessListener<Intent>() {
#Override
public void onSuccess(Intent intent) {
startActivityForResult(intent, RC_SAVED_GAMES);
}
});
}
#Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
if (intent != null) {
if (intent.hasExtra(SnapshotsClient.EXTRA_SNAPSHOT_METADATA)) {
// Load a snapshot.
SnapshotMetadata snapshotMetadata =
intent.getParcelableExtra(SnapshotsClient.EXTRA_SNAPSHOT_METADATA);
mCurrentSaveName = snapshotMetadata.getUniqueName();
// Load the game data from the Snapshot
// ...
} else if (intent.hasExtra(SnapshotsClient.EXTRA_SNAPSHOT_NEW)) {
// Create a new snapshot named with a unique string
String unique = new BigInteger(281, new Random()).toString(13);
mCurrentSaveName = "snapshotTemp-" + unique;
// Create the new snapshot
// ...
}
}
}
Obviously, Google wants you to use their provided intent to let the user decide which saved game to load or if a new save game should be created.
I, on the other hand, want to do this decision for the user. However, I'm unable to find a way to return a list of snapshots and to load snapshot data.
Since my game won't require to maintain more than one saved game per user I'm less interested in getting a list of snapshots without an intent (which would be an interesting solution, though) and more in loading a snapshot based on the name of the saved game, silently.
How can I load a snapshot without showing an intent?
The comment of jess leaded me to a solution that is now deprecated. However the person who posted the answer pointed out that there is also a working solution in the CollectAllTheStars sample app that is provided by Google. I was tempted to check this sample app to find out if the Google team has changed the code to fit the new way. For my amuse the comments in that sample app were describing the old deprecated way, still, but the code was changed for my luck.
Inspecting the code gave me ideas, so I came up with this solution:
String serializedSavedGameData;
public void downloadSavedGameData(final String name) {
if(snapshotsClient != null) {
snapshotsClient.open(name, true, SnapshotsClient.RESOLUTION_POLICY_MOST_RECENTLY_MODIFIED).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.e(TAG, "Error while opening snapshot: ", e);
}
}).continueWith(new Continuation<SnapshotsClient.DataOrConflict<Snapshot>, byte[]>() {
#Override
public byte[] then(#NonNull Task<SnapshotsClient.DataOrConflict<Snapshot>> task) throws Exception {
Snapshot snapshot = task.getResult().getData();
// Opening the snapshot was a success and any conflicts have been resolved.
try {
// Extract the raw data from the snapshot.
return snapshot.getSnapshotContents().readFully();
} catch (IOException e) {
Log.e(TAG, "Error while reading snapshot: ", e);
} catch (NullPointerException e) {
Log.e(TAG, "Error while reading snapshot: ", e);
}
return null;
}
}).addOnCompleteListener(new OnCompleteListener<byte[]>() {
#Override
public void onComplete(#NonNull Task<byte[]> task) {
if(task.isSuccessful()) {
byte[] data = task.getResult();
try {
serializedSavedGameData = new String(data, "UTF-16BE");
} catch (UnsupportedEncodingException e) {
Log.d(TAG, "Failed to deserialize save game data: " + e.getMessage());
}
} else {
Exception ex = task.getException();
Log.d(TAG, "Failed to load saved game data: " + (ex != null ? ex.getMessage() : "UNKNOWN"));
}
}
});
}
}
I implemented a simple resolving policy (take the newest saved game on conflicts), but I had no time to hard test all the different cases like conflicts, but it passed my simple tests so far.
Hopefully anybody can profit from this.
I want to implement a very simple Java Telegram Client, which is capable of sending and receiving messages and store the sessions across multiple starts. I already managed to authenticate and receive messages
api = new TelegramApi(apiState, new AppInfo(API_ID, "console", "1", "1", "en"), new ApiCallback() {
#Override
public void onAuthCancelled(TelegramApi api) {
Log.d(TAG, "-----------------CANCELLED----------------");
Log.d(TAG, api.getApiContext().toString());
}
#Override
public void onUpdatesInvalidated(TelegramApi api) {
Log.d(TAG, "-----------------INVALIDATED----------------");
Log.d(TAG, api.getApiContext().toString());
}
#Override
public void onUpdate(TLAbsUpdates tlAbsUpdates) {
Log.d(TAG, "-----------------UPDATE----------------");
Log.d(TAG, tlAbsUpdates.toString());
if (tlAbsUpdates instanceof TLUpdateShortMessage) {
Log.d(TAG, "-----------------UPDATE CHAT MESSAGE----------------");
int senderId = ((TLUpdateShortMessage) tlAbsUpdates).getUserId();
Log.d(TAG, "Message from " + senderId);
String message = ((TLUpdateShortMessage) tlAbsUpdates).getMessage();
Log.d(TAG, message);
activity.appendMessage(TAG, message);
}
}
});
api.switchToDc(2);
TLConfig config = null;
try {
config = api.doRpcCallNonAuth(new TLRequestHelpGetConfig());
} catch (TimeoutException | IOException e) {
e.printStackTrace();
}
apiState.updateSettings(config);
However, I struggle to send messages to another user. For the beginning, it would be enough if I could send a message back to the user, who sent me a message before (by retrieving the senderId, as you can see in the onUpdate method before). However, if someone could also help me with retrieving the ids of my saved contacts, it would be perfect.
Furthermore, I want to store the sessions accross multiple startups, since I get a FLOOD_WAIT error (420), if I test my code to often.
For this I used https://github.com/rubenlagus/TelegramApi/blob/51713e9b6eb9e0ae0d4bbbe3d4deffff9b7f01e4/src/main/java/org/telegram/bot/kernel/engine/MemoryApiState.java and its used classes (e.g. TLPersistence), which stores and loads the ApiState. However, apparently it does not store the signin status, since I always have to authenticate my number every time I update the code.
By the way, I am using Api layer 66 (https://github.com/rubenlagus/TelegramApi/releases).
UPDATE 1:
Problems with sending messages solved myself:
private void sendMessageToUser(int userId, String message) {
TLInputPeerUser peer = new TLInputPeerUser();
peer.setUserId(userId);
TLRequestMessagesSendMessage messageRequest = new TLRequestMessagesSendMessage();
messageRequest.setFlags(0);
messageRequest.setPeer(peer);
messageRequest.setRandomId(new SecureRandom().nextLong());
messageRequest.setMessage(message);
api.doRpcCallNonAuth(messageRequest, 1500, new RpcCallback<TLAbsUpdates>() {
#Override
public void onResult(TLAbsUpdates tlAbsUpdates) {
Log.d(TAG, "-----------------------MESSAGE SENT-----------------------");
}
#Override
public void onError(int i, String s) {
Log.d(TAG, "-----------------------MESSAGE SENT ERROR-----------------------");
Log.d(TAG, String.valueOf(i));
if(s != null) {
Log.d(TAG, s);
}
}
});
}
However, now I am stuck at finding the userIds of my contacts.
After first update this is left:
Saving the session state (and signin state)
Find userIds of contacts
Update 2:
I managed to fetch the users, with which there are already dialogs. This is enough for my use case, however, loading all contacts would be perfect. This is how to load users from existing dialogs:
private int getUserId(String phone) throws InterruptedException {
TLRequestMessagesGetDialogs dialogs = new TLRequestMessagesGetDialogs();
dialogs.setOffsetId(0);
dialogs.setLimit(20);
dialogs.setOffsetPeer(new TLInputPeerUser());
CountDownLatch latch = new CountDownLatch(1);
api.doRpcCallNonAuth(dialogs, 1500, new RpcCallback<TLAbsDialogs>() {
#Override
public void onResult(TLAbsDialogs tlAbsDialogs) {
Log.d(TAG, "----------------------getUsers--------------------");
for(TLAbsUser absUser : ((TLDialogs) tlAbsDialogs).getUsers()) {
users.add((TLUser) absUser);
}
latch.countDown();
}
#Override
public void onError(int i, String s) {
Log.d(TAG, "----------------------getUsers ERROR--------------------");
latch.countDown();
}
});
latch.await();
for(TLUser user : users) {
if(user.getPhone().equals(phone)) {
return user.getId();
}
}
return 0;
}
After second update this is left:
Saving the session state (and signin state)
Get user ids from contacts instead of dialogs
I am implementing Facebook friends invite in my android app and I need to get to how many friends user sent app request so that I can award him some points.
What I have done so far is as below
WebDialog requestsDialog = (new WebDialog.RequestsDialogBuilder(this,
sessiob, params)).setOnCompleteListener(
new OnCompleteListener() {
#Override
public void onComplete(Bundle values,FacebookException error) {
if (error != null) {
} else {
final String requestId = values.getString("request");
final String[] requestArr1 = values.getStringArray("to");
if (requestId != null) {
Log.e("RequestId1",requestId + "\n" + values.toString());
else {
Toast.makeText(Login.this,"Request cancelled", Toast.LENGTH_SHORT).show();
}
}
}
}).build();
And the Bundle value I am getting is Bundle[{to[0]=808411111111111,to[1]=151584774222222, request=879734911111111}]
While my above code final String requestId = values.getString("request"); working fine however values.getStringArray("to"); giving me Null.
I want to know value "to" inside the Bundle is a StringArray or not and if yes then what's wrong in my extraction process.
Check out this example you will find your all answers
Simple facebook
I have a list that is populated by the package manager and it dieplays a list of all system apps. I need to find a way to "filter" that list so only specific apps show. I have a list of apps that I will "allow" the user to manimpulate.
Is that possible to show ONLY those apps out of all system apps? I mean filter for their name and if found on device' then show on the list?
Thank you so much for any advice! All is appreciated!
This is the relevant code that populates the list (the parent is fragment and the list is being created in async task).
private class GearAppsToDebloat extends AsyncTask<Void, Void, Void> {
private ProgressDialog progress = null;
//Using package manager to get all installed apps on the device
final List<PackageInfo> packageList = packageManager
.getInstalledPackages(PackageManager.GET_META_DATA);
final List<PackageInfo> packageList1 = packageManager
.getInstalledPackages(0);
#Override
protected Void doInBackground(Void... params) {
try {
packageList1.clear();
for (int n = 0; n < packageList.size(); n++) {
PackageInfo PackInfo = packageList.get(n);
// List only system apps
if (((PackInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM
) != 0) == true)
{
try {
packageList1.add(packageList.get(n));
// Sort App list on name basis
Collections.sort(packageList1, new Comparator<PackageInfo>()
{
public int compare(PackageInfo o1, PackageInfo o2) {
// Return sorted list of packages
return o1.applicationInfo.loadLabel(getActivity().getPackageManager()).toString()
.compareToIgnoreCase(o2.applicationInfo.loadLabel(getActivity().getPackageManager())
.toString());
}
});
} catch (NullPointerException e) {
e.printStackTrace();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Sorry, I just did this in C# in Xamarin but I'm sure you can write the equivalent.
I got the needed applications from using a lambda expression on the GetInstalledApplications method. It selects the application if the ClassName is present in the list of apps I am looking for.
This also provided me with a very quick return, using other methods I've come across took about 8 seconds to do the same task.
const string Facebook = "com.facebook.katana.app.FacebookApplication";
const string Email = "com.android.email.Email";
const string Whatsapp = "com.whatsapp.App";
const string Twitter = "com.twitter.android.TwitterApplication";
private IEnumerable<string> AvailableApps;
private void SearchAvailableApps()
{
List<string> apps = new List<string>() { Facebook, Email, Whatsapp, Twitter };
PackageManager pm = this.Activity.PackageManager;
AvailableApps = pm.GetInstalledApplications(0).Where(x => apps.Contains(x.ClassName)).Select(x => x.ClassName);
}
You should now have the list of apps that you know the user has installed on his/her device.