I'm working with the Google Billing Library Version 5. I've pretty
much gotten my code to do the right thing. I tested it on hardware
devices running API levels 16, 23, 28, and 32.
Then I did a factory reset on the device running API level 23. I
wanted to establish a brand new Google ID to test the billing library.
It's a Samsung Galaxy S5 running Android M. Purchasing no longer
works. billingClient.isReady returns false, and
billingClient.isFeatureSupported returns SERVICE_DISCONNECTED.
onBillingServiceDisconnected is not being called.
billingClient.queryPurchasesAsync() does work. It returns an OK response
code and reports zero purchases.
My code runs fine on my other devices, and it used to run fine on the
Galaxy S5 until I reset it and gave it a new user ID.
https://developer.android.com/google/play/licensing/setting-up says I
should be running on a device that has the Google Play client
pre-installed. The Applications Manager shows a Google Play Store app
with the same version number as my other devices. Ditto for Google
Play Services. The Play Store app tells me it is up to date.
What am I missing?
public void doPurchase( Activity activity )
{
billingClient = BillingClient.newBuilder(activity).enablePendingPurchases()
.setListener(this).build();
if ( ! billingClient.isReady() )
Log.i( appName, "Billing client is not ready" );
BillingResult billingResult = billingClient.isFeatureSupported( BillingClient.FeatureType.PRODUCT_DETAILS );
if ( billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK )
{
Log.i( appName, String.format( "feature not supported %s", billingResult.toString() ) );
return;
}
billingClient.startConnection(new BillingClientStateListener()
{
public void onBillingSetupFinished( #NonNull BillingResult billingResult )
{
if ( billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK )
{
Log.i( appName, String.format( "billing setup response code %s", billingResult.toString() ) );
return;
}
launchPurchase( activity );
}
public void onBillingServiceDisconnected()
{
Log.i( appName, "Disconnected" );
}
});
}
private void launchPurchase( final Activity activity )
{
ImmutableList<QueryProductDetailsParams.Product> productList
= ImmutableList.of( QueryProductDetailsParams.Product.newBuilder()
.setProductId( SKU_BUY )
.setProductType( BillingClient.ProductType.INAPP)
.build());
QueryProductDetailsParams.Builder builder = QueryProductDetailsParams.newBuilder();
builder.setProductList( productList );
billingClient.queryProductDetailsAsync( builder.build(),
new ProductDetailsResponseListener() {
#Override
public void onProductDetailsResponse( #NonNull BillingResult billingResult,
#Nullable List<ProductDetails> list ) {
if ( billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK )
{
Log.i( appName, String.format( "product details response code %s", billingResult.toString() ) );
return;
}
if ( list == null || list.size() <= 0 )
{
Log.i( appName, "no products found" );
return;
}
ImmutableList<BillingFlowParams.ProductDetailsParams> deetsParams = ImmutableList.of(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails( list.get( 0 ) )
.build() );
BillingFlowParams purchaseParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList( deetsParams )
.build();
billingClient.launchBillingFlow( activity, purchaseParams );
}
} );
}
Related
I'm implementing the Google Sign In option on a Flutter app, it works fine with iOS (emulator) but in Android (emulator) the credentials that I get are always marked invalid when I check them with Google.
Future<GoogleSignInAuthentication?> _validateGoogleToken(
GoogleSignInAccount? googleSignInAccount) async {
try {
if (googleSignInAccount?.authentication != null) {
final credentials = await googleSignInAccount?.authentication;
if (credentials != null && credentials.idToken != null) {
final urlRequest = Uri.parse(https://www.googleapis.com/oauth2/v3/userinfo);
final response = await http.get(urlRequest, headers: {
'Authorization': 'Bearer ${credentials.accessToken}',
});
final responseData = json.decode(response.body);
if (responseData['error'] == null &&
responseData['email_verified'] == true) {
this._profile = Profile.fromJson(json.decode(response.body));
_authenticationToken = credentials.idToken!;
return Future.value(credentials);
}
}
}
return Future.value(null);
} catch (error) {
return Future.value(null);
}
}
The Google OAuth always returns me that the credentials are invalid. However, in the case of iOS, the credential that my app receives always gets validated with no issues.
I noticed that for some reason the credential in Android, is always the same and it seems to be shorter/truncated than the one from iOS.
Any recommendations?
Using the same Flutter code on both Android and iOS, restores work on iOS, but not on Android.
I’m able to buy non-consumable products on both platforms, but when I try to restore them on Android, I always get zero as a return value.
This is how I initialize the IAP:
#override
initState() {
final Stream<List<PurchaseDetails>> purchaseUpdated = InAppPurchase.instance.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdatedBooks(purchaseDetailsList);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
super.initState();
}
For purchasing, I used the following code:
late PurchaseParam purchaseParam;
List<PurchaseDetails> _purchases = <PurchaseDetails>[];
InAppPurchase _inAppPurchase = InAppPurchase.instance;
bool isIOS = Theme
.of(context)
.platform == TargetPlatform.iOS;
if (isIOS == false) {
final Map<String, PurchaseDetails> purchasesMap =
Map<String, PurchaseDetails>.fromEntries(
_purchases.map((PurchaseDetails purchase) {
if (purchase.pendingCompletePurchase) {
_inAppPurchase.completePurchase(purchase);
}
return MapEntry<String, PurchaseDetails>(
purchase.productID, purchase);
}));
final GooglePlayPurchaseDetails? oldSubscription = _getOldSubscription(
productL, purchasesMap);
purchaseParam = GooglePlayPurchaseParam(
productDetails: productL,
applicationUserName: null,
changeSubscriptionParam: (oldSubscription != null)
? ChangeSubscriptionParam(
oldPurchaseDetails: oldSubscription,
prorationMode: ProrationMode
.immediateWithTimeProration,
)
: null);
} else {
purchaseParam = PurchaseParam(
productDetails: productL,
applicationUserName: null,
);
}
await InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam)
.catchError((e) {
print('Got error: $e'); // Finally, callback fires.
return false; // Future completes with 42.
}).then((value) {
if (value == true) {
print('The value is $value');
}
});
I expected to get a value different from zero for restored purchases, but on Android, I always get zero as a return value.
On Android, I am also checking for old subscriptions, as I am required to do.
In order for in-app purchases to register on Android, does my code need to be further adapted for Android? We are running the app under "Closed testing", all of our test users are configured under "License testing" in the Play Console, and we are using version 3.0.2 of the in_app_purchase library. Many thanks!
I made a messaging class extending FirebaseMessagingService and found that onNewToken method was working fine.
But, when I used the sample code for FirebaseMessaging class to get the current Firebase Token, I ran into an error: error: cannot find symbol method getToken().
What am I missing?
Any suggestion would be appreciated.
DataManager.java
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.FirebaseMessagingService;
public class DataManager {
public static String registerGoogleServiceInBackground( final OnDataManagerRegisterGooglePlayServiceListener listener )
{
boolean isEnabledPlayService = true;
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable( getContext() );
{
if( GooglePlayServicesUtil.isUserRecoverableError( resultCode ) )
{
GooglePlayServicesUtil.getErrorDialog( resultCode, (Activity) getContext(), PLAY_SERVICES_RESOLUTION_REQUEST ).show();
} else {
Log.i( "MainActivity.java|checkPlayService", "|This device is not supported.|" );
}
isEnabledPlayService = false;
}
if( isEnabledPlayService )
{
if( TextUtils.isEmpty( registrationId ) )
{
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground( Void... params )
{
String msg;
try {
FirebaseMessaging.getInstance().getToken()
.addOnCompleteListener(new OnCompleteListener<String>() {
#Override
public void onComplete(#NonNull Task<String> task) {
if (!task.isSuccessful()) {
Log.w(tag, "Fetching FCM registration token failed", task.getException());
return;
}
// Get new FCM registration token
registrationId = task.getResult();
}
});
msg = "Device registered, registration ID=" + registrationId;
Log.d("Device registered", "|" + registrationId);
} catch( Exception ex ) {
msg = "Error :" + ex.getMessage();
}
return msg;
}
#Override
protected void onPostExecute( String msg ){
if( listener != null ) listener.onFinish( true, registrationId );
Log.i( "MainActivity.java | onPostExecute", "|" + msg + "|" );
}
}.execute( null, null, null );
return "";
}
} else {
if( listener != null ) listener.onFinish( true, registrationId );
return registrationId;
}
} else {
Log.i( "MainActivity.java | onCreate", "|No valid Google Play Services APK found.|" );
if( listener != null ) listener.onFinish( true, registrationId );
return null;
}
}
}
I think it has to do with firebase dependencies versions mismatch. Try to import the BoM for the Firebase platform. By using BoM your app will always use compatible versions of the Firebase Android libraries.
In your app-level build.gradle you can try add the following;
dependencies {
// Import the BoM for the Firebase platform
implementation platform('com.google.firebase:firebase-bom:28.3.0')
// Declare the dependencies for the Firebase Cloud Messaging and Analytics libraries
// When using the BoM, you don't specify versions in Firebase library dependencies
implementation 'com.google.firebase:firebase-messaging'
implementation 'com.google.firebase:firebase-analytics'
}
You can following the instructions here https://firebase.google.com/docs/cloud-messaging/android/client
Cheers!
I think like handsben above that the problem lies in the dependencies. I have an alternative suggestion that worked for me. This was the error message supplied by android studio
Cannot resolve method 'getToken' in 'FirebaseMessaging'
In the dependencies (in the app-level build.gradle) was this line
implementation 'com.google.firebase:firebase-messaging:20.1.0'
When I clicked on it android studio suggested I change it to
implementation 'com.google.firebase:firebase-messaging:23.0.5'
which I did and the error went away.
Hope this could work for you too
I have a non static traduction file that I periodically get from a server. I cannont change the format of the file.
When I a new page is instanciate in my Ionic App I set this.traductions from a value in storage. See constructor below :
constructor(storage: Storage, public navCtrl: NavController, public navParams: NavParams, public commandeService: CommandeService, public alertCtrl: AlertController, public translate: TranslateService) {
this.categorie = this.navParams.get('categorie');
storage.get('boissons').then((StringBoissons) => {
var boissons: Array<any> = JSON.parse(StringBoissons);
this.boissons = boissons.filter(
(value) => {
return value.tboi_id == this.categorie.tboi_id;
});
}
);
storage.get('traductions').then((val) => {
this.traductions = JSON.parse(val);
});
this.commande = commandeService.getCommande();
this.translate = translate;
}
Then my View call getTraduction(...)
getTraduction(table, champ, id, objet) {
var traduction = this.traductions.find( (value) => {
return value.trad_table == table && value.trad_champ == champ && value.trad_code_langue == this.translate.currentLang && value.trad_id_item == objet[id];
});
if ( traduction && traduction.trad_texte )
return traduction.trad_texte;
else
return objet[champ];
}
Everything works fine in browser preview but on device I get a
Cannot call method 'find' of null
at t.getTraduction
I think it is due to asynchronous results but I can't quite get my head around it and figure how to solve this.
Thanks in advance for any insight
OK quite dumb question actually Sylvain :
You should have put your getTraduction function in a provider.
#Injectable()
export class TraductionDynaService {
traductions;
constructor(
public storage: Storage,
public http: Http,
public translate: TranslateService,
) {
storage.get('traductions').then((val) => {
this.traductions = JSON.parse(val);
});
}
getTraduction(table, champ, id, objet) {
var traduction = this.traductions.find( (value) => {
return value.trad_table == table && value.trad_champ == champ && value.trad_code_langue == this.translate.currentLang && value.trad_id_item == objet[id];
});
if ( traduction && traduction.trad_texte )
return traduction.trad_texte;
else
return objet[champ];
}
}
And it worked like a charm. getTraduction(...) could even return a promise to handle when traduction is null or undefined...
#Override
protected List< String > doInBackground( Void... params )
{
try
{
//This line below is the cause of the insufficient permissions error
ListMessagesResponse messagesWithLabels = mService.users().messages().list("me").setQ("label:inbox").execute();
/*for( Message m : messagesWithLabels.getMessages( ) )
{
if( m.size( ) > MAXSIZE )
{
List<MessagePartHeader> headers = m.getPayload( ).getHeaders( );
for ( MessagePartHeader header : headers )
{
if ( header.getName( ).equals("From") || header.getName().equals("Date")
|| header.getName().equals("Subject") || header.getName().equals("To")
|| header.getName().equals("CC")) {
messageDetails.put( header.getName( ).toLowerCase( ), header.getValue( ) );
}
}
messageDetails.put("snippet", m.getSnippet());
messageDetails.put("threadId", m.getThreadId());
messageDetails.put("id", m.getId());
messageDetails.put("body",m.getPayload().getBody().getData());
GmailFunctions.deleteThread( mService, "me", m.getId( ) );
messageDetails.clear( );
}
}*/
return getDataFromApi( );
}
catch ( Exception e )
{
mLastError = e;
cancel( true );
return null;
}
}
I have marked the line which is causing a 402 Insufficient permissions domain: global error. If I comment out said line the program will return the labels and print them to the screen without the permissions error. I have signed my release apk and set up the Google Play Developer console. The app is signing just fine it's SHA1 and I followed the sample application which retrieves credentials.
https://developers.google.com/gmail/api/quickstart/java
What to do about insufficient permissions?
Thank you.
The creation of mservice:
private class MakeRequestTask extends AsyncTask< Void, Void, List< String > > {
private com.google.api.services.gmail.Gmail mService = null;
private Exception mLastError = null;
ArrayList<String> sRemovalIds = new ArrayList<String>( );
List< String > inserts = new ArrayList< String >( );
Map<String, Object> messageDetails = new HashMap<String, Object>( );
public MakeRequestTask( GoogleAccountCredential credential )
{
HttpTransport transport = AndroidHttp.newCompatibleTransport( );
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance( );
mService = new com.google.api.services.gmail.Gmail.Builder(
transport, jsonFactory, credential )
.setApplicationName( "Gmail API Android Quickstart" )
.build( );
}
#Override
protected List< String > doInBackground( Void... params )
{
try
{
ListMessagesResponse messagesWithLabels = mService.users().messages().list("me").setQ("label:inbox").execute();
/*for( Message m : messagesWithLabels.getMessages( ) )
{
if( m.size( ) > MAXSIZE )
private static final String[ ] SCOPES = { GmailScopes.GMAIL_LABELS, GmailScopes.GMAIL_COMPOSE,
GmailScopes.GMAIL_INSERT, GmailScopes.GMAIL_MODIFY, GmailScopes.GMAIL_READONLY, GmailScopes.MAIL_GOOGLE_COM };
Using these scopes instead of the default with only GMAIL_LABELS worked for me.
You may also need to delete your previous credentials file after changing permissions. This is usually in $HOME/.credentials/
You may also need to delete your previous credentials file after changing permissions. It's token.json in the current folder.
Apart from setting scopes mentioned by #Eae, delete StoredCredential file, path of file is your_project/tokens/StoredCredential.