how to check if iap transactions are finished? - flutter

i am using https://pub.dev/packages/flutter_inapp_purchase this package for in-app-purchases. i sold credit but users can receive more credit then they paid, as shown in this video :
https://youtu.be/Bo5MQdi5wGY
how can i fix this bug ? help me , please. thanks.
All relevant lines of code are in below;
code:
Future<void> initPlatformState() async {
await Purchases.setDebugLogsEnabled(true);
await Purchases.setup("RowkOdfhhydoMREZKRckltCNUNzJqgnj");
String platformVersion;
try {
platformVersion = await FlutterInappPurchase.instance.platformVersion;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
var result = await FlutterInappPurchase.instance.initConnection;
print('result: $result');
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
try {
String msg = await FlutterInappPurchase.instance.consumeAllItems;
print('consumeAllItems: $msg');
} catch (err) {
print('consumeAllItems error: $err');
}
_conectionSubscription =
FlutterInappPurchase.connectionUpdated.listen((connected) {
print('connected: $connected');
});
_purchaseUpdatedSubscription =
FlutterInappPurchase.purchaseUpdated.listen((productItem) {
// String deviceId = await PlatformDeviceId.getDeviceId;
dispose();
initPlatformState();
//FlutterInappPurchase.instance.finishTransaction(productItem);
if (productItem.productId == "oddinbetcredit10") {
print("CREDİT 10 !!!");
addCredit(deviceid, "10").then((value) => {
tipController.updateCredit(),
Fluttertoast.showToast(
msg: "Successfully loaded 10 credit !...",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.SNACKBAR,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
)
});
} else if (productItem.productId == "oddinbetcredit20") {
print("CREDİT 20 !!!");
addCredit(deviceid, "20").then((value) => {
tipController.updateCredit(),
Fluttertoast.showToast(
msg: "20 credits successfully loaded !...",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.SNACKBAR,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
)
});
} else if (productItem.productId == "oddinbetcredit50") {
print("CREDİT 50 !!!");
addCredit(deviceid, "50").then((value) => {
tipController.updateCredit(),
Fluttertoast.showToast(
msg: "50 credits successfully loaded !...",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.SNACKBAR,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
)
});
} else if (productItem.productId == "oddinbetcredit100") {
print("CREDİT 100 !!!");
addCredit(deviceid, "100").then((value) => {
tipController.updateCredit(),
Fluttertoast.showToast(
msg: "100 crdits successfully loaded!...",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.SNACKBAR,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
)
});
} else if (productItem.productId == "oddinbetcreditvip"){
isPremium = true;
Fluttertoast.showToast(
msg: "VIP successfully loaded!...",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.SNACKBAR,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
);
}
});
_purchaseErrorSubscription =
FlutterInappPurchase.purchaseError.listen((purchaseError) {
print('purchase-error: $purchaseError');
});
}

Actually, this is a bug in this plugin and it is still open on Github
The reason why you are getting more credits is because the purchaseUpdated listener receives multiple callbacks on iOS and hence your addCredit() function is called multiple times.
As a workaround, you can use the transactionId as a flag and avoid calling the code inside the purchaseUpdated listener multiple times.
Add a check
if(lastTransactionId!=productItem.transactionId)
And initialize the lastTransactionId outside the if statement body
lastTransactionId = productItem.transactionId;
Complete code:
Declare a global variable
String lastTransactionId;
And then update your listener code with this
_purchaseUpdatedSubscription =
FlutterInappPurchase.purchaseUpdated.listen((productItem) {
// String deviceId = await PlatformDeviceId.getDeviceId;
if(lastTransactionId!=productItem.transactionId){ //Added this to avoid multiple callbacks for the same transaction
dispose();
initPlatformState();
//FlutterInappPurchase.instance.finishTransaction(productItem);
if (productItem.productId == "oddinbetcredit10") {
print("CREDİT 10 !!!");
addCredit(deviceid, "10").then((value) => {
tipController.updateCredit(),
Fluttertoast.showToast(
msg: "Successfully loaded 10 credit !...",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.SNACKBAR,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
)
});
} else if (productItem.productId == "oddinbetcredit20") {
print("CREDİT 20 !!!");
addCredit(deviceid, "20").then((value) => {
tipController.updateCredit(),
Fluttertoast.showToast(
msg: "20 credits successfully loaded !...",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.SNACKBAR,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
)
});
} else if (productItem.productId == "oddinbetcredit50") {
print("CREDİT 50 !!!");
addCredit(deviceid, "50").then((value) => {
tipController.updateCredit(),
Fluttertoast.showToast(
msg: "50 credits successfully loaded !...",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.SNACKBAR,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
)
});
} else if (productItem.productId == "oddinbetcredit100") {
print("CREDİT 100 !!!");
addCredit(deviceid, "100").then((value) => {
tipController.updateCredit(),
Fluttertoast.showToast(
msg: "100 crdits successfully loaded!...",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.SNACKBAR,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
)
});
} else if (productItem.productId == "oddinbetcreditvip"){
isPremium = true;
Fluttertoast.showToast(
msg: "VIP successfully loaded!...",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.SNACKBAR,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
);
}
}
lastTransactionId = productItem.transactionId; //finally initialize your lastTransactionId with productItem.transactionId, outside the if statement
});
PS: You should call the await FlutterInappPurchase.instance.initConnection; method only once.
I hope this answer helps!

Related

How can I change this code to work without calling method

I have this code which accepts title and return a custom version of FlutterToast.
class CustomToast {
CustomToast(this.title);
final String title;
Future buildWidget() async {
return Fluttertoast.showToast(
msg: title,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.black.withOpacity(0.7),
textColor: Colors.white,
fontSize: 16.0,
);
}
}
I need to call CustomToast('Toast1').buildWidget(); to make it work. How to convert the CustomToast so that I can use CustomToast.buildWidget('Toast1')?
class CustomToast {
// pass the value into buildwidget method and make the method static
static Future buildWidget(String title) async {
return Fluttertoast.showToast(
msg: title,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.black.withOpacity(0.7),
textColor: Colors.white,
fontSize: 16.0,
);
}
}

In flutter I am getting user input in form which I wants to save.But i am facing issue where 1 integer field can be null and user may/may not fill it

On the click of below elevated button I am calling method and sending all data collected from user.Its working fine with string values means whenever user fills or not fills the data for those string values it still saves the data but when user don't fill int field it doesn't saves it. The form should be saved even if user don't fill the int field:
Container(
//alignment: Alignment.center,
height: 35,
width: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color.fromARGB(255, 3, 89, 168),
),
child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Colors.transparent,
// shadowColor: Colors.transparent,
// // shape: RoundedRectangleBorder(borderRadius: borderRadius),
// ),
onPressed: () {
// _formKey.currentState?.validate();
// final isValidForm = _formKey.currentState!.validate();
// if (isValidForm) {
final aptId = apID;
print('apt id add $aptId');
final int docId = dID;
print('doc id add $dID');
final int staffId = staff_Id;
print('add staff id $staffId');
final String partyName = partyNameController.text;
final String partyType = selectedValue.toString();
final String aptStatus = selectedItem.toString();
final String? paymentMode = selectedMode.toString();
//final String paymentMode = paymentModeController.text;
final String aptplace = aptplaceController.text;
final String city = cityController.text;
final String aptDate = aptDateController.text;
final String aptTime = aptTimeController.text;
final int? feesCollected =
int.parse(feesCollectedController.text).toInt();
final int totalFees =
int.parse(totalFeesController.text).toInt();
final String contactNo = contactNoController.text;
final String? comments = commentsController.text;
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => AppointmentPageFE()));
setState(() {
_futureAppointment = createAppointment(
aptId,
docId,
staffId,
partyName,
contactNo,
partyType,
aptplace,
city,
feesCollected ?? 0,
totalFees,
paymentMode ?? '',
aptDate,
aptTime,
// aptExecutive,
aptStatus,
comments ?? '',
);
});
// }
// }
},
child: const Text("Save"),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Color.fromARGB(255, 3, 89, 168))),
))
The mothod where API is calling:
Future<Appointment> createAppointment(
int? aptId,
int docId,
int staffId,
//String docTitle,
// String tokenNo,
String partyName,
String contactNo,
String partyType,
String aptPlace,
String city,
int? feesCollected,
int totalFees,
String paymentMode,
String aptDate,
String aptTime,
// String aptExecutive,
String aptStatus,
String comments,
// String createdAt,
) async {
final response = await http.put(
// Uri.parse('http://192.168.0.134:8080/AtdochubJ-3/appointment/register'),
Uri.parse('http://3.108.194.111:8080/AtdochubJ-3/appointment/register'
// 'http://3.108.194.111:8080/AtdochubJ-3/appointment/register'
),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, dynamic>{
'aptId': aptId,
'docId': docId,
'staffId': staffId,
// 'docTitle': docTitle,
// 'tokenNo': tokenNo,
'partyName': partyName,
'contactNo': contactNo,
'partyType': partyType,
'aptPlace': aptPlace,
'city': city,
'feesCollected': feesCollected ?? 0,
'totalFees': totalFees,
'paymentMode': paymentMode,
'aptDate': aptDate,
'aptTime': aptTime,
//'aptExecutive': aptExecutive,
'aptStatus': aptStatus,
'comments': comments,
// 'createdAt': createdAt,
}));
if (response.statusCode == 200 || response.statusCode == 201) {
Fluttertoast.showToast(
msg: "Appointment Scheduled Successfully",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 3,
backgroundColor: Color.fromARGB(255, 77, 171, 248),
textColor: Colors.white,
fontSize: 12.0);
print("response.statusCode ${'successful'}");
return Appointment.fromJson(jsonDecode(response.body)[0]);
} else {
if (response.statusCode == 500) {
Fluttertoast.showToast(
msg: "Internal Server Error !",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 3,
backgroundColor: Color.fromARGB(255, 77, 171, 248),
textColor: Colors.white,
fontSize: 12.0);
} else {
Fluttertoast.showToast(
msg: "Failed to schedule appointment",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 3,
backgroundColor: Color.fromARGB(255, 77, 171, 248),
textColor: Colors.white,
fontSize: 12.0);
}
}
throw Exception(' Failed To Create Appointment');
}

