How to correctly use querySkuDetailsAsync? - android

I am trying to use the billing library and I am getting info from the official Android Developer site here. But I am finding a lot of trouble. Mainly compiling problems. It looks like that documentation is not completed. When I started following step by step I had to search for a lot of extra information. Now I am stuck trying to do querySkuDetailsAsync()
This is my code:
billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
List<String> skuList = new ArrayList<> ();
skuList.add("sp_hide_ads_year_01");
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS);
billingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
//*** I want to Continue here ***
}
});
}
}
#Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
});
The compiler (Android Studio) says:
'onSkuDetailsResponse(BillingResult, List)' in 'Anonymous
class derived from
com.android.billingclient.api.SkuDetailsResponseListener' clashes with
'onSkuDetailsResponse(BillingResult, List)' in
'com.android.billingclient.api.SkuDetailsResponseListener'; both
methods have erasure, yet neither overrides the other
I have no idea what this means. any help here?
By the way, I use
implementation 'com.android.billingclient:billing:2.1.0'

I changed the line:
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList)
for this one, and everything compiled:
public void onSkuDetailsResponse(BillingResult billingResult, List<com.android.billingclient.api.SkuDetails> skuDetailsList)

Related

querySkuDetailsAsync callback never called

I think to have followed all the steps correctly from the documentation but I can't reach the callback of querySkuDetailsAsync, no errors reported. The app is working correctly with IAB, now I'm only migrating from old library to the new 'com.android.billingclient:billing:2.0.3' but many problems.
Another question, in the new library is also necessary the use of License Key from Play Console? I don't find documentation about using that in the new library.
I can correctly and without errors reach this line billingClient.querySkuDetailsAsync(params, (billingResult2, skuDetailsList) ->
Sku ids are correctly
private void setupIab()
{
billingClient = BillingClient.newBuilder(getApplicationContext()).enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener()
{
#Override
public void onBillingSetupFinished(BillingResult billingResult)
{
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK)
{
List<String> skuList = new ArrayList<> ();
skuList.add("test_sku_1");
SkuDetailsParams params = SkuDetailsParams.newBuilder().setSkusList(skuList).setType(BillingClient.SkuType.INAPP).build();
billingClient.querySkuDetailsAsync(params, (billingResult2, skuDetailsList) ->
{
// Process the result.
if (billingResult2.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null)
{
}
});
}
}
#Override
public void onBillingServiceDisconnected()
{
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
});
}
Best regards
The code seems correct form me and is similar to what I use, but I do not call querySkuDetailsAsync within onBillingSetupFinished, I call it only when the user buy something.
Maybe when onBillingSetupFinished runs, the setup is not really finished yet, you could try using this, so it will be called just later:
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
new Handler().post(() -> {
List<String> skuList = new ArrayList<>();
skuList.add("test_sku_1");
SkuDetailsParams params = SkuDetailsParams.newBuilder().setSkusList(skuList).setType(BillingClient.SkuType.INAPP).build();
billingClient.querySkuDetailsAsync(params, (billingResult2, skuDetailsList) ->
{
// Process the result.
if (billingResult2.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
}
});
});
}

android billing how to enable enablePendingPurchases()

