Wednesday, June 5, 2013

Android In-App Integration Tutorial

Android In-App Integration Tutorial

Today I am going post about In-App Billing.
In-app products are the digital goods that you offer for sale from inside your application to users. Examples of digital goods includes in-game currency, application feature upgrades that enhance the user experience, and new content for your application.

Below i explained step by step. How to Integrate In-App Billing in Your Application.

*Note.
Don't Test In-App In emulator or unsigned application.

AndroidManifest.xml file you need to give permission 
<uses-permission android:name="com.android.vending.BILLING" />

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.vj.test"
    android:versionCode="9"
    android:versionName="1.9" >

    <uses-permission android:name="com.android.vending.BILLING" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:icon="@drawable/icon"
        android:label="@string/app_name" >
        <activity
            android:name="com.vj.test.Mainactivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name="com.vj.test.BillingService" />

        <receiver android:name="com.vj.test.BillingReceiver" >
            <intent-filter>
                <action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
                <action android:name="com.android.vending.billing.RESPONSE_CODE" />
                <action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
            </intent-filter>
        </receiver>
    </application>

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="16" />


</manifest>


Then come to Activity class

here you need to add your in-app product ID
private static final CatalogEntry[] CATALOG = new CatalogEntry[] {
                    new CatalogEntry("Your Product ID", R.string.abc123, Managed.MANAGED),
                    new CatalogEntry("Your Product ID ", R.string.abc1234, Managed.MANAGED),
                    new CatalogEntry("Your Product ID ", R.string.ab123200, Managed.MANAGED)

       };


Full Source Code Activity Class 

package com.vj.test;

import java.util.HashSet;
import java.util.Locale;
import java.util.Set;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.SimpleCursorAdapter;
import android.widget.Spinner;
import android.widget.Toast;

import com.vj.test.BillingService.RequestPurchase;
import com.vj.test.BillingService.RestoreTransactions;
import com.vj.test.Consts.PurchaseState;
import com.vj.test.Consts.ResponseCode;

