I'm refactoring in_app_purchases and I'm trying to get the past purchases. According to the documentation:
The InAppPurchaseConnection.queryPastPurchases method has been
removed. Instead, you should use InAppPurchase.restorePurchases. This
method emits each restored purchase on the
InAppPurchase.purchaseStream, the PurchaseDetails object will be
marked with a status of PurchaseStatus.restored
But the example they provide doesn't get the past purchases, it adds the one you buy at that moment.
I moved from this:
final QueryPurchaseDetailsResponse purchaseResponse =
await _connection.queryPastPurchases();
to this:
final Stream<List<PurchaseDetails>> purchaseUpdated = inAppPurchase.purchaseStream;
print(purchaseUpdated.toList());
I tried the above but the list is empty and for sure my user has purchases as I can show here when I try to buy the same version I bought before:
How could get a List from previous purchases?
You have to listen to the purchaseStream like this code in the example:
final Stream<List<PurchaseDetails>> purchaseUpdated =
_inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
All the purchased items will be added to this stream, so you need to add all the results to your list like below:
final List<PurchaseDetails> purchasedList = [];
final Stream<List<PurchaseDetails>> purchaseUpdated =
_inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
purchasedList.addAll(purchaseDetailsList);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
Now you can use purchasedList as previous purchases. By the way, all the newly purchased items will be also added to that stream and purchasedList.
UPDATE: After the above steps, you need to call _inAppPurchase.restorePurchases() to get the previous purchases, all the previous purchases will be added to purchasedList by the purchaseStream.
Related
I am importing a product catalog using Firebase DB with Flutter. Only 1 person can add the product to the cart. If 2 or more people want to add the product to the basket at the same time, the product is added to all of them. What can I do to get this back?
void addBasket(Map<String, dynamic> data, index) async {
var _storage = await GetStorage();
final CollectionReference user_basket =
await FirebaseFirestore.instance.collection('user basket');
final CollectionReference product =
await FirebaseFirestore.instance.collection('product');
final DocumentReference ref = await product.doc(data['id']);
await FirebaseFirestore.instance.runTransaction((Transaction tx) async {
DocumentSnapshot snap = await tx.get(ref);
if (snap.exists) {
await product.doc(data['id']).delete();
await tx.update(ref, data);
await user_basket
.doc(_storage.read('uid'))
.collection('basket')
.doc(data['id'])
.set(data);
inArea.removeAt(index);
Get.back();
Get.back();
} else {
await user_basket
.doc(_storage.read('uid'))
.collection('basket')
.doc(data['id'])
.delete();
Get.back();
Get.defaultDialog(
title: "Wrong",
middleText:
"This product is being reviewed by another person.");
}
});
}
I tried to use Transaction as you can see in the code. I tested this code the first time I wrote it. when two people pressed the button at the same time, one of the 2 could add the product to the cart. I wanted to test again today. I pressed the add product to cart button, as a result both the If and Else blogs worked. So I started getting errors. The exact result I want to get is when more than 1 person wants to add the same product to the cart, to get the opposite. Let the first click on the button add the product to the basket, and the others will be informed through the Alerd Dialog.
I believe you are creating a race condition between clients. In all of my implementations of Firestore, I do not use await on the collection or query references. You should only need to await the get() method itself.
When you look at Flutter Firestore documentation for their implementation of transactions, you'll see they do not await the collection reference at the top.
https://firebase.google.com/docs/firestore/manage-data/transactions#dart
Instead of:
final CollectionReference user_basket =
await FirebaseFirestore.instance.collection('user basket');
final CollectionReference product =
await FirebaseFirestore.instance.collection('product');
final DocumentReference ref = await product.doc(data['id']);
Try using:
final CollectionReference user_basket = FirebaseFirestore.instance.collection('user basket');
final CollectionReference product = FirebaseFirestore.instance.collection('product');
final DocumentReference ref = product.doc(data['id']);
I hope that helps! I will try and put together a Firestore transactions demo to see if I can replicate and resolve that issue you described.
It could be data race issue. you can use transaction class to prevent it.
Buying a product works fine but when I try to get the bought products on startup of the app the listening is not working.
The logger.i("purchaseInit") message is not displayed.
purchaseInit() {
StreamSubscription<List<PurchaseDetails>> _subscription;
final Stream purchaseUpdated = InAppPurchase.instance.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
logger.i("purchaseInit"); // not showing up
_listenToPurchaseUpdated(purchaseDetailsList);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error
});}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
logger.i("_showPendingUI()");
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
logger.i(purchaseDetails.error);
} else if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
logger.i(purchaseDetails);
}
if (purchaseDetails.pendingCompletePurchase) {
await InAppPurchase.instance.completePurchase(purchaseDetails);
}
}
});
}
It seems, you are just listening to purchases by subscribing to the stream. But previous purchases are not emitted by the stream upon startup of the app or upon subscribing to it.
In order to get a list of previous purchases, you must trigger a restore.
await InAppPurchase.instance.restorePurchases();
This will re-emit the previous purchases of non-consumable products and your listener will be able to handle them.
However, I suggest that you try to keep track of what the user purchased yourself and only call restorePurchases() if necessary.
https://pub.dev/packages/in_app_purchase#restoring-previous-purchases
trying to make a 1:1 video meeting with agora with flutter and after following the docs i got
AgoraRtcException(20, Make sure you call RtcEngine.initialize first) exception although I am sure I am initializing it first however this the initialize code
void initState() {
super.initState();
setupVideoSDKEngine();
join();
the setupVideoSDKEngine() method code is
Future<void> setupVideoSDKEngine() async {
// retrieve or request camera and microphone permissions
await [Permission.microphone, Permission.camera].request();
//create an instance of the Agora engine
agoraEngine = createAgoraRtcEngine();
await agoraEngine
.initialize(RtcEngineContext(appId: Environment.agoraAppId));
await agoraEngine.enableVideo();
// Register the event handler
agoraEngine.registerEventHandler(
RtcEngineEventHandler(
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
showMessage(
"Local user uid:${connection.localUid} joined the channel");
setState(() {
_isJoined = true;
});
},
onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
showMessage("Remote user uid:$remoteUid joined the channel");
setState(() {
_remoteUid = uid;
player.stop();
customTimer!.resetAndStart();
});
},
onUserOffline: (RtcConnection connection, int remoteUid,
UserOfflineReasonType reason) {
showMessage("Remote user uid:$remoteUid left the channel");
callEnded = true;
setState(() {
_remoteUid = null;
});
print('stats ${reason.name}');
if (!userOffline) {
Future.delayed(Duration(seconds: 1), () => Navigator.pop(context));
}
userOffline = true;
},
),
);
}
I am expecting to join the channel but nothing happens and it throws this error
I tried to delete the app and reinstall it but nothing happens
and got this exception too AgoraRtcException(-17, null)
you can to call this before using any other agora function i.e. You are trying to use agora sdk function without initializing it so here it is
This will be written globally
late final RtcEngineEx _engine;
this will be written in initState
_engine = createAgoraRtcEngineEx();
await _engine.initialize(const RtcEngineContext(
appId: APP_ID,
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));
This piece of advice I'm giving is for the correct usage of async and await keywords.
In your initState() function, you are calling two functions one after another; out of which the first function setupVideoSDKEngine() is an async function, and another function join() chould also be an async because the Agora join channel code returns a Future.
await engine.joinChannel(agoraToken, channelId, '', 0,);
Your code right now does not wait for the engine initialization and starts joining channel. Thus, the error. So you got to await your initialization and then write your join channel code.
For eg.
/* permission stuffs */
await agoraEngine.initialize(RtcEngineContext(appId: Environment.agoraAppId));
/* do event handling tasks */
// Now join the channel.
await engine.joinChannel(agoraToken, channelId, '', 0,);
It is recommended that you put the whole code into one async function.
I use in-app purchase in my app. Hassle free purchase. But when I want to buy the same product for the second time, this Play Store shows me "You Already Have This Item". I want my products to be bought again and again. I don't have any idea how I can solve it. Thank you very much for the help. I left my source codes below.
InAppPurchase iap = InAppPurchase.instance;
bool available = true;
List<ProductDetails> products = [];
List<PurchaseDetails> purchases = [];
StreamSubscription subscription;
int credits = 0;
#override
void initState() {
initialize();
super.initState();
}
#override
void dispose() {
subscription.cancel();
super.dispose();
}
void initialize() async {
available = await iap.isAvailable();
if (available) {
await getProducts();
verifyPurchase();
subscription = iap.purchaseStream.listen((data) => setState(() {
print('New Purchase');
purchases.addAll(data);
verifyPurchase();
}));
}
}
Future<void> getProducts() async {
Set<String> ids = Set.from(['buy_300_wecoin']);
ProductDetailsResponse response = await iap.queryProductDetails(ids);
setState(() {
products = response.productDetails;
});
}
PurchaseDetails hasPurchased(String productID) {
return purchases.firstWhere((purchase) => purchase.productID == productID,
orElse: () => null);
}
bool verifyPurchase() {
PurchaseDetails purchase = hasPurchased('buy_300_wecoin');
if (purchase != null && purchase.status == PurchaseStatus.purchased) {
return true;
}
}
void buyProduct(ProductDetails prod) {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: prod);
iap.buyConsumable(purchaseParam: purchaseParam, autoConsume: false);
}
you should use Consumable In-App Purchase
A consumable product is one that a user consumes to receive in-app content, such as in-game currency. When a user consumes the product, your app dispenses the associated content, and the user can
then purchase the item again.
Note that the App Store does not have any APIs for querying consumable products, and Google Play considers consumable products to no longer be owned once they're marked as consumed and fails to return them here. For restoring these across devices you'll need to persist them on your own server and query that as well.
Try in_app_purchase library and read about consumable IAP using this library.
I am trying to implement a subscription system in my Flutter app with the in_app_purchase package. All I found in the documentation is that I need an old purchase to handle the subscription:
final PurchaseDetails oldPurchaseDetails = ...;
PurchaseParam purchaseParam = PurchaseParam(
productDetails: productDetails,
changeSubscriptionParam: ChangeSubscriptionParam(
oldPurchaseDetails: oldPurchaseDetails,
prorationMode: ProrationMode.immediateWithTimeProration));
InAppPurchaseConnection.instance
.buyNonConsumable(purchaseParam: purchaseParam);
I already have the productDetails.
How can I subscribe to it for the first time?
You don't need an old subscription to subscribe for the first time, the parameter changeSubscriptionParam is optional and you can pass null for it. When you are going to change the subscription, you will need to pass the old subscription in this parameter in the changeSubscriptionParam
parameter. This is only needed for android and a simple way to do get the old subscription is (in my case there are only two possible signatures):
Future<GooglePlayPurchaseDetails> _getOld() async {
if (Platform.isAndroid) {
final InAppPurchaseAndroidPlatformAddition androidAddition =
_inAppPurchase
.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
QueryPurchaseDetailsResponse oldPurchaseDetailsQuery =
await androidAddition.queryPastPurchases();
oldPurchaseDetailsQuery.pastPurchases.forEach((element) {
oldPurchaseDetails = element;
});
}
return oldPurchaseDetails;
}