Flutter Firebase auth phone and Firestore database

I would like when connecting by phone via OTP verification, to know if the account already exists and in this case retrieve its information. But if the account does not exist, create a collection in Firestore database with default information
My functional code for the telephone connection:
class AuthenticateScreen extends StatefulWidget {
#override
_AuthenticateScreenState createState() => _AuthenticateScreenState();
}
class _AuthenticateScreenState extends State<AuthenticateScreen> {
TextEditingController phoneController = TextEditingController();
TextEditingController otpController = TextEditingController();
FirebaseAuth auth = FirebaseAuth.instance;
bool otpVisibility = false;
User? user;
String verificationID = "";
String phoneNumber = "";
final DocumentReference documentReference =
FirebaseFirestore.instance.doc("users/{users}");
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
"Phone Auth",
style: TextStyle(
fontSize: 30,
),
),
backgroundColor: Colors.indigo[900],
),
body: Container(
margin: EdgeInsets.all(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: phoneController,
decoration: InputDecoration(
hintText: 'Phone Number',
prefix: Padding(
padding: EdgeInsets.all(4),
child: Text('+33'),
),
),
maxLength: 10,
keyboardType: TextInputType.phone,
),
Visibility(
child: TextField(
controller: otpController,
decoration: InputDecoration(
hintText: 'OTP',
prefix: Padding(
padding: EdgeInsets.all(4),
child: Text(''),
),
),
maxLength: 6,
keyboardType: TextInputType.number,
),
visible: otpVisibility,
),
SizedBox(
height: 10,
),
MaterialButton(
color: Colors.indigo[900],
onPressed: () {
if (otpVisibility) {
verifyOTP();
} else {
loginWithPhone();
}
},
child: Text(
otpVisibility ? "Verify" : "Login",
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
),
],
),
),
);
}
void loginWithPhone() async {
auth.verifyPhoneNumber(
phoneNumber: "+33" + phoneController.text,
verificationCompleted: (PhoneAuthCredential credential) async {
await auth.signInWithCredential(credential).then((value) {
print("You are logged in successfully");
});
},
verificationFailed: (FirebaseAuthException e) {
print(e.message);
},
codeSent: (String verificationId, int? resendToken) {
otpVisibility = true;
verificationID = verificationId;
setState(() {});
},
codeAutoRetrievalTimeout: (String verificationId) {},
);
}
void verifyOTP() async {
PhoneAuthCredential credential = PhoneAuthProvider.credential(
verificationId: verificationID, smsCode: otpController.text);
await auth.signInWithCredential(credential).then(
(value) {
setState(() {
phoneNumber = phoneController.text;
print("Connecte avec le compte " + "+33" + phoneController.text,);
user = FirebaseAuth.instance.currentUser;
});
},
).whenComplete(
() {
if (user != null) {
Fluttertoast.showToast(
msg: "Vous êtes maintenant connecté",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Blue2Color,
textColor: Colors.white,
fontSize: 16.0,
);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => Home_Screen(),
),
);
} else {
Fluttertoast.showToast(
msg: "La connexion a échoué.",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0,
);
}
},
);
}
}
And here is my old code for login by mail to create a collection on firestore database :
class AuthenticationService {
final FirebaseAuth _auth = FirebaseAuth.instance;
AppUser? _userFromFirebaseUser(User? user) {
initUser(user);
return user != null ? AppUser(user.uid) : null;
}
void initUser(User? user) async {
if(user == null) return;
NotificationService.getToken().then((value) {
DatabaseService(user.uid).saveToken(value);
});
}
Stream<AppUser?> get user {
return _auth.authStateChanges().map(_userFromFirebaseUser);
}
Future signInWithEmailAndPassword(String email, String password) async {
try {
UserCredential result =
await _auth.signInWithEmailAndPassword(email: email, password: password);
User? user = result.user;
return _userFromFirebaseUser(user);
} catch (exception) {
print(exception.toString());
return null;
}
}

FATAL EXCEPTION: main Process: com.flutter_image, PID: 32038 kotlin.KotlinNullPointerException

I am getting this error and app crashes when I am pressing the button after compressing the image plzz help me
Thanks in advance
this is my code
onPressed: () async {
print('Selected Item = '+'$radioItemHolder');
if (radioItemHolder.contains('High')) {
print('High');
for (int i = 0;i < widget.image.length;i++) {
var path = await FlutterAbsolutePath.getAbsolutePath(
widget.image[i].identifier);
print("path");
print(path);
File compressedFile =await FlutterNativeImage.compressImage(path, quality:
90);
images.add(compressedFile);
GallerySaver.saveImage(compressedFile.path, albumName: 'Image Resizer')
.then((bool success) {
Fluttertoast.showToast(msg: "Image saved to gallary",toastLength:
Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 5,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
});
}
Navigator.push(context,
MaterialPageRoute(builder: (context) => Share_Convert_Iamge(image:
images)),
);
}}
First, download this package:
https://pub.dev/packages/common_utils
Then try to force wait for the compression to complete, before running the navigation logic. Just do this:
import 'package:common_utils/common_utils.dart';
///.........
Future runImageCompressionLogic() async {
for (int i = 0;i < widget.image.length;i++) {
var path = await FlutterAbsolutePath.getAbsolutePath(
widget.image[i].identifier);
print("path");
print(path);
await FlutterNativeImage.compressImage(path, quality:
90).then((compressedFile) {
if (!TextUtil.isEmpty(compressedFile)) {
images.add(compressedFile);
GallerySaver.saveImage(compressedFile.path,
albumName: 'Image Resizer')
.then((bool success) {
Fluttertoast.showToast(msg: "Image saved to
gallary",toastLength:
Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 5,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
});
});
}
}
}
}
///.....
onPressed: () async {
print('Selected Item = '+'$radioItemHolder');
if (radioItemHolder.contains('High')) {
print('High');
runImageCompressionLogic().whenComplete((){
Navigator.push(context,
MaterialPageRoute(builder: (context) =>
Share_Convert_Iamge(image:
images)),
});
);
}}

How do I get the total price of all the items in the basket

How do I get the total price of all the items in the basket?
I want to get the total price of all the items in the cart after adding items in the cart. The price should also increase when the quantity of each item is increased and also decrease when the quantity is decreased. Please help me with that,
The total variable in the cart class outputs null
The cart class
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'dish_object.dart';
class Cart extends StatefulWidget {
final List<Dish> _cart;
Cart(this._cart);
#override
_CartState createState() => _CartState(this._cart);
}
class _CartState extends State<Cart> {
_CartState(this._cart);
List<Dish> _cart;
double totalPrice = 0;
double getTotalPrice() {
double total = 0;
_cart.forEach((item) {
total += item.totalPrice;
});
setState(() {
totalPrice = total;
});
}
#override
void initState() {
super.initState();
getTotalPrice();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Cart'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.send_rounded),
tooltip: "Confirm Order",
onPressed: () {
if (_cart.isNotEmpty) {
setState(() {
Fluttertoast.showToast(
msg: "Order Confirmed",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.grey,
textColor: Colors.white,
fontSize: 16.0);
});
}
if (_cart.isEmpty) {
setState(() {
Fluttertoast.showToast(
msg: "Cart Empty",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
});
}
}),
if (_cart.length > 0)
Padding(
padding: const EdgeInsets.only(left: 0.0),
child: CircleAvatar(
radius: 10.0,
backgroundColor: Colors.red,
foregroundColor: Colors.white,
child: Text(
_cart.length.toString(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12.0,
),
),
),
),
],
),
body: ListView.builder(
itemCount: _cart.length,
itemBuilder: (context, index) {
var item = _cart[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 2.0),
child: Card(
elevation: 4.0,
child: ListTile(
//Leading
leading: Text(item.totalPrice.toString() +
item.category +
"\n" +
"R" +
item.price.toString()),
//Title
title: Text(item.brandName +
"\n" +
"(" +
item.counter.toString() +
")"),
//Subtitle
subtitle: GestureDetector(
child: Icon(
Icons.add,
color: Colors.green,
),
onTap: () {
setState(() {
item.incrementCounter();
});
},
),
//Trailing
trailing: GestureDetector(
child: Icon(
Icons.remove_circle,
color: Colors.red,
),
onTap: () {
setState(() {
item.decrementCounter();
});
},
),
isThreeLine: true,
),
),
);
},
),
);
}
}
You could try adding a variable _totalPrice and accessing that
double _totalPrice = 0;
which can be changed by one general function:
double getTotalPrice() {
double total = 0;
_cart.forEach((item) {
total+= item.totalPrice;
});
setState(() {
_totalPrice = total;
});
}
add getTotalPrice() to your initState() function, so it gets called when the screen is mounted or somewhere else in your code.
or increase/decrease the total value every time a product is added/deleted
_addPrice(double price) {
setState(() {
_totalPrice += price;
});
}
_subtractPrice(double price) {
setState(() {
_totalPrice -= price;
});
}
--
edit: And make sure to insert the variable wherever in your view you want to show it
cartTotalPrice() doesn't really do much except for keeping the total price locally. And it's never called. Declare the total variable outside and wrap the for each body in setState() or just return it from the method. Don't forget to reset the total variable after adding a new item to the _cart or simply just add the new items price to the total.