public class Mainactivity extends Activity implements OnClickListener,
             OnItemSelectedListener {
       private static final String TAG = "Dungeons";
       private static final String DB_INITIALIZED = "db_initialized";
       private DungeonsPurchaseObserver mDungeonsPurchaseObserver;
       private Handler mHandler;
       private BillingService mBillingService;
       private Button mBuyButton;
       private Spinner mSelectItemSpinner;
       private PurchaseDatabase mPurchaseDatabase;
       private Cursor mOwnedItemsCursor;
       private Set<String> mOwnedItems = new HashSet<String>();
       private String mPayloadContents = null;
       private static final int DIALOG_CANNOT_CONNECT_ID = 1;
       private static final int DIALOG_BILLING_NOT_SUPPORTED_ID = 2;
       private static final int DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID = 3;

       private enum Managed {
             MANAGED, UNMANAGED, SUBSCRIPTION
       }

       private class DungeonsPurchaseObserver extends PurchaseObserver {
             public DungeonsPurchaseObserver(Handler handler) {
                    super(Mainactivity.this, handler);
             }

             @Override
             public void onBillingSupported(boolean supported, String type) {
                    if (Consts.DEBUG) {
                           Log.i(TAG, "supported: " + supported);
                    }
                    if ((type == null) || type.equals(Consts.ITEM_TYPE_INAPP)) {
                           if (supported) {
                                 restoreDatabase();
                                 mBuyButton.setEnabled(true);
                           } else {
                                 showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
                           }
                    } else if (type.equals(Consts.ITEM_TYPE_SUBSCRIPTION)) {
                           mCatalogAdapter.setSubscriptionsSupported(supported);
                    } else {
                           showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
                    }
             }

             @Override
             public void onPurchaseStateChange(PurchaseState purchaseState,
                           String itemId, int quantity, long purchaseTime,
                           String developerPayload) {
                    if (Consts.DEBUG) {
                           Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " "
                                        + purchaseState);
                    }

                    if (developerPayload == null) {
                           logProductActivity(itemId, purchaseState.toString());
                    } else {
                           logProductActivity(itemId, purchaseState + "\n\t"
                                        + developerPayload);
                    }

                    if (purchaseState == PurchaseState.PURCHASED) {
                           mOwnedItems.add(itemId);

                           for (CatalogEntry e : CATALOG) {
                                 if (e.sku.equals(itemId)
                                               && e.managed.equals(Managed.SUBSCRIPTION)) {
                                 }
                           }
                    }
                    mCatalogAdapter.setOwnedItems(mOwnedItems);
                    mOwnedItemsCursor.requery();
             }

             @Override
             public void onRequestPurchaseResponse(RequestPurchase request,
                           ResponseCode responseCode) {
                    if (Consts.DEBUG) {
                           Log.d(TAG, request.mProductId + ": " + responseCode);
                    }
                    if (responseCode == ResponseCode.RESULT_OK) {
                           if (Consts.DEBUG) {
                                 Log.i(TAG, "purchase was successfully sent to server");
                           }
                           logProductActivity(request.mProductId,
                                        "sending purchase request");
                    } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
                           if (Consts.DEBUG) {
                                 Log.i(TAG, "user canceled purchase");
                           }
                           logProductActivity(request.mProductId,
                                        "dismissed purchase dialog");
                    } else {
                           if (Consts.DEBUG) {
                                 Log.i(TAG, "purchase failed");
                           }
                           logProductActivity(request.mProductId,
                                        "request purchase returned " + responseCode);
                    }
             }

             @Override
             public void onRestoreTransactionsResponse(RestoreTransactions request,
                           ResponseCode responseCode) {
                    if (responseCode == ResponseCode.RESULT_OK) {
                           if (Consts.DEBUG) {
                                 Log.d(TAG, "completed RestoreTransactions request");
                           }
                           SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
                           SharedPreferences.Editor edit = prefs.edit();
                           edit.putBoolean(DB_INITIALIZED, true);
                           edit.commit();
                    } else {
                           if (Consts.DEBUG) {
                                 Log.d(TAG, "RestoreTransactions error: " + responseCode);
                           }
                    }
             }
       }

       private static class CatalogEntry {
             public String sku;
             public int nameId;
             public Managed managed;

             public CatalogEntry(String sku, int nameId, Managed managed) {
                    this.sku = sku;
                    this.nameId = nameId;
                    this.managed = managed;
             }
       }

       private static final CatalogEntry[] CATALOG = new CatalogEntry[] {
                    new CatalogEntry("your product id", R.string.abc123, Managed.MANAGED),
                    new CatalogEntry("your product id", R.string.abc1234, Managed.MANAGED),
                    new CatalogEntry("your product id", R.string.ab123200, Managed.MANAGED)

       };

       private String mItemName;
       private String mSku;
       private Managed mManagedType;
       private CatalogAdapter mCatalogAdapter;

       @SuppressWarnings("deprecation")
       @Override
       public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.main);

             mHandler = new Handler();
             mDungeonsPurchaseObserver = new DungeonsPurchaseObserver(mHandler);
             mBillingService = new BillingService();
             mBillingService.setContext(this);

             mPurchaseDatabase = new PurchaseDatabase(this);
             setupWidgets();
             ResponseHandler.register(mDungeonsPurchaseObserver);
             if (!mBillingService.checkBillingSupported()) {
                    showDialog(DIALOG_CANNOT_CONNECT_ID);
             }

             if (!mBillingService
                           .checkBillingSupported(Consts.ITEM_TYPE_SUBSCRIPTION)) {
                    showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
             }
       }

       @Override
       protected void onStart() {
             super.onStart();
             ResponseHandler.register(mDungeonsPurchaseObserver);
             initializeOwnedItems();
       }

       @Override
       protected void onStop() {
             super.onStop();
             ResponseHandler.unregister(mDungeonsPurchaseObserver);
       }

       @Override
       protected void onDestroy() {
             super.onDestroy();
             mPurchaseDatabase.close();
             mBillingService.unbind();
       }

       @Override
       protected void onSaveInstanceState(Bundle outState) {
             super.onSaveInstanceState(outState);

       }

       @Override
       protected void onRestoreInstanceState(Bundle savedInstanceState) {
             super.onRestoreInstanceState(savedInstanceState);

       }

       @Override
       protected Dialog onCreateDialog(int id) {
             switch (id) {
             case DIALOG_CANNOT_CONNECT_ID:
                    return createDialog(R.string.cannot_connect_title,
                                 R.string.cannot_connect_message);
             case DIALOG_BILLING_NOT_SUPPORTED_ID:
                    return createDialog(R.string.billing_not_supported_title,
                                 R.string.billing_not_supported_message);
             case DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID:
                    return createDialog(R.string.subscriptions_not_supported_title,
                                 R.string.subscriptions_not_supported_message);
             default:
                    return null;
             }
       }

       private Dialog createDialog(int titleId, int messageId) {
             String helpUrl = replaceLanguageAndRegion(getString(R.string.help_url));
             if (Consts.DEBUG) {
                    Log.i(TAG, helpUrl);
             }
             final Uri helpUri = Uri.parse(helpUrl);

             AlertDialog.Builder builder = new AlertDialog.Builder(this);
             builder.setTitle(titleId)
                           .setIcon(android.R.drawable.stat_sys_warning)
                           .setMessage(messageId)
                           .setCancelable(false)
                           .setPositiveButton(android.R.string.ok, null)
                           .setNegativeButton(R.string.learn_more,
                                        new DialogInterface.OnClickListener() {
                                               @Override
                                               public void onClick(DialogInterface dialog,
                                                            int which) {
                                                     Intent intent = new Intent(Intent.ACTION_VIEW,
                                                                   helpUri);
                                                     startActivity(intent);
                                               }
                                        });
             return builder.create();
       }

       private String replaceLanguageAndRegion(String str) {
             if (str.contains("%lang%") || str.contains("%region%")) {
                    Locale locale = Locale.getDefault();
                    str = str.replace("%lang%", locale.getLanguage().toLowerCase());
                    str = str.replace("%region%", locale.getCountry().toLowerCase());
             }
             return str;
       }

       private void setupWidgets() {

             mBuyButton = (Button) findViewById(R.id.buy_button);
             mBuyButton.setEnabled(false);
             mBuyButton.setOnClickListener(this);
             mSelectItemSpinner = (Spinner) findViewById(R.id.item_choices);
             mCatalogAdapter = new CatalogAdapter(this, CATALOG);
             mSelectItemSpinner.setAdapter(mCatalogAdapter);
             mSelectItemSpinner.setOnItemSelectedListener(this);

             mOwnedItemsCursor = mPurchaseDatabase.queryAllPurchasedItems();
             startManagingCursor(mOwnedItemsCursor);
             String[] from = new String[] {
                           PurchaseDatabase.PURCHASED_PRODUCT_ID_COL,
                           PurchaseDatabase.PURCHASED_QUANTITY_COL };
             int[] to = new int[] { R.id.item_name, R.id.item_quantity };
             new SimpleCursorAdapter(this, R.layout.item_row, mOwnedItemsCursor,
                           from, to);

       }

       private void prependLogEntry(CharSequence cs) {
             SpannableStringBuilder contents = new SpannableStringBuilder(cs);
             contents.append('\n');

       }

       private void logProductActivity(String product, String activity) {
             SpannableStringBuilder contents = new SpannableStringBuilder();
             contents.append(Html.fromHtml("<b>" + product + "</b>: "));
             contents.append(activity);
             prependLogEntry(contents);
       }

       private void restoreDatabase() {
             SharedPreferences prefs = getPreferences(MODE_PRIVATE);
             boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
             if (!initialized) {
                    mBillingService.restoreTransactions();
                    Toast.makeText(this, R.string.restoring_transactions,
                                 Toast.LENGTH_LONG).show();
             }
       }

       private void initializeOwnedItems() {
             new Thread(new Runnable() {
                    @Override
                    public void run() {
                           doInitializeOwnedItems();
                    }
             }).start();
       }

       private void doInitializeOwnedItems() {
             Cursor cursor = mPurchaseDatabase.queryAllPurchasedItems();
             if (cursor == null) {
                    return;
             }

             final Set<String> ownedItems = new HashSet<String>();
             try {
                    int productIdCol = cursor
                                  .getColumnIndexOrThrow(PurchaseDatabase.PURCHASED_PRODUCT_ID_COL);
                    while (cursor.moveToNext()) {
                           String productId = cursor.getString(productIdCol);
                           ownedItems.add(productId);
                    }
             } finally {
                    cursor.close();
             }

             mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                           mOwnedItems.addAll(ownedItems);
                           mCatalogAdapter.setOwnedItems(mOwnedItems);
                    }
             });
       }

       @Override
       public void onClick(View v) {
             if (v == mBuyButton) {
                    if (Consts.DEBUG) {
                           Log.d(TAG, "buying: " + mItemName + " sku: " + mSku);
                    }

                    if ((mManagedType != Managed.SUBSCRIPTION)
                                 && !mBillingService.requestPurchase(mSku,
                                               Consts.ITEM_TYPE_INAPP, mPayloadContents)) {
                           showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
                    } else if (!mBillingService.requestPurchase(mSku,
                                 Consts.ITEM_TYPE_SUBSCRIPTION, mPayloadContents)) {
                           showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
                    }
             }

       }

       @Override
       public void onItemSelected(AdapterView<?> parent, View view, int position,
                    long id) {
             mItemName = getString(CATALOG[position].nameId);
             mSku = CATALOG[position].sku;
             mManagedType = CATALOG[position].managed;
       }

       @Override
       public void onNothingSelected(AdapterView<?> arg0) {
       }

       private static class CatalogAdapter extends ArrayAdapter<String> {
             private CatalogEntry[] mCatalog;
             private Set<String> mOwnedItems = new HashSet<String>();
             private boolean mIsSubscriptionsSupported = false;

             public CatalogAdapter(Context context, CatalogEntry[] catalog) {
                    super(context, android.R.layout.simple_spinner_item);
                    mCatalog = catalog;
                    for (CatalogEntry element : catalog) {
                           add(context.getString(element.nameId));
                    }
                    setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
             }

             public void setOwnedItems(Set<String> ownedItems) {
                    mOwnedItems = ownedItems;
                    notifyDataSetChanged();
             }

             public void setSubscriptionsSupported(boolean supported) {
                    mIsSubscriptionsSupported = supported;
             }

             @Override
             public boolean areAllItemsEnabled() {
                    return false;
             }

             @Override
             public boolean isEnabled(int position) {

                    CatalogEntry entry = mCatalog[position];
                    if ((entry.managed == Managed.MANAGED)
                                 && mOwnedItems.contains(entry.sku)) {
                           return false;
                    }
                    if ((entry.managed == Managed.SUBSCRIPTION)
                                 && !mIsSubscriptionsSupported) {
                           return false;
                    }
                    return true;
             }

             @Override
             public View getDropDownView(int position, View convertView,
                           ViewGroup parent) {

                    View view = super.getDropDownView(position, convertView, parent);
                    view.setEnabled(isEnabled(position));
                    return view;
             }
       }
}

