Here is the method I've got:
public void setupBillingClient() { //connect to google play
billingClient = BillingClient.newBuilder(context)
.enablePendingPurchases()
.setListener(this)
.build();
billingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//The BillingClient is setup successfully
loadAllSkus();
}
}
#Override
public void onBillingServiceDisconnected() {
//TODO: implement retry logic to handle lost connections to Google Play by calling startConnection() again
}
});
}
Google says I should "implement retry logic" but doesn't say how. I thought maybe to just call setupBillingClient() inside onBillingServiceDisconnected() but some people said that causes a crash. Also I feel if it was that simple then google would have told us to write that instead of the vague instruction to implement a retry logic.
I also ran into this issue. Google documentation about this is just a mess, (well, like the API itself).
So, here Google says
To implement retry logic, override the onBillingServiceDisconnected()
callback method, and make sure that the BillingClient calls the
startConnection() method to reconnect to Google Play before making
further requests.
Which implies that after the disconnection we have to call startConnection manually.
But here Google says
Called to notify that the connection to the billing service was lost.
Note: This does not remove the billing service connection itself -
this binding to the service will remain active, and you will receive a
call to onBillingSetupFinished(BillingResult) when the billing service
is next running and setup is complete.
Which, in my opinion, absolutely contradicts the previous statement.
From my experience with the billing library, I believe the last statement is more likely to be true. I'm not 100% sure though.
But I can confirm that I saw a disconnect message in the logcat, followed by another message that the billing client was ready. I didn't do any restart actions though. Also, if I tried to startConnection in disconnection callback, then I began to receive two messages in the logcat on each connection/disconnection.
Based on this, I can say that:
You can go here and click on "Not helpful" at the bottom of the page. Or tag them on Twitter, or create an issue on their tracker.
Retry logic, which we are talking about - is not about a connection retry. It's about to retry operation that we tried to perform using the billing client, but it didn't work because it was disconnected.
Related
I have succeeded integrating the class BillingClient, starting the connection etc. I can, in fact, make a test purchase of the product from the App (it shows the payment form, buys the product etc.) I implemented it as it is described in this link that explains how to integrate the Google Play Billing Library.
It works, the purchase is performed... but I am clueless about how to implement the unlocking logic! For the record, it is only one product, non-consumable:
private void handlePurchase(Purchase purchase) {
AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = new AcknowledgePurchaseResponseListener() {
#Override
public void onAcknowledgePurchaseResponse(#NonNull BillingResult billingResult) {
// TODO Ok, now what???
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// TODO Handle the success of the "acknowledge"
// Unlock things etc...
}
}
};
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
}
// TODO Unlock from here?
is_premium_unlocked = true;
}
}
I am not sure howto implement the unlock: but for keeping it simple, let's say that the variable is_premium_unlocked will do the job. Will this grant it to be true after closing and reopening the App? I don't think so... Even though this handlePurchase is triggered onPurchaseUpdated, when this event occurrs? Only in new purchase requests? Every time the BillingClient connection is successful? I wonder about all of these things...
So, summarizing: What does my business logic have to do about the "acknowledging"? I am aware that this is some kind of mechanism to prevent duplicated purchases by the same user and stuff like that... but what should my App do about it, beyond what the code samples suggest? But most importantly: how is the app supposed to know that the "unlock product" is already purchased while starting? Is this done by some of the callbacks implemented by documentation's code samples, or have I to implement it my own way? Is there a way of, looking at the ProductDetails to check if it is has been already purchased before, and proceed to execute unlocking logic right away? Is this up to my code, or up to Google Play Billing Service?
The point is, what I have already done following those docs, lets the app "trigger" the unlock just after the product is purchased (AKA is_premium_unlocked = true), but I don't know how is the billingClient supposed to check if the product was already purchased in future connections, or if billingClient is able to do so at all in the first place.
Finally, it was in the docs (I didn't read them hard enough). It looks like it must be done this way, by adding an async checker. So, if I trigger queryPurchasesAsync every time the billingClient connects successfully, I can trigger the unlock from there after app starts:
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder()
.setProductType(ProductType.INAPP)
.build(),
new PurchasesResponseListener() {
public void onQueryPurchasesResponse(BillingResult billingResult, List purchases) {
is_premium_unlocked = true;
}
}
);
I'm messing around with Cloud Firestore.
I would like to simply get a callback when reading from DB fails, so I can show a dialog to the user about he has no internet connection. Of course, this would need sophisticated exception handling, but first things first, I would like to just simply get a callback when the app cannot reach the server.
HOWEVER, whenever I test my application with an emulator which has no internet connection, I still get successful callbacks.
This is the log:
Logging_: onSuccess
Logging_: onComplete
Logging_: Task was successful without an internet connection, how?
How is it possible? Am I thinking right that Cloud Firestore is simply not available for this use case since it was built to provide cached data and aggressive syncing in order to provide a seamless user experience even when there is no internet connection?
I would just need a way to just KNOW whether the DB is reachable. (a.k.a - Is there an internet connection problem?)
Code is really simple, it just tries to reach for the current account's characters.
db.collection("users")
.document(accountId)
.collection("characters")
.get()
.addOnCanceledListener(new OnCanceledListener() {
#Override
public void onCanceled() {
Log.i("Logging_", "onCanceled");
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.i("Logging_", "onFailure");
}
})
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
Log.i("Logging_", "onSuccess");
}
})
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
Log.i("Logging_", "onComplete");
if (task.isSuccessful()) {
Log.i("Logging_", "Task was successful without internet connection, how?");
} else {
Log.i("Logging_", "Task wasn't successful.");
}
}
});
I would like to simply get a callback when reading from DB fails, so I can show a dialog to the user about whether he has no internet connection.
The Firestore SDK doesn't throw an error when there is no internet connection, and it makes sense since Firestore is designed to work offline. Behind the scenes, Firestore SDK tries to reconnect until the devices regain connectivity. So not having an internet connection cannot be considered a failure. If you want to check for internet connectivity, the following answer might help:
How to verify if user has network access and show a pop-up alert when there isn't
Please notice that Firestore has a built-in mechanism that can help know when an error occurs. So the failure callback occurs when Firestore servers reject the request due to a security rule issue.
There is a solution in which you can force the retrieval of data only from the cache or from the server. Here is the official documentation regarding source options:
https://firebase.google.com/docs/firestore/query-data/get-data#source_options
Firestore has built in caching that is enabled by default for reading from a database on Apple and Android devices. If you want to disable being able to read the cached data, you can do something like this:
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(false)
.build();
db.setFirestoreSettings(settings);
I think what you may want to do instead is listen to network events in Android which would allow you to update the user if they try to perform an action while there is no network available.
This might be a bug. I have logged the tracking info here on GitHub
I am wondering if there is a way to test to see if you are subscribed to a topic on the android side of things.
Basically, I am HOPING that all devices will subscribe to a topic during their installation, when the token is first obtained by the device. However, there is always a chance that the device fails to subscribe. The FCM registration token should be installed on the device for a long time, and thus, the onTokenRefresh() method shouldn't be called again without clearing data, uninstall/reinstall, etc.
My idea was to test to see if the device is subscribed to a topic in my MainActivity, and if not, then try to subscribe again. If it fails to subscribe, then get a new token and try again, etc.
#Override
public void onTokenRefresh() {
// Get updated InstanceID token.
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.e(TAG, "Refreshed token: " + refreshedToken);
// Subscribe to a topic
Log.e(TAG, "Subscribing to topic");
FirebaseMessaging.getInstance().subscribeToTopic("test");
So, I can subscribe and unsubscribe, but how do I check if the device is subscribed to a topic? I did my fair share of googling, and couldn't find anything, unfortunately.
I would greatly appreciate any/all assistance. Thanks!
There is currently no way to check on the client side if they are subscribed to a topic.
The behavior for subscribeToTopic is it would immediately subscribe to the specified topic, if it fails, it would retry on it's own (unless your app was killed). See my answer here.
I think that forcing the onTokenRefresh call just to make sure that subscribeToTopic is too much. You could simply just call it in your initial activity if you want, that way, everytime the app starts, it sends the subscription request.
Actually this can be done by using this api: https://developers.google.com/instance-id/reference/server#get_information_about_app_instances
As IID_TOKEN you need the FCM token and in the header you have to pass Authentication: key=YOUR_SERVER_KEY. You can find the server key as described here: Firebase messaging, where to get Server Key?.
Don't forget to include details=true as query parameter in the url, otherwise the topics won't be included in the response.
I would recommend writing a Cloud Function to encapsulate it, so you don't deploy your server key to the client.
I a using firebase and found and issue that firebase not send error for timeout or if not able to connect to server. In that case we are unable to provide correct information to user what the issue is.
Firebase developers must handle this very common use-case. Did anyone encounter this issue?
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot data) {
}
#Override
public void onCancelled(FirebaseError arg0) {
}
Utilize .info/connected to monitor connection state. Firebase works while offline and your onCancelled event is not going to be fired because it is still waiting for the connection to be restored so the message can be delivered.
Firebase is a real-time sync platform. You cannot keep data in sync without any internet access (how will local and remote be reconciled?). So you need to utilize disk persistence (in beta on iOS) or at least have an initial connection to get things moving. Check out offline capabilities for details on all of these topics.
On my Android App, I'm implementing SignalR connection (https://github.com/erizet/SignalA) to connect to a Hub server to send requests and receive responses.
a sample of my code is as follows:
signalAConnection = new com.zsoft.SignalA.Connection(Constants.getHubUrl(), this, new LongPollingTransport())
{
#Override
public void OnError(Exception exception)
{
}
#Override
public void OnMessage(String message)
{
}
#Override
public void OnStateChanged(StateBase oldState, StateBase newState)
{
}
};
if (signalAConnection != null)
signalAConnection.Start();
There's also the sending bit
signalAConnection.Send(hubMessageJson, new SendCallback()
{
public void OnError(Exception ex)
{
}
public void OnSent(CharSequence message)
{
}
});
The sending and receiving will occur across activites, and some responses will be sent at random times regardless of the activity, also, the connection should be opened as long as the app is running (even if the app is running in the background) that's why I wish to implement the signalA connection as a background service
The question is should I implement it as:
1 - a Service (http://developer.android.com/reference/android/app/Service.html)
OR
2 - an Intent Service (http://developer.android.com/training/run-background-service/create-service.html)
Keeping in mind that I will need to send strings to the service and get response strings from the service.
I would be most grateful if someone would show me how to implement this kind of connection in code as a background service/intentservice.
Thanks for reading.
UPDATE:
Please see this demo activity made by the developer as how he implemented SignalA
https://github.com/erizet/SignalA/blob/master/Demo/src/com/zsoft/SignalADemo/DemoActivity.java
The problem is AQuery (which I know nothing about) is being used in this demo activity. Does AQuery run in the background all the time ?
The problem is, the latest update on SignalA mentions the following
I have changed the transport. LongPolling now uses basic-http-client
instead of Aquery for http communication. I've removed all
dependencies on Aquery.
Hence I'm not sure whether I should follow this demo activity or not
Update 2:
This is the thing that is confusing me most
in the IntentService, the OnHandleIntent method calls stopSelf after it finishes its tasks, when I actually want the code in the IntentService to keep running all the time
protected abstract void onHandleIntent (Intent intent)
Added in API level 3
This method is invoked on the worker thread with a request to process. Only one Intent is processed at a time, but the processing happens on a worker thread that runs independently from other application logic. So, if this code takes a long time, it will hold up other requests to the same IntentService, but it will not hold up anything else. When all requests have been handled, the IntentService stops itself, so you should not call stopSelf().
SignalA is running on the thread that creates and starts the connection, but all network access is done in the background. The remaining work on the starting thread is really lightweight, hence its perfectly ok to do it on the UI tread.
To answer your question, you need to have a thread running the signala connection. Therefore I think a Service is the best choice since SignalA need to be running all the time.
Regarding Aquery and the demo project. I removed all dependencies to Aquery in the libraries, not in the Demo. To be clear, you don't need Aquery to run SignalA.
In my case, what I wanted was a Service not an Intent Service, since I wanted something that would keep running until the app closes