flutter: Can't detect subscription renewal (in_app_purchase) - android

The bounty expires in 6 days. Answers to this question are eligible for a +50 reputation bounty.
mr.Penguin wants to draw more attention to this question.
I'm using a tool called in_app_purchase (version: ^3.1.4 ) to manage subscriptions in my app. It's working well, and I'm able to give users access to the product they're subscribing to.
Right now, my app is in test mode. I get emails from Google to let me know if a subscription has been canceled, renewed, or if it's new.
When a subscription is renewed, I want to know about it so I can update the user's account. To do that, I use a method called restorePurchases() to check past purchases. I then compare the date of the latest purchase with the one I have stored in my FireStore database. If the new purchase is more recent than the last one, I update the user's account and give them access to the product they're subscribed to.
Bellow is how I implemented that logic:
initState method:
#override
void initState() {
super.initState();
final Stream purchaseUpdated = _inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen(
(purchaseDetailsList) {
loggy.info("I AM LISTENING....");
_listenToPurchaseUpdated(purchaseDetailsList);
},
onDone: () {
_subscription.cancel();
},
onError: (error) {
// handle error here.
},
);
_inAppPurchase.restorePurchases();//here I trigger the purchaseStream each time initState is called
}
_listenToPurchaseUpdated method:
Future<void> _listenToPurchaseUpdated(purchaseDetailsList) async {
for (final PurchaseDetails purchaseDetails in purchaseDetailsList) {
if (purchaseDetails.status == PurchaseStatus.pending) {
showPendingUI();
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
hendleError(purchaseDetails);
} else if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
if (!mounted) return;
final lastTransactionDate =
await SubscriptionService().getLastTransactionDate();
if (lastTransactionDate.isNotEmpty) {
loggy.info(
"last transaction date: $lastTransactionDate",
);
}
loggy.info(
"current transaction date: ${purchaseDetails.transactionDate}",
);
if (lastTransactionDate.isEmpty ||
lastTransactionDate != purchaseDetails.transactionDate) {
final bool isRecentRenewal = DateTime.fromMillisecondsSinceEpoch(
int.parse(purchaseDetails.transactionDate!),
).isAfter(
DateTime.fromMillisecondsSinceEpoch(
int.parse(lastTransactionDate),
),
);
if (isRecentRenewal) {
loggy.info("delivering product in progress....");
await deliverProduct(purchaseDetails);
}
}
}
if (purchaseDetails.pendingCompletePurchase) {
await _inAppPurchase.completePurchase(purchaseDetails);
}
}
}
}
In the above method, I'm just getting the last transactionDate that was stored in FireStore and comparing it with the current transactionDate, this field can be found on:
purchaseDetails.transactionDate
Unfortunately, when the subscription is renewed, I can't detect it in the app, and the transactionDate remains the same:

Related

Flutter queryPastPurchases() returns empty list (android)

I want to know if user has active subscription when the app starts.
Future checkPastSubsAndroid() async {
final InAppPurchaseAndroidPlatformAddition androidAddition = InAppPurchase
.instance
.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
var purchases = await androidAddition.queryPastPurchases();
if (purchases.error != null) {
} else {
await Future.forEach(purchases.pastPurchases, (purchaseDetails) async {
if (purchaseDetails.billingClientPurchase.isAcknowledged) {
subActive = true;
}
});
}}
But even after purchasing purchases.pastPurchases is empty. The test subscription is active (I can see it in Google Play, and get 'already purchased' if try to buy again) but this method didn't return it.

Flutter workmanager with home_widget working in debug and profile, but not in Android build APK