Then Add In-App product 
Go to Developer account  -> Add Apk Signed Apk -> Then you can able to see In-App product Navigation then click -> Add New Product - >Manage Product -> Enter Product ID and Mandatory field -> Then Make it  Active Product.
Check the below Screen Shot 

*This product  ID only you have to add in above the code for every product.



Then you need base64EncodedPublicKey 

This key you need to add Security.Java class line no 81

Cehck this screen shot 




Copy this key paste it. 

Security.Java class full source code

package com.vj.test;

import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.HashSet;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.text.TextUtils;
import android.util.Log;

import com.example.dungeons.util.Base64;
import com.example.dungeons.util.Base64DecoderException;
import com.vj.test.Consts.PurchaseState;

public class Security {
       private static final String TAG = "Security";

       private static final String KEY_FACTORY_ALGORITHM = "RSA";
       private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
       private static final SecureRandom RANDOM = new SecureRandom();

       private static HashSet<Long> sKnownNonces = new HashSet<Long>();

       public static class VerifiedPurchase {
             public PurchaseState purchaseState;
             public String notificationId;
             public String productId;
             public String orderId;
             public long purchaseTime;
             public String developerPayload;

             public VerifiedPurchase(PurchaseState purchaseState,
                           String notificationId, String productId, String orderId,
                           long purchaseTime, String developerPayload) {
                    this.purchaseState = purchaseState;
                    this.notificationId = notificationId;
                    this.productId = productId;
                    this.orderId = orderId;
                    this.purchaseTime = purchaseTime;
                    this.developerPayload = developerPayload;
             }
       }