I've moved from an old gradle of billing api, to the most recent to date, and now I've tried adding
BillingClient.Builder enablePendingPurchases = BillingClient.newBuilder(this).setListener(this);
but I can not get it to work, here's the error
Caused by: java.lang.IllegalArgumentException: Support for pending purchases must be enabled. Enable this by calling 'enablePendingPurchases()' on BillingClientBuilder.
at com.android.billingclient.api.BillingClient$Builder.build(BillingClient.java:309)
at com.aplicacion.vivaluganoapp.ar.ponerDineroActivity.setupBillingClient(ponerDineroActivity.java:144)
at com.aplicacion.vivaluganoapp.ar.ponerDineroActivity.onCreate(ponerDineroActivity.java:125)
complete code:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_poner_dinero);
recyclerProduct.setHasFixedSize(true);
recyclerProduct.setLayoutManager(new LinearLayoutManager(this));
BillingClient.Builder enablePendingPurchases = BillingClient.newBuilder(this).setListener(this);
enablePendingPurchases.build();
setupBillingClient();
}
private void setupBillingClient() {
billingClient = BillingClient.newBuilder (this).setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(BillingResult responseCode) {
int maca = BillingClient.BillingResponseCode.OK;
String maca2 = String.valueOf(maca);
String maca3 = String.valueOf(responseCode);
if (maca3 == maca2)
{
Toast.makeText(ponerDineroActivity.this, "WORKS", Toast.LENGTH_SHORT).show();
}
else
{
Toast.makeText(ponerDineroActivity.this, "ERROR", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onBillingServiceDisconnected() {
Toast.makeText(ponerDineroActivity.this, "Disconnected from Billing", Toast.LENGTH_SHORT).show();
}
});
}
if I place only:
BillingClient.Builder enablePendingPurchases = BillingClient.newBuilder(this);
the error is:
Caused by: java.lang.IllegalArgumentException: Please provide a valid listener for purchases updates.
any help? i'm tired of trying
From the first stacktrace in your question
Enable this by calling 'enablePendingPurchases()'
we can find documentation for method enablePendingPurchases()
This method is required to be called to acknowledge your application
has been updated to support purchases that are pending. Pending
purchases are not automatically enabled since your application will
require updates to ensure entitlement is not granted before payment
has been secured. For more information on how to handle pending
transactions see
https://developer.android.com/google/play/billing/billing_library_overview
If this method is not called, BillingClient instance creation fails.
Your line of code should be:-
enablePendingPurchases = BillingClient.newBuilder(this)
.enablePendingPurchases()
.setListener(this);
Instead of :-
enablePendingPurchases = BillingClient.newBuilder(this).setListener(this);
This worked for me.
Just add enablePendingPurchases() like below:
billingClient = BillingClient.newBuilder(this)
.setListener(this)
.enablePendingPurchases()
.build();
BillingClient billingClient =
BillingClient.newBuilder(context!!)
.enablePendingPurchases()
.setListener(this)
build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode==BillingClient.BillingResponseCode.OK) {
skuList = HashMap()
skuList.put(BillingClient.SkuType.SUBS, listOf(getString(R.string.subscription_monthly),getString(R.string.subscription_yearly)))
querySkuDetailsAsync(BillingClient.SkuType.SUBS,skuList.get(BillingClient.SkuType.SUBS),object :SkuDetailsResponseListener{
override fun onSkuDetailsResponse(billingResult: BillingResult?, skuDetailsList: MutableList<SkuDetails>?) {
DebugLog.e("DATAAA "+skuDetailsList?.size+"")
}
})
}
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
})

Is it possible to directly consume the Google Play BillingClient from a Xamarin Android app?

I'm working on integrating subscriptions into a Xamarin Android app. All the examples I can find online specific to Xamarin use the Plugin.InAppBilling from Montemagno, et al.
This is certainly very convenient, but just for the sake of due dilligence, I want to look at what it would take to directly consume the BillingClient classes, as described in Google's documentation (for example, here: https://developer.android.com/google/play/billing/billing_java_kotlin).
Strangely, Xamarin documentation shows how to directly consume the iOS billing classes, but not the ones for Android. I thought that the Xamarin.GooglePlayServices NuGet packages might contain what I need, but I don't see one that seems to directly pertain to billing.
Any pointers would be greatly appreciated.
TL;DR : Yes you can... (I use it...)
The classes that are shown in "Implement Google Play Billing" docs are wrapper classes that eventually make use of the Billing AIDL. That AIDL interface is THE actual billing API and the AIDL defines those IPC calls to it (this is not just for the cross-process method calling, but also for security)...
Android Studio, via Gradle, automatically imports the billing wrapper and AIDL and sets everything up for you when you add the billing api dependence to the build.gradle file. (of course, that is not an option via Visual Studio and MSBuild...)
Montemagno's Android in-app billing directly uses the AIDL interface methods and wraps those in a cross-platform way...
To use the Java wrapper classes, the basic steps are to grab the aar (billing-1.1.aar) from Jcenter Maven repo, create a Xamarin.Android binding library, add the aar, fix-up the warning (and namespace if desire) and reference that library in your Xamarin.Android application project...
Now you can directly use the Google docs, with some minor Xamarin.Android/C# normalizations, i.e.:
// I did not alter the original Java namespace in the binding lib
using Com.Android.Billingclient.Api;
~~~
var billingClient = BillingClient
.NewBuilder(this)
.SetListener(this)
.Build();
~~~
var flowParams = BillingFlowParams
.NewBuilder()
.SetSku("StackOverflowXamarinTag")
.SetType("Answer")
.Build();
using Android.BillingClient.Api.BillingClient;
https://www.nuget.org/packages/Xamarin.Android.Google.BillingClient/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.BillingClient.Api;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using static Android.BillingClient.Api.BillingClient;
namespace Android.Basic.Core
{
public class BilingSupport : Java.Lang.Object, IPurchasesUpdatedListener, IBillingClientStateListener, ISkuDetailsResponseListener, IConsumeResponseListener
{
Context Context;
public IList<SkuDetails> SkuDetails;
Android.BillingClient.Api.BillingClient billingClient;
private List<string> skuList = new List<string>() { "get_5_coins", "get_10_coins" };
public BilingSupport(Context context)
{
this.Context = context;
billingClient = Android.BillingClient.Api.BillingClient.NewBuilder(this.Context).SetListener(this).Build();
billingClient.StartConnection(this);
}
public void LoadPurchases()
{
if (billingClient.IsReady)
{
var paramse = SkuDetailsParams.NewBuilder().SetSkusList(skuList).SetType(Android.BillingClient.Api.BillingClient.SkuType.Inapp).Build();
billingClient.QuerySkuDetails(paramse, this);
}
}
public void PurchaseNow(SkuDetails skuDetails)
{
var billingFlowParams = BillingFlowParams.NewBuilder().SetSkuDetails(skuDetails).Build();
billingClient.LaunchBillingFlow(this.Context as Activity, billingFlowParams);
}
public void OnBillingServiceDisconnected()
{
Console.WriteLine("BILLING | onBillingServiceDisconnected | DISCONNECTED");
}
public void OnBillingSetupFinished(BillingResult p0)
{
if (p0.ResponseCode == BillingResponseCode.Ok)
{
Console.WriteLine("BILLING | startConnection | RESULT OK");
}
else
{
Console.WriteLine("BILLING | startConnection | RESULT: $billingResponseCode");
}
}
//Response code 7 in OnPurchasesUpdated.It means: Item Already Owned.
public void OnPurchasesUpdated(BillingResult p0, IList<Purchase> p1)
{
}
public void OnSkuDetailsResponse(BillingResult p0, IList<SkuDetails> p1)
{
if (p0.ResponseCode == BillingResponseCode.Ok)
{
Console.WriteLine("querySkuDetailsAsync, responseCode: $responseCode");
InitProductAdapter(p1);
}
else
{
Console.WriteLine("Can't querySkuDetailsAsync, responseCode: $responseCode");
}
}
public event EventHandler<IList<SkuDetails>> SkuDetailsLoaded;
public void InitProductAdapter(IList<SkuDetails> skuDetails)
{
this.SkuDetails = skuDetails;
this.SkuDetailsLoaded(this, this.SkuDetails);
}
public void ClearOrConsumeAllPurchases()
{
var querylist = billingClient.QueryPurchases(Android.BillingClient.Api.BillingClient.SkuType.Inapp).PurchasesList;
foreach (var query in querylist)
{
var consumeParams = ConsumeParams.NewBuilder().SetPurchaseToken(query.PurchaseToken).Build();
billingClient.Consume(consumeParams, this);
}
}
public void OnConsumeResponse(BillingResult p0, string p1)
{
if (p0.ResponseCode == BillingResponseCode.Ok && p1 != null)
{
Console.WriteLine("onPurchases Updated consumeAsync, purchases token removed: $purchaseToken");
}
else
{
Console.WriteLine("onPurchases some troubles happened: $responseCode");
}
}
}
}