I have a todo app built in Flutter and intended only for Android. I built a home screen widget for it (using the home_widget package in Flutter) to allow users to see a list of tasks and check them off directly from the widget.
At midnight, the tasks should reset with the new tasks for the day (I used the workmanager package to accomplish this, although I also tried the android_alarm_manager_plus package, with the same results). All of this functionality is working perfectly in debug mode, and even in profile mode (I can't test it in release mode because, according to my understanding, that would remove services and thus the home_widget would not work; however, when I do the build, that doesn't seem to be the problem because the home widget still shows up). BUT! When I build the release APK and submit it to Google Play for internal testing, then download it onto my Pixel 7 (with no power saving modes on, as far as I'm aware), the midnight function does not run. :(
Here's the relevant code:
main_prod.dart
void main() async {
return mainGeneric('Prod Name', ProdFirebaseOptions.currentPlatform, Environment.prod);
}
main_generic.dart
/// Used for Background Updates using Workmanager Plugin
#pragma('vm:entry-point')
void workmanagerCallbackDispatcher() {
Workmanager().executeTask((taskName, inputData) {
if (taskName == 'widgetBackgroundUpdate') {
try {
return Future.wait<void>([
// This is a static Future<void> function from a helper class that resets
// the tasks; it seems to be working when I test it by itself, as well as
// in debug or profile mode.
MidnightService.resetTasks(),
]).then((value) {
return Future.value(true);
});
} catch(err) {
print(err.toString());
throw Exception(err);
}
}
return Future.value(true);
});
}
void _startBackgroundUpdate() async {
if (await MidnightService.shouldUpdateWorkManagerTasks()) {
(await SharedPreferences.getInstance()).setInt('midnight_tasks_update_version', Constants.MIDNIGHT_TASKS_UPDATE_VERSION);
await Workmanager().cancelAll();
DateTime now = DateTime.now();
int nowMillis = now.millisecondsSinceEpoch;
int midnightTonightMillis = DateTime(now?.year ?? 0, now?.month ?? 0, (now?.day ?? 0) + 1).millisecondsSinceEpoch;
int millisUntilMidnight = midnightTonightMillis - nowMillis;
await Workmanager().registerPeriodicTask('midnightUpdate', 'widgetBackgroundUpdate', initialDelay: Duration(milliseconds: millisUntilMidnight), frequency: Duration(days: 1));
}
}
void mainGeneric(String appName, FirebaseOptions firebaseOptions, Environment environment) async {
// Avoid errors caused by flutter upgrade.
WidgetsFlutterBinding.ensureInitialized();
Workmanager().initialize(workmanagerCallbackDispatcher, isInDebugMode: kDebugMode).then((_) => _startBackgroundUpdate());
...
// If this is the first time opening the app with widget functionality.
HomeWidget.getWidgetData<String>('todays_tasks_string', defaultValue: '').then((todaysTasksString) async {
if (todaysTasksString == '') {
List<Task> todaysTasks = await Repositories().taskRepository.getFocusedTasks();
await HomeWidgetUtils.setTodaysTasks(todaysTasks);
return true;
}
return false;
});
Firebase.initializeApp(
name: appName,
options: firebaseOptions,
).then((_) async {
...
});
HomeWidget.registerBackgroundCallback(homeWidgetBackgroundCallback);
runApp(AppConfig(
child: MyApp(),
environment: environment,
appTitle: appName,
));
}
// Called when doing background work initiated from home screen widget
#pragma('vm:entry-point')
Future<void> homeWidgetBackgroundCallback(Uri uri) async {
if (uri.host.startsWith('completetask_')) {
String todaysTasksString = await HomeWidgetUtils.updateTaskById(uri.host.split('_')[1], true);
await HomeWidget.saveWidgetData<String>('todays_tasks_string', todaysTasksString);
await HomeWidget.updateWidget(androidName: 'TodaysTasksWidgetProvider');
}
}
midnight_service.dart
class MidnightService {
...
static Future<bool> shouldUpdateWorkManagerTasks() async {
try {
final prefs = await SharedPreferences.getInstance();
int midnightTasksUpdateVersion = prefs.getInt('midnight_tasks_update_version');
return Constants.MIDNIGHT_TASKS_UPDATE_VERSION > midnightTasksUpdateVersion;
}
catch (e) { print(e); }
return true;
}
}
It might also be valuable to note that, when a user checks off a task from the home screen widget, sometimes the task takes a while to actually be checked off (and sometimes requires the app to be opened before it will execute). However, I figured this is just a slowness issue or something controlled by the OS that I can't do much about.
With all of that, my question is then, why is the workmanager not executing its midnight task?
I've been smashing my head against this for days, so any help and/or advice is greatly appreciated!!

How I can get current active users from Firebase with Flutter?

I am making an application using flutter and firebase. I need to get active users from firebase.
I am trying by creating a boolean field in firebase collection with name isActive = false and initiate a function with name _checkInternetConnectivity(); in this function I set when the internet connection is available update value of isActive = true in firebase and when there is no internet connection update value isActive = false so far my code is working perfectly but problem is that when internet connection is available it set the value of isActive = true but when there is no internet connection it unable to update the value isActive = false.
code is here:
void initState() {
super.initState();
_checkInternetConnectivity();
}
_checkInternetConnectivity() async {
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile) {
FirebaseFirestore.instance.collection('Profile').doc(user.uid).update({
'isActive': true,
});
print('done mobile');
} else if (connectivityResult == ConnectivityResult.wifi) {
_addData.doc(user.uid).update({
'isActive': true,
});
print('done wifi');
} else if (connectivityResult == ConnectivityResult.none) {
FirebaseFirestore.instance.collection('Profile').doc(user.uid).update({
'isActive': false,
});
print('done none');
}
}
}
so, how I can improve this code or any other way by which I can get active users from firebase with flutter. Regards
OnDisconnect
The OnDisconnect class is used to manage operations that will be run on the server when this client disconnects. It can be used to add or remove data based on a client's connection status. It is very useful in applications looking for 'presence' functionality.
Instances of this class are obtained by calling onDisconnect on a Firebase Database ref.
Your solution is here
You can create a field like lastActiveTimestamp and update it every time an user opens the app. Then you can filter users by the last time they use the app.