       public static long generateNonce() {
             long nonce = RANDOM.nextLong();
             sKnownNonces.add(nonce);
             return nonce;
       }

       public static void removeNonce(long nonce) {
             sKnownNonces.remove(nonce);
       }

       public static boolean isNonceKnown(long nonce) {
             return sKnownNonces.contains(nonce);
       }

       public static ArrayList<VerifiedPurchase> verifyPurchase(String signedData,
                    String signature) {
             if (signedData == null) {
                    Log.e(TAG, "data is null");
                    return null;
             }
             if (Consts.DEBUG) {
                    Log.i(TAG, "signedData: " + signedData);
             }
             boolean verified = false;
             if (!TextUtils.isEmpty(signature)) {

                    String base64EncodedPublicKey = "Here Your Public KEey";
                    PublicKey key = Security.generatePublicKey(base64EncodedPublicKey);
                    verified = Security.verify(key, signedData, signature);
                    if (!verified) {
                           Log.w(TAG, "signature does not match data.");
                           return null;
                    }
             }

             JSONObject jObject;
             JSONArray jTransactionsArray = null;
             int numTransactions = 0;
             long nonce = 0L;
             try {
                    jObject = new JSONObject(signedData);
                    nonce = jObject.optLong("nonce");
                    jTransactionsArray = jObject.optJSONArray("orders");
                    if (jTransactionsArray != null) {
                           numTransactions = jTransactionsArray.length();
                    }
             } catch (JSONException e) {
                    return null;
             }

             if (!Security.isNonceKnown(nonce)) {
                    Log.w(TAG, "Nonce not found: " + nonce);
                    return null;
             }

             ArrayList<VerifiedPurchase> purchases = new ArrayList<VerifiedPurchase>();
             try {
                    for (int i = 0; i < numTransactions; i++) {
                           JSONObject jElement = jTransactionsArray.getJSONObject(i);
                           int response = jElement.getInt("purchaseState");
                           PurchaseState purchaseState = PurchaseState.valueOf(response);
                           String productId = jElement.getString("productId");
                           @SuppressWarnings("unused")
                           String packageName = jElement.getString("packageName");
                           long purchaseTime = jElement.getLong("purchaseTime");
                           String orderId = jElement.optString("orderId", "");
                           String notifyId = null;
                           if (jElement.has("notificationId")) {
                                 notifyId = jElement.getString("notificationId");
                           }
                           String developerPayload = jElement.optString(
                                        "developerPayload", null);

                           if ((purchaseState == PurchaseState.PURCHASED) && !verified) {
                                 continue;
                           }
                           purchases.add(new VerifiedPurchase(purchaseState, notifyId,
                                        productId, orderId, purchaseTime, developerPayload));
                    }
             } catch (JSONException e) {
                    Log.e(TAG, "JSON exception: ", e);
                    return null;
             }
             removeNonce(nonce);
             return purchases;
       }