RxJava: Continue next iteration even if error occurs

I'm using RxSearchView to emit out the results of a search query from an API to a recyclerview. However, if one of those query fails, onError() is called(which is expected) but the subscription as a whole is also canceled. Subsequent queries are not executed at all.
How should i modify the code so that the call to onError() is prevented when a query fails and the next incoming queries are executed normally?
Here's a code snippet:
subscription = RxSearchView.queryTextChanges(searchView)
.debounce(500, MILLISECONDS)
.filter(charSequence -> !TextUtils.isEmpty(charSequence))
.map(CharSequence::toString)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.switchMap(query -> apiService.getSearchResults(query))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<SearchResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(SearchResponse searchResponse) {
if (searchResponse.getStatus().equals("OK")) {
//update Adapter
} else {
//update error views
}
}
});
P.S: I am using switchMap() so that the results of old queries are ignored, if the results of new query has arrived.
You have to handle this error and return an object instead. You can do it, for example, by using onErrorResumeNext operator with apiService.getSearchResults(query) call. What you are going to return - depends on you, you can even return null if you want, but better to create some wrapper which can carry both response status flag and normal response if received.
Something like:
subscription = RxSearchView.queryTextChanges(searchView)
.debounce(500, MILLISECONDS)
.filter(charSequence -> !TextUtils.isEmpty(charSequence))
.map(CharSequence::toString)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.switchMap(query -> apiService
.getSearchResults(query)
.onErrorResumeNext(error -> null)
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<SearchResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(SearchResponse searchResponse) {
if (searchResponse != null && searchResponse.getStatus().equals("OK")) {
//update Adapter
} else {
//update error views
}
}
});
Of course, this is naive example with using null, in reality you need to write error handling logic. Better to return wrapper, because if using RxJava 2, then it doesn't support null.

Verify mock interactions within anonymous inner class

