I'm migrating an app from the play store to the Amazon App store, considering that in this way it will be avilable for Windows 11 too.
For this to make something very fast and easy i made an activity called PurchaseActivity which contains the codes brought by the amazon IAP guide PDF.
The activity is called from a "buy now" button of a dialog window with the following code:
public class PurchaseActivity extends Activity {
String parentSKU = "com.amazon.sample.iap.subscription.mymagazine";
//Define UserId and MarketPlace
private String currentUserId;
private String currentMarketplace;
private ProgressDialog progress;
#Override
protected void onStart(){
super.onStart();
progress = new ProgressDialog(this);
progress.setTitle("Purchasing");
progress.setMessage("Wait while making the purchase...");
progress.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
progress.dismiss();//dismiss dialog
finish();
}
});
progress.setCancelable(false); // disable dismiss by tapping outside of the dialog
progress.show();
PurchasingService.registerListener(this, purchasingListener);
PurchasingService.purchase(parentSKU);
}
#Override
protected void onResume() {
super.onResume();
//getUserData() will query the Appstore for the Users information
PurchasingService.getUserData();
//getPurchaseUpdates() will query the Appstore for any previous purchase
PurchasingService.getPurchaseUpdates(true);
//getProductData will validate the SKUs with Amazon Appstore
final Set<String> productSkus = new HashSet<String>();
productSkus.add(parentSKU);
PurchasingService.getProductData(productSkus);
Log.v("Validating SKUs", "Validating SKUs with Amazon");
}
PurchasingListener purchasingListener = new PurchasingListener() {
#Override
public void onUserDataResponse(UserDataResponse response) {
final UserDataResponse.RequestStatus status = response.getRequestStatus();
switch (status) {
case SUCCESSFUL:
currentUserId = response.getUserData().getUserId();
currentMarketplace = response.getUserData().getMarketplace();
Log.v("IAP SDK", "loaded userdataResponse");
break;
case FAILED:
case NOT_SUPPORTED:
// Fail gracefully.
Log.v("IAP SDK", "loading failed");
break;
}
}
#Override
public void onProductDataResponse(ProductDataResponse productDataResponse) {
switch (productDataResponse.getRequestStatus()) {
case SUCCESSFUL:
//get informations for all IAP Items (parent SKUs)
final Map<String, Product> products = productDataResponse.getProductData();
for (String key : products.keySet()) {
Product product = products.get(key);
Log.v("Product:", String.format("Product: %s\n Type: %s\n SKU: %s\n Price: %s\n Description: %s\n", product.getTitle(), product.getProductType(),
product.getSku(), product.getPrice(), product.getDescription()));
}
//get all unavailable SKUs
for (String s : productDataResponse.getUnavailableSkus()) {
Log.v("Unavailable SKU:" + s, "Unavailable SKU:" + s);
}
break;
case FAILED:
Log.v("FAILED", "FAILED");
progress.dismiss();
finish();
break;
}
}
#Override
public void onPurchaseResponse(PurchaseResponse purchaseResponse) {
switch (purchaseResponse.getRequestStatus()) {
case SUCCESSFUL:
PurchasingService.notifyFulfillment(purchaseResponse.getReceipt().getReceiptId(),
FulfillmentResult.FULFILLED);
break;
case FAILED:
progress.dismiss();
finish();
break;
}
}
#Override
public void onPurchaseUpdatesResponse(PurchaseUpdatesResponse response) {
// Process receipts
switch (response.getRequestStatus()) {
case SUCCESSFUL:
for (final Receipt receipt : response.getReceipts()) {
// Process receipts
if (!receipt.isCanceled()) {
// sharedprefs
SharedPreferences sharedPreference = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
SharedPreferences.Editor sharedPrefEditor = sharedPreference.edit();
sharedPrefEditor.putBoolean("isPro",true);
sharedPrefEditor.apply();
progress.dismiss();
finish();
}
}
if (response.hasMore()) {
PurchasingService.getPurchaseUpdates(true);
}
break;
case FAILED:
Log.d("FAILED", "FAILED");
progress.dismiss();
finish();
break;
}
}
};
}
Yeah i know i should not call all that stuff in the onStart() method but i'll make an onCreate() with a UI later.
As you can see from this code, i'm testing in sandbox mode.
THE PROBLEM: Actually when the activity starts, i see progressDialog, and i read in the debug logs that "V/Validating SKUs: Validating SKUs with Amazon" but i don't see then the amazon buy window. It also seems like the listener code is never called, even if i put some breakpoints in there, they're never reached which is very weird considering that apprently it gets initialized and called successfully by the method "PurchasingService.registerListener(this, purchasingListener)"
Any help would be very apreciated!
Thanks and have a good evening
You need to call registerListener in your Application's onCreate, otherwise Amazon fails to detect there's an Activity being displayed and it will not show the purchase dialog.
Related
From the Flutter side, using the PlatformChannel, I am navigating to an Android Java activity, and doing some processes.
The activity successfully opens and I'm able to do the functionality and have the final result of it.
How may I navigate back to the Flutter side to a specific page and pass a value?
P.S.: without going back to the same page and then redirecting to the
next page.
On the Flutter side:
I have these variables
/// Filters Method Channel
final filtersChannel = const MethodChannel('flutter.native/filters');
/// Filters Method Channel
final filtersResultChannel = const MethodChannel("flutter.native/result_filters");
I have a floatingActionButton with this function which invokes a MethodChannel
Future<void> startNewActivity() async {
try {
await filtersChannel.invokeMethod('open_filters');
} on PlatformException catch (e) {
debugPrint("Failed to Invoke: '${e.message}'.");
}
}
On the MainActivity.java
On the protected void onCreate(#Nullable Bundle savedInstanceState) function, I'm starting an activity which has the AR video recording like this:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, FiltersActivity.class);
startActivity(intent);
}
On the FiltersActivity.java
On the public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) function
I’m defining and invoking my two channels:
The flutter.native/result_filters channel which builds the UI and
the functionality.
The flutter.native/filters channel which returns the final result.
Here:
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
String resultFiltersChannelIdentifier = "flutter.native/result_filters";
filtersResultChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), resultFiltersChannelIdentifier);
String filtersChannelIdentifier = "flutter.native/filters";
MethodChannel filtersChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), filtersChannelIdentifier);
filtersChannel.setMethodCallHandler(this::filtersMethodCallHandler);
}
Then, the flutter.native/filters displays the UI using the filtersMethodCallHandler function. Here:
private void filtersMethodCallHandler(MethodCall methodCall, MethodChannel.Result result) {
if (methodCall.method.equals("open_filters")) {
openUI();
} else {
result.notImplemented();
}
}
In the openUI function, I'm assigning the record button a function, here:
recordButton.setOnClickListener(this::toggleRecording);
And here's the toggleRecording function:
public void toggleRecording(View unusedView) {
boolean recording = videoRecorder.onToggleRecord();
if (recording) {
recordButton.setImageResource(R.drawable.round_stop);
Toast.makeText(this, "Started Recording", Toast.LENGTH_SHORT).show();
} else {
recordButton.setImageResource(R.drawable.round_videocam);
Toast.makeText(this, "Recording Stopped", Toast.LENGTH_SHORT).show();
videoPath = videoRecorder.getVideoPath().getAbsolutePath();
Toast.makeText(this, "Video saved: " + videoPath, Toast.LENGTH_SHORT).show();
Log.d(TAG, "Video saved: " + videoPath);
// Send notification of updated content.
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.TITLE, "Sceneform Video");
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
values.put(MediaStore.Video.Media.DATA, videoPath);
getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
filtersResultChannel.invokeMethod("filters_result", videoPath);
finish();
}
}
As shown above, I'm invoking the filters_result method for the filtersResultChannel channel and I'm adding the videoPath to it.
And then, I'm calling the finish(); method to close the FiltersActivity and return back to the MainAvtivity which successfully returns me to the Flutter page!
BACK to the Flutter side,
I'm listening to the filtersResultChannel like this:
#override
void initState() {
super.initState();
filtersResultChannel.setMethodCallHandler(_filtersResultHandler);
}
Future _filtersResultHandler(MethodCall methodCall) async {
if (methodCall.method == "filters_result") {
final videoPath = methodCall.arguments;
if (videoPath != null && videoPath.length >= 0) {
SchedulerBinding.instance.addPostFrameCallback((_) {
debugPrint("YES YES YES => $videoPath");
setState(() {
reportStatus = videoPath;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VideoShow(clipPath: videoPath),
),
);
});
});
}
return null;
} else {
return null;
}
}
As shown above, I have a debugPrint statement, this statement prints the returned videoPath from the filtersResultChannel
<--------->
THE PROBLEM
<--------->
Even though I'm successfully getting the videoPath value and successfully returning back to the Flutter page, I'm NOT able to use it!!
The setState(); doesn't update the UI NOR navigate to the next screen, the VideoShow screen!
HOW MAY I FIX SUCH AN ISSUE?
I have a Android Studio project with firebase, I already implement firebase correctly.
im trying to make a sign in with sms authentication, everything works but no sms arrives
I already configure firebase authentication
here's some code
mCallbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
#Override
public void onVerificationCompleted(PhoneAuthCredential credential) {
Toast.makeText(MainActivity.this, "Verification Complete", Toast.LENGTH_SHORT).show();
}
#Override
public void onVerificationFailed(FirebaseException e) {
Toast.makeText(MainActivity.this, "Verification Failed", Toast.LENGTH_SHORT).show();
}
#Override
public void onCodeSent(String verificationId,
PhoneAuthProvider.ForceResendingToken token) {
Toast.makeText(MainActivity.this, "Code Sent", Toast.LENGTH_SHORT).show();
}
};
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.bt_send_otp:
PhoneAuthProvider.getInstance().verifyPhoneNumber(
etPhone.getText().toString(), // Phone number to verify
1, // Timeout duration
TimeUnit.MINUTES, // Unit of timeout
this, // Activity (for callback binding)
mCallbacks); // OnVerificationStateChangedCallbacks
break;
case R.id.bt_resend_otp:
break;
case R.id.bt_verify_otp:
break;
}
}
when I press the send otp button the toast "Code Sent" appears
Thanks for the help :)
Since it is a test number, OTP will not arrive. Though for testing you can use the verification code you added with the test phone number on the console.
Firebase Console Dashboard -> Authentication -> SignIn Method -> Phone -> Phone numbers for testing.
Check out the docs for Firebase Phone Verification for Android : https://firebase.google.com/docs/auth/android/phone-auth?authuser=1#test-with-whitelisted-phone-numbers
I have this app which parses SMS and then converts them into Audio. My app users usually minimize the app and runs it all the time. But my app is getting terminated after sometime. How can i make sure my app will run till a user "terminates" it. Since the core functionality of the app is to convert SMS to audio, i need it running all the time.How can i do this ?
My current MainActivity.java
public class MainActivity extends AppCompatActivity {
TextView txtGateway, txtTime, txtAmount;
Speakerbox speakerbox;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//textView = findViewById(R.id.txt_message);
speakerbox = new Speakerbox(getApplication());
txtAmount = findViewById(R.id.tv_amount);
txtGateway = findViewById(R.id.tv_gateway);
txtTime = findViewById(R.id.tv_time);
requestSmsPermission();
}
#Override
public void onResume() {
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, new IntentFilter("otp"));
super.onResume();
}
#Override
public void onPause() {
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, new IntentFilter("otp"));
super.onPause();
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equalsIgnoreCase("otp")) {
final String message = intent.getStringExtra("message");
String gateway = intent.getStringExtra("gateway");
String time = intent.getStringExtra("time");
String amount = intent.getStringExtra("amount");
speakerbox.play(message);
txtGateway.setText(gateway);
txtTime.setText(time);
txtAmount.setText(amount);
// message is the fetching OTP
}
}
};
/**
* Requesting multiple permissions (storage and location) at once
* This uses multiple permission model from dexter
* On permanent denial opens settings dialog
*/
private void requestSmsPermission() {
Dexter.withActivity(this)
.withPermissions(
Manifest.permission.RECEIVE_SMS,
Manifest.permission.READ_SMS,
Manifest.permission.SEND_SMS,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(new MultiplePermissionsListener() {
#Override
public void onPermissionsChecked(MultiplePermissionsReport report) {
// check if all permissions are granted
if (report.areAllPermissionsGranted()) {
// Toast.makeText(getApplicationContext(), "All permissions are granted!", Toast.LENGTH_SHORT).show();
}
// check for permanent denial of any permission
if (report.isAnyPermissionPermanentlyDenied()) {
// show alert dialog navigating to Settings
showSettingsDialog();
}
}
#Override
public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {
token.continuePermissionRequest();
}
}).
withErrorListener(new PermissionRequestErrorListener() {
#Override
public void onError(DexterError error) {
Toast.makeText(getApplicationContext(), "Error occurred! ", Toast.LENGTH_SHORT).show();
}
})
.onSameThread()
.check();
}
/**
* Showing Alert Dialog with Settings option
* Navigates user to app settings
* NOTE: Keep proper title and message depending on your app
*/
private void showSettingsDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("Need Permissions");
builder.setMessage("This app needs permission to use this feature. You can grant them in app settings.");
builder.setPositiveButton("GOTO SETTINGS", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
openSettings();
}
});
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.show();
}
// navigating user to app settings
private void openSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, 101);
}
}
Use a foreground service to ensure that your app is not killed by Android. On newer version of Android, app's background process gets killed after sometime. Having a foreground service will ensure that your app stays active. Keep the service on background thread and not on the main thread.
Read more at:
https://developer.android.com/guide/components/services
and
https://androidwave.com/foreground-service-android-example/
Activities are used for user facing parts of your application. Use service instead. Most likely, your application is getting terminated by os due to low memory situation.
Use service for the core functionality if you want to run all the time in background.If user is not using your app actively yet app is taking RAM memory then OS will terminate the app to avoid out of memory.
i am fairly new to android development, and am having major troubles understanding how to implement google in app billing. I have read the official google documentation, read through quite a few tutorials, etc. The one that i have found that is the most simple to me is the following. Even then i am struggling to get it working.
http://redappz.com/micro-transactions-tutorial-iap-for-android/
What i have done is, imported all helper classes from trivial drive. Have a google dev account with a signed apk.
The logic i want for my application is when a purchase is successful in Activity A. Activity B displays a button.
public class WorkoutPlan1 extends AppCompatActivity {
private Button WorkoutPlan1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_workoutplan1);
WorkoutPlan1 = (Button) findViewById(R.id.Workout1MoreInfo);
Base code with out any google billing stuff. Basic button just to run the google billing off.
public class WorkoutPlan1 extends AppCompatActivity {
private Button WorkoutPlan1;
//*************************************************************************************************************************
// Debug tag, for logging
static final String TAG = "test";
// SKUs for our products: the premium upgrade (non-consumable)
static final String SKU_PREMIUM = "android.test.purchased";
// Does the user have the premium upgrade?
boolean mIsPremium = false;
// (arbitrary) request code for the purchase flow
static final int RC_REQUEST = 1;
// The helper object
IabHelper mHelper;
//*************************************************************************************************************************
#Override
protected void onCreate(Bundle savedInstanceState) {
//*************************************************************************************************************************
String base64EncodedPublicKey = "KEY GOES HERE FROM MY GOOGLE DEV";
//It is recommended to add more security than just pasting it in your source code;
mHelper = new IabHelper(this, base64EncodedPublicKey);
Log.d(TAG, "Starting setup.");
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
Log.d(TAG, "Setup finished.");
if (!result.isSuccess()) {
// Oh noes, there was a problem.
Log.d(TAG, "Problem setting up In-app Billing: " + result);
}
// Hooray, IAB is fully set up!
// mHelper.queryInventoryAsync(mGotInventoryListener);
}
});
//*************************************************************************************************************************
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_workoutplan1);
WorkoutPlan1 = (Button) findViewById(R.id.Workout1MoreInfo);
}
//*************************************************************************************************************************
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Query inventory finished.");
if (result.isFailure()) {
Log.d(TAG, "Failed to query inventory: " + result);
return;
}
else {
Log.d(TAG, "Query inventory was successful.");
// does the user have the premium upgrade?
mIsPremium = inventory.hasPurchase(SKU_PREMIUM);
// update UI accordingly
WorkoutPlan1.setVisibility(View.VISIBLE);
Log.d(TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));
}
Log.d(TAG, "Initial inventory query finished; enabling main UI.");
}
};
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
if (result.isFailure()) {
Log.d(TAG, "Error purchasing: " + result);
return;
}
else if (purchase.getSku().equals(SKU_PREMIUM)) {
// give user access to premium content and update the UI
WorkoutPlan1.setVisibility(View.VISIBLE);
}
}
};
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
} else {
Log.d(TAG, "onActivityResult handled by IABUtil.");
}
}
Im aware i dont have an onclick listener on the button. But i dont even know how i would invoke the payment / purchase flow.
Any help AT ALL to point me in the right direction would be so appreciated, as at this point i just feel lost.
Thank you everyone
You would use the IabHelper object to invoke the purchase flow. For instance:
try {
mHelper.launchPurchaseFlow(this, SKU_BUNDLE, RC_REQUEST, mPurchaseFinishedListener,
payload);
} catch (IabAsyncInProgressException e) {
complain("Error launching purchase flow. Another async operation in progress.");
}
... So I would include this code snippet inside an onClickListener for your button. And then you would handle the purchases' response through the OnIabPurchaseFinishedListener.
P.S. I'm answering this with my mobile device, so excuse the poor formatting 😅😅😅
I followed these instructions to integrate both Libgdx and native android code using ActionResolver interface. I have no problem calling the Android method from the Libgdx part of my code. But I am hitting a dead end when I am trying to intergrate Google IAP with Libgdx. According to TrivialDrive example, it uses mPurchaseFinishedListener (outside of calling method).
My question is: how do I pass this IAP resultcode back to Libgdx since the listener is outside the calling method? Currently, purchase process went through, but the libgdx part of my code is not being "informed" of the purchase status/result.
This is my code:
Any help is much appreciated.
ActionResolver:
public interface IActionResolver {
public int requestIabPurchase(int product);
}
MainActivity:
public class MainActivity extends AndroidApplication implements IActionResolver {
// Debug tag, for logging
static final String TAG = "greatgame";
// Does the user have the premium upgrade?
boolean mIsUpgraded = false;
// SKUs for our products: the cat, all, or pow
static final String SKU_UPGRADE = "android.test.purchased";
// (arbitrary) request code for the purchase flow
static final int RC_REQUEST = 10001;
// The helper object
IabHelper mHelper;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
cfg.useGL20 = false;
initialize(new Catland(this), cfg);
}
void iAbStartup() {
String base64EncodedPublicKey = "some key";
// Create the helper, passing it our context and the public key to verify signatures with
Log.d(TAG, "Creating IAB helper.");
mHelper = new IabHelper(this, base64EncodedPublicKey);
// enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(true);
// Start setup. This is asynchronous and the specified listener
// will be called once setup completes.
Log.d(TAG, "Starting setup.");
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
Log.d(TAG, "Setup finished.");
if (!result.isSuccess()) {
// Oh noes, there was a problem.
Log.d(TAG, "Problem setting up in-app billing: " + result);
return;
}
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) {
return;
}
// IAB is fully set up. Now, let's get an inventory of stuff we own.
Log.d(TAG, "Setup successful. Querying inventory.");
mHelper.queryInventoryAsync(mGotInventoryListener);
}
});
}
// 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(TAG, "Query inventory finished.");
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) {
return;
}
// Is it a failure?
if (result.isFailure()) {
Log.d(TAG, "Failed to query inventory: " + result);
return;
}
Log.d(TAG, "Query inventory was successful.");
// Do we have the SKU_UPGRADE upgrade?
Purchase thisUpgrade = inventory.getPurchase(SKU_UPGRADE);
mIsUpgraded = (thisUpgrade != null && verifyDeveloperPayload(thisUpgrade));
Log.d(TAG, "User is " + (mIsUpgraded ? "Upgraded" : "Free"));
Log.d(TAG, "Initial inventory query finished; enabling main UI.");
runPurchaseFlow(submitProduct);
}
};
// Run real purchase flow
public void runPurchaseFlow(int product) {
Log.d(TAG, "runPurchaseFlow");
/* TODO: for security, generate your payload here for verification. See the comments on
* verifyDeveloperPayload() for more info. Since this is a SAMPLE, we just use
* an empty string, but on a production app you should carefully generate this. */
String payload = "";
if (product == 1)
mHelper.launchPurchaseFlow(this, SKU_UPGRADE, RC_REQUEST, mPurchaseFinishedListener, payload);
}
// Callback for when a purchase is finished
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);
// if we were disposed of in the meantime, quit.
if (mHelper == null) return;
if (result.isFailure()) {
Log.d(TAG, "Error purchasing: " + result);
return;
}
if (!verifyDeveloperPayload(purchase)) {
Log.d(TAG, "Error purchasing. Authenticity verification failed.");
return;
}
Log.d(TAG, "Purchase successful.");
if (purchase.getSku().equals(SKU_CAT)) {
// bought the upgrade!
Log.d(TAG, "Purchase Upgrade. Congratulating user.");
mIsUpgraded = true;
// how do i pass this result to the libgdx?
}
}
};
/** Verifies the developer payload of a purchase. */
boolean verifyDeveloperPayload(Purchase p) {
String payload = p.getDeveloperPayload();
return true;
}
#Override
public int requestIabPurchase(int product) {
iAbStartup();
return 0; // how do i get the result from mPurchaseFinishedListener?
}
}
PurchaseScreen
result = greatgame.actionResolver.requestIabPurchase(1);
You won't be able to return the result from requestIabPurchase() - the only methods of doing so would block for a long time. The best way, in my opinion, would be to create a listener interface of your own that your LibGdx project implements, and pass that into your request interface. For example:
In your libGdx project somewhere:
interface PurchaseCallback {
public int setPurchaseResult(int result);
}
ActionResolver:
public interface IActionResolver {
public int requestIabPurchase(int product, PurchaseCallback callback);
}
In PurchaseScreen, implement PurchaseCallback:
#override
public int setPurchaseResult(int result) {
// Yay! I have a result from a purchase! Maybe you want a boolean instead of an int? I don't know. Maybe an int (for the product code) and a boolean.
}
...and pass whatever is implementing PurchaseCallback (I'm assuming your PurchaseScreen does itself):
result = greatgame.actionResolver.requestIabPurchase(1, this);
Finally, hook it all up in MainActivity:
PurchaseCallback mCallback = null;
mPurchaseFinishedListener = ... etc. etc.
.
.
.
if (mCallback != null) {
mCallback.setPurchaseResult(0);
}
.
.
.
#Override
public int requestIabPurchase(int product, PurchaseCallback callback) {
mCallback = callback; // save this for later
iAbStartup();
return 0;
}
Note that you should call PurchaseCallback.setPurchaseResult() everywhere that mPurchaseFinishedListener has return, not only at the line // how do i pass this result to the libgdx? - otherwise, you will never know if a purchase failed or is just taking a really long time.