       public static PublicKey generatePublicKey(String encodedPublicKey) {
             try {
                    byte[] decodedKey = Base64.decode(encodedPublicKey);
                    KeyFactory keyFactory = KeyFactory
                                 .getInstance(KEY_FACTORY_ALGORITHM);
                    return keyFactory
                                 .generatePublic(new X509EncodedKeySpec(decodedKey));
             } catch (NoSuchAlgorithmException e) {
                    throw new RuntimeException(e);
             } catch (InvalidKeySpecException e) {
                    Log.e(TAG, "Invalid key specification.");
                    throw new IllegalArgumentException(e);
             } catch (Base64DecoderException e) {
                    Log.e(TAG, "Base64 decoding failed.");
                    throw new IllegalArgumentException(e);
             }
       }

       public static boolean verify(PublicKey publicKey, String signedData,
                    String signature) {
             if (Consts.DEBUG) {
                    Log.i(TAG, "signature: " + signature);
             }
             Signature sig;
             try {
                    sig = Signature.getInstance(SIGNATURE_ALGORITHM);
                    sig.initVerify(publicKey);
                    sig.update(signedData.getBytes());
                    if (!sig.verify(Base64.decode(signature))) {
                           Log.e(TAG, "Signature verification failed.");
                           return false;
                    }
                    return true;
             } catch (NoSuchAlgorithmException e) {
                    Log.e(TAG, "NoSuchAlgorithmException.");
             } catch (InvalidKeyException e) {
                    Log.e(TAG, "Invalid key specification.");
             } catch (SignatureException e) {
                    Log.e(TAG, "Signature exception.");
             } catch (Base64DecoderException e) {
                    Log.e(TAG, "Base64 decoding failed.");
             }
             return false;
       }
}


Then publish the application 
Wait for several hours.

Then download application enjoy with In-App Billing













67 comments:

  1. Meine Hände wanderten in ihren Schritt und ich begann Sie zu
    fummeln. Der Mensch ist ein Gewohnheitstier. Jedoch wenn du mir unverschämt wirst, fahre ich unsere
    Krallen aus und kratze ebenso schon mal.

    Besuchen Sie auch meine Seite live strip cam

    ReplyDelete
  2. Ich stehe darаuf, gebumst zu dürften, bis ich einеn Höhepunkt nach ԁieѕem anderen bekomme.
    Willst du еbenfallѕ einmal meinen hеißen Arѕch nehmen und mich
    in meinen Hinteгn νerwöhnen. Ich hаbe schon unzählige
    Kеrlе glücklich gemасht und miсh vοn
    viеlеn Herren bumsen lassen.

    Moechteѕt du dir mеine Pаge ansehen? kleinanzeigen österreich

    ReplyDelete
  3. Hi I am so thrilled I found your blog page, I really found you by accident,
    while I was browsing on Google for something else, Regardless I am here now and would
    just like to say thanks a lot for a remarkable post and a all
    round exciting blog (I also love the theme/design), I
    don't have time to look over it all at the moment but I have book-marked it and also added your RSS feeds, so when I have time I will be back to read a lot more, Please do keep up the awesome work.

    Visit my web-site Louis vuitton pas Cher

    ReplyDelete
  4. I have done these tutorial today. Its really amazing to integrated a third party app into one app. Thanks for sharing.

    ReplyDelete
  5. In future,Android application development should be good level compared to other fields.Because Android application plays an important things in people life.
    Website Development Companies Bangalore

    ReplyDelete
  6. This is the clear lesson of all.
    But I do not understand how this script access to the full application and all buttons after purchase.
    Please reply to email weeeeeez@yandex.ru

    ReplyDelete
  7. Thanks for sharing this tutorial

    ReplyDelete
  8. Now the process of Android application development is very difficult.why i saying like this because,more competition going on this field.
    Outsourcing Website Designer

    ReplyDelete
  9. A well trained android developers should be having ability to make the useful mobile application to people.
    Web Design Companies | Web Designing Company

    ReplyDelete
  10. This comment has been removed by the author.

    ReplyDelete
  11. Hy vọng các bạn sẽ có nhiều bài viết hay và thú vị hơn. Xin cảm ơn rất nhiều

    chó Bull Pháp

    bán chó bull pháp

    chó bull pháp giá bao nhiêu

    mua chó bull pháp

    ReplyDelete
  12. Aquest és un dels millors articles de la història. Gràcies per compartir. Li desitjo sort i èxit!


    giảo cổ lam 5 lá

    giảo cổ lam 7 lá

    giảo cổ lam khô

    giảo cổ lam 9 lá

    ReplyDelete
  13. Your post is just outstanding! thanks for such a post, its really going great work.

    Website Designing Company in Delhi

    ReplyDelete
  14. This comment has been removed by the author.

    ReplyDelete
  15. Vanskeligheter( van bi ) vil passere. På samme måte som( van điện từ ) regnet utenfor( van giảm áp ) vinduet, hvor nostalgisk( van xả khí ) er det som til slutt( van cửa ) vil fjerne( van công nghiệp ) himmelen.

    ReplyDelete
  16. It is amazing and wonderful to visit your Blog.Thanks for sharing this information,this is useful to us. Keep posting!

    ReplyDelete
  17. Hello,
    I love your blog so much. i would like to inform you that there's plenty of good information on this blog, I loved to read it and I think people will get a lot more support from this blog. Thanks for sharing this informative blog, please keep it up and share some special posts with us in the future.
    about us
    Mobile app development companies in Bangalore
    we are also best Mobile app development company in Bangalore
    Mobile app development companies in India
    Mobile app development companies in India

    ReplyDelete
  18. This is a very good article and nice collection of information , would love to read more of such blogs and also know our services

    Mobile application development in India
    Mobile application development in Delhi

    ReplyDelete
  19. A well informative blog which speaks about Mobile applications hands-on experience like these helps us to become an Android application developer.

    ReplyDelete
  20. Hey Nice Blog!! Thanks For Sharing!!!Wonderful blog & good post.Its really helpful for me, waiting for a more new post. Keep Blogging!
    industrial automation systems

    bockchain development


    ReplyDelete
  21. HI guys,
    This is a very good post, and I like it very much. For us, it's insightful and helpful. For a long time, I've been looking for this and I'm just really pleased to say that I'm working with this stuff as well. Thanks for sharing this with us.

    Digital Marketing Company in Jaipur
    Digital Marketing company In Delhi
    Digital Marketing Company in Bangalore
    SEO Company in Jaipur
    Website development Company in Jaipur
    PPC Company in Jaipur
    Digital Marketing Company in USA

    ReplyDelete
  22. Thanks for your great post.We are the leading seo company in mumbai. Hire our seo agency in mumbai today for seo services in mumbai

    ReplyDelete
  23. Nice post really useful information. We are the leading Ecommerce Website Designers in Bangalore . Hire our ecommerce website designers in bangalore for ecommerce website development services.

    ReplyDelete
  24. thanks for sharing this.
    get the best blog to read -https://lilacinfotech.com/blog/90/top-android-app-development-trends-to-lookout-in-2021

    ReplyDelete
  25. This is genuinely an awesome read for me. I have bookmarked it and I am anticipating perusing new articles. Keep doing awesome!
    SEO Company Pune
    SEO Pune

    ReplyDelete
  26. Hey, Thanks for asking.

    Did you know that there are limited number of global biomedical researchers and scientists to biospecimen suppliers across the globe.

    Cancer Samples
    FFPE Samples
    Human Specimens

    ReplyDelete
  27. I’ve been surfing on the web more than 3 hours today, yet I never found any stunning article like yours. It’s alluringly worth for me. As I would see it,
    if all web proprietors and bloggers made puzzling substance as you did, the net will be in a general sense more beneficial than at whatever point in late memory.

    Packers and movers in Nagpur | Movers and packers in Nagpur
    home shifting services in Nagpur

    ReplyDelete
  28. Great article. Keep writing such kind of information on your blog.
    Unified Communications

    ReplyDelete
  29. Dịch vụ cho thuê xe nâng giá rẻ

    Thuê xe nâng xếp dỡ máy móc

    Dịch vụ nâng cẩu hàng nặng

    Hotline: 091.351.9810- 0912.018.299

    Tel: 024.3208.4888

    ReplyDelete

Check out this may be help you

Related Posts Plugin for WordPress, Blogger...