I am trying to test my ViewModel in my application, here is the constructor:
#Inject
public SearchUserViewModel(#Named("searchUser") UseCase searchUserUseCase) {
this.searchUserUseCase = searchUserUseCase;
}
In my test I create a SearchUserUseCase with mocks like this:
Observable error = Observable.error(new Throwable("Error"));
when(gitHubService.searchUser(MockFactory.TEST_USERNAME_ERROR)).thenReturn(error);
when(ObserverThread.getScheduler()).thenReturn(Schedulers.immediate());
when(SubscriberThread.getScheduler()).thenReturn(Schedulers.immediate());
searchUserUseCase = new SearchUserUseCase(gitHubService, SubscriberThread, ObserverThread);
In my ViewModel class I have this snippet which I want to test:
public void onClickSearch(View view) {
loadUsers();
}
private void loadUsers() {
if (username == null) {
fragmentListener.showMessage("Enter a username");
} else {
showProgressIndicator(true);
searchUserUseCase.execute(new SearchUserSubscriber(), username);
}
}
private final class SearchUserSubscriber extends DefaultSubscriber<SearchResponse> {
#Override
public void onCompleted() {
showProgressIndicator(false);
}
#Override
public void onError(Throwable e) {
showProgressIndicator(false);
fragmentListener.showMessage("Error loading users");
}
#Override
public void onNext(SearchResponse searchResponse) {
List<User> users = searchResponse.getUsers();
if (users.isEmpty()) {
fragmentListener.showMessage("No users found");
} else {
fragmentListener.addUsers(users);
}
}
}
Finally in my test I have this:
#Test
public void shouldDisplayErrorMessageIfErrorWhenLoadingUsers() {
SearchUserViewModel searchUserViewModel = new SearchUserViewModel(searchUserUseCase);
searchUserViewModel.setFragmentListener(mockFragmentListener);
searchUserViewModel.setUsername(MockFactory.TEST_USERNAME_ERROR);
searchUserViewModel.onClickSearch(view);
verify(mockFragmentListener).showMessage("Error loading users");
}
I get this error from Mockito:
Wanted but not invoked:
fragmentListener.showMessage(
"Error loading users"
);
I am not sure if this is a good test, but I somehow want to test the SearchUserSubscriber one way or another. Thanks
Edit: I have found similar questions to this problem here: Can't verify mock method call from RxJava Subscriber (which still isn't answered) and here: Verify interactions in rxjava subscribers. The latter question is similar but does not execute the subscriber in a separate class (which happens in SearchUserUseCase here).
I also tried RobolectricGradleTestRunner instead of MockitoJunitRunner and changed to Schedulers.io() and AndroidSchedulers.mainThread(), but I still get the same error.
Tried mocking SearchUserUseCase instead of GitHubService (which feels cleaner), but I'm not sure on how to test the subscriber that way since that is passed as an argument to the void method execute() in UseCase.
public void execute(Subscriber useCaseSubscriber, String query) {
subscription = buildUseCase(query)
.observeOn(postExecutionThread.getScheduler())
.subscribeOn(threadExecutor.getScheduler())
.subscribe(useCaseSubscriber);
}
And buildUseCase()
#Override
public Observable buildUseCase(String username) throws NullPointerException {
if (username == null) {
throw new NullPointerException("Query must not be null");
}
return getGitHubService().searchUser(username);
}
For me it worked out to add a Observable.Transformer<T, T> as followed:
void gatherData() {
service.doSomeMagic()
.compose(getSchedulerTransformer())
.subscribe(view::displayValue);
}
private <T> Observable.Transformer<T, T> getSchedulerTransformer() {
if (mTransformer == null) {
mTransformer = (Observable.Transformer<T, T>) observable -> observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
return mTransformer;
}
void setSchedulerTransformer(Observable.Transformer<Observable<?>, Observable<?>> transformer) {
mTransformer = transformer;
}
And to set the Transformer. I just passed this:
setSchedulerTransformer(observable -> {
if (observable instanceof Observable) {
Observable observable1 = (Observable) observable;
return observable1.subscribeOn(Schedulers.immediate())
.observeOn(Schedulers.immediate());
}
return null;
});
So just add a #Before method in your test and call presenter.setSchedulerTransformer and it should be able to test this. If you want more detail check this answer.
If you are using Mockito, you can probably get hold of a SearchUserSubscriber using an ArgumentCaptor, for example...
#Captor
private ArgumentCaptor<SearchUserSubscriber> subscriberCaptor;
private SearchUserSubscriber getSearchUserSubscriber() {
// TODO: ...set up the view model...
...
// Execute the code under test (making sure the line 'searchUserUseCase.execute(new SearchUserSubscriber(), username);' gets hit...)
viewModel.onClickSearch(view);
verify(searchUserUseCase).execute(subscriberCaptor.capture(), any(String.class));
return subscriberCaptor.getValue();
}
Now you can have test cases such as...
#Test
public void shouldDoSomethingWithTheSubscriber() {
SearchUserSubscriber subscriber = getSearchUserSubscriber();
...
}

Categories

Resources