Upload to Firestore in background with Flutter

I am using christocracy's flutter_background_geolocation package to build a crowdsensing app. This app relies on the geofencing function of the aforementioned package quite heavily. In the main function, I have implemented a callback function that is as follows (partial code):
void _onGeofence(bg.GeofenceEvent event) async {
await showGeofenceNotification(flutterLocalNotificationsPlugin,
title: "Geofence", body: "$event", id: notification_id);
if (action == "ENTER") {
// update certain variables
BarometerService.startBarometerService();
BarometerService.floorChange.listen((floorChanges) {
// update floor
updateDatabase();
});
}
else if (action == "EXIT") {
// update certain variables
BarometerService.stopBarometerService();
}
updateDatabase();
setState(() {
// update UI
});
}
The code works perfectly when the app is open and in focus. However, when in background, the barometer service stops. The updateDatabase() function is also not carried out as my Firestore console doesn't get updated.
Here is the code for updating the database:
Future updateUserState(String matric, bool inLWN, bool inVaughan, String activity, int confidence, int floor) async {
return await userCollection.document(uid).setData({
'matric': matric,
'inLWN': inLWN,
'inVaughan': inVaughan,
'activity': activity,
'confidence': confidence,
'floor': floor,
});
}
And here is the code for BarometerService (which uses Flutter sensors plugin):
import 'package:sensors/sensors.dart';
static startBarometerService() {
Stream<BarometerEvent> barometer10Events = barometerEvents.throttle(Duration(seconds:PERIOD_SECONDS));
subscription = barometer10Events.listen(onBarometer);
streamController = new StreamController();
}
How do I make my services run even when app is closed or terminated? I have implemented the same code in my headless callback functions (except updating UI), but nothing besides updating my (local) variables and showing local flutter notifications is working.
Headless task for reference:
void headlessTask(bg.HeadlessEvent headlessEvent) async {
print('[BackgroundGeolocation HeadlessTask]: $headlessEvent');
switch(headlessEvent.name) {
case bg.Event.GEOFENCE:
bg.GeofenceEvent geofenceEvent = headlessEvent.event;
onHeadlessGeofence(geofenceEvent);
print('- [Headless] GeofenceEvent: $geofenceEvent');
break;
case bg.Event.ACTIVITYCHANGE:
bg.ActivityChangeEvent event = headlessEvent.event;
onHeadlessActivityChange(event);
print('- [Headless] ActivityChangeEvent: $event');
break;
}
}
onHeadlessGeofence is almost identical to the callback _onGeofence, besides the setState().
The full code can be found here

How to use the rewarded video listener in flutter?

I can't figure out how to use the listener to reward the user for watching the video.
package page : https://pub.dartlang.org/packages/firebase_admob
RewardedVideoAd.instance.listener =
(RewardedVideoAdEvent event, [String rewardType, int rewardAmount]) {
if (event == RewardedVideoAdEvent.rewarded) {
setState(() {
// Here, apps should update state to reflect the reward.
_goldCoins += rewardAmount;
});
}
};
All I managed to do is display the ad, I have no clue how to use the listener.
This is an example:
https://github.com/Maherr/listener/blob/master/lib/main.dart
How to change rewarded to true ?
First, you are using the outdated code. This one is the latest one. Notice that it has optional named parameters {} instead of optional positional parameters [].
RewardedVideoAd.instance.listener =
(RewardedVideoAdEvent event, {String rewardType, int rewardAmount}) {
if (event == RewardedVideoAdEvent.rewarded) {
setState(() {
rewarded = true;
});
}
};
This is how listener works. You don't have to assign this listener to anywhere. All you need to do is call
RewardedVideoAd.instance.load(...)

Categories

Resources