In one of my Android apps, I'm trying to implement a simple grab of the inventory from Google's In-App billing, but it keeps giving me errors at the line of
mHelper.queryInventoryAsync(mGotInventoryListener);
with the message that
IabHelper is not setup. Can't perform operation: queryInventory
Here is all the IabHelper code.
mHelper = new IabHelper(this, base64EncodedPublicKey);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
// Oh noes, there was a problem.
Log.d(TAG, "Problem setting up In-app Billing: " + result);
}
// Hooray, IAB is fully set up!
}
});
//check to see if ads have been removed(bought)
IabHelper.QueryInventoryFinishedListener mGotInventoryListener
= new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result,
Inventory inventory) {
if (result.isFailure()) {
// handle error here
}
else {
// does the user have the premium upgrade?
if(inventory.hasPurchase(REMOVE_ADS)) removeAdsPurchased = true;
// update UI accordingly
}
}
};
mHelper.queryInventoryAsync(mGotInventoryListener);
The short answer is that your queryInventoryAsync() call should be made from inside your onIabSetupFinished() method. This is an asynchronous call, and so you cannot just proceed with using the IabHelper instance until that callback has been invoked to tell you that the helper's communication with the billing service has been established. The way your code is presently written, you have a race condition, and your queryInventoryAsync() call is going to win that race and attempt to use the IabHelper object before it has been set up, which is the cause of your problem.
Also, any further code in UI handlers that relies upon this object (e.g., the handler for a button that initiates a purchase) should test for a fully set-up IabHelper object, and should not allow the user to use that UI element until the IabHelper instance created in onCreate() has successfully completed setup. The easiest way to handle this situation is to simply disable such UI elements until the setup callback has been invoked to indicate that setup has completed successfully.
That is the easy part. The more serious problems occur when you have actions that occur immediately after your onCreate() method runs (i.e., not under the control of the user), that require the use of a fully set-up IabHelper instance. This typically occurs as a result of activity lifecycle calls - specifically, onResume() (if there is something requiring an IabHelper instance that must be done each time your app comes to the foreground, and not just when onCreate() is called) and, most notably, in onActivityResult() (which is invoked when the user completes or aborts an interaction with the billing interface - e.g., as part of making an in-app payment).
The problem is that your app may be stopped by the OS (e.g., to make room for the billing interface itself when the user initiates a purchase), causing your IabHelper instance to be destroyed along with your app, and that instance will have to be regenerated when your onCreate() is next invoked, and setup will once again be initiated in onCreate(), and once again you will need to wait for setup to complete before doing anything else with that object.
One notable situation where this can occur is during the user's interaction with the billing interface as part of the purchase process. The result of that interaction will be communicated to your app via onActivityResult(), which itself needs to use a fully set-up IabHelper object, and so if your app gets flushed from memory while the user is interacting with the billing service to make (or cancel) a purchase, then onActivityResult() will have to wait for the IabHelper instance to get set up again (after it is re-created in onCreate() before it can use it.
One way to handle this would be to set up and post to a queue of pending actions requiring an IabHelper instance, that your onResume() and/or onActivityResult() code adds to, and have that queue processed by your onIabSetupFinished() method once the IabHelper setup (initiated by onCreate()) has completed.
It's not trivial. And last time I checked, the TrivialDrive sample app did not handle the above situation.
The best way to test this kind of use case is to use the developer option "Don't Keep Activities," which causes your app to be destroyed each time the user leaves it, to simulate what the OS will do when it needs to reclaim memory, so that you can make sure your app works under those conditions.
Great stuff from Carl. I "think" I am seeing a similar thing, though my app is crashing via:
java.lang.IllegalStateException: IabHelper was disposed of, so it cannot be used.
And it my case, rotating the device one way, then immediately back to the original orientation SOMETIMES causes this crash. Seems that there's a "window" of time where this crash "might" happen (due to the asynchronous nature of IAB like Carl explained).
My fix
My "fix" was to make mHelper static, and only instantiate it if (mHelper == null), and NOT destroy it in the activity's onDestroy() method. This way, once it's setup, it sticks around, and there's no need to worry about asynchronous operations (causes by device orientation) anymore.
Not sure if this is the right fix or not, but thought I'd mention it in case it help others.
Simply make the checkNotDisposed() method synchronised and the problem goes away. This is because it is sometimes called in a separate thread and does not always have the latest value of mDisposed, which may have been set in the main thread:
private synchronized void checkNotDisposed()
Related
I'm building my first CloudKit application, and am using CKFetchRecordZoneChangesOperation on startup to get any new records changed while the current device was offline.
I am also calling CKFetchRecordZoneChangesOperation when I receive a subscription notification of changes.
It is possible the subscription notification could come in before the startup call finishes. I am currently using a lock to prevent the 2nd call from starting until the recordZoneFetchCompletionBlock handler is called, signalling that the first one is done. This works, but it also smells a bit hacky.
Let's say we have a Flutter app where we want to save some persistent data using shared preferences.
(We don't want to save the data persistently every time the user changes it because the UI depends directly on the data, and in order to save it we need to use await async, and that lags the UI), so we use WidgetsBindingObserver to detect when the app goes to the background in order to use that event as an efficient catch-all opportunity to save the data.
So, we have some code like this in the root page of our app:
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.paused) {
// went to Background
myPersistentData.instance.write(); // <-------------- this is an async operation
}
if (state == AppLifecycleState.resumed) {
// came back to Foreground
}
}
This seems to work, but I'm concerned that the lifecycle could progress beyond "paused" (pause>stop>finish) so quickly that this "trailing" operation doesn't have time to finish... and thus the data would not be saved.
Is this a legitimate concern?
I would say as much as possible try to avoid calling async operations in the dispose or lifecycle methods of widgets, while you may be concerned that it may/may not complete i would recommend like the dart docs states here: https://api.dart.dev/stable/2.14.1/dart-async/unawaited.html, "Not all futures need to be awaited.", "You should use unawaited to convey the intention of deliberately not waiting for the future.". But generally, i'd avoid calling async functions in these methods.
It should be a concern for sure because the time available for an app to run in background is limited and not defined.
Based on the memory resources needed for the most important app, which is the one in foreground, the system may kill your app faster or at a later point in time. In either scenarios you don't have much time to complete the tasks, this event should be used to do fast clean up like killing tasks that are started, clearing token or maybe saving a token or a session info.
There are also scenarios which will cause problems for this approach, like the pone runs out of battery or the OS crashes, in this case you don't have any notification of app going into bg. Or the user force quits your app.
In your case, I would say you have another concern, saving data in shared preferences shouldn't affect your UI only if:
you save it synchronously which is a no no
you save a huge chunk of data in shared prefs, in this case you should use a database.
and a less likely scenario, you have a race condition while you save in shared prefs which are not thread safe.
I would suggest to rethink a bit your approach on this matter, maybe you can spawn async operations that save the data in certain points of the app (user exits a screen or whatever) while also keeping a copy in memory. Maybe is better to save it using a database. In the end is your decision, but my advice would be, don't rely on the app running in bg time.
For something like this, there is no way you can continue a service purely in Flutter. That's bummer of course but there are work-arounds you can use to do that.
If you want to approach this problem in a more convenient but difficult way, you can always create a service in native (Kotlin/Java in case of Android or Swift/Objective-C for iOS) and call your native functions using MethodChannels. Your native service can be alive even if you kill your app. Think of Push-notifications service where even if you kill your app, Push service is always alive to serve your notification.
I have a Mac OS X app and I want to detect or catch the event when the user Force Close my app.
At first I thought applicationWillTerminate will do the work but it didn't:
func applicationWillTerminate(_ aNotification: Notification) {
print("applicationWillTerminate called")
}
Do you have any idea how can I do that?
Major credit goes to #caseynolan for putting work in here to come up with an answer. Unfortunately, there are, in fact, some major issues with the suggested approach.
First, signal handlers are per-process. There are other, legitimate uses of signals, and installing handlers will negatively impact their behavior without very careful work.
Second, signals interrupt a thread, and that thread can be doing anything, including holding locks. Examples of things that hold locks - malloc/free, the Objective-C runtime.
This is an part of a class of problems known as "async safety". If you check out "man sigaction" (note, sigaction is a much improved API over signal) you see that there are actually a very small number of functions that are safe to call from a signal handler. Calling unsafe functions, like NSLog, will sometimes work. But, will also sometimes deadlock, depending on what the thread was doing at the time.
Now, I will admit that my experience with signals does not include use of SIGTERM. However, since it can be delivered at any time, it still will be subject to async safety issues, even thought it is not a crash.
In short: it is almost certainly unsafe to run your code in a signal handler, and it will deadlock sometimes. And, those deadlocks will occur during unpredictable points of its execution.
Third, there are fatal events that aren't mapped to signals, on top of SIGKILL. This may not matter to do, depending on how strong a guarantee you need about detecting your process termination.
What I'd do Instead:
I think you're only safe option is to use a sentinel process. The idea is you start up a child process, and in that child, observe the parent. If/when that process disappears, you can then execute your code.
This is a safe, deadlock-risk free way to monitor for arbitrary process exits. However, it could be challenging to move your operations out-of-process. Unfortunately, I believe that's a necessary aspect of this, if you want it to be reliable.
Good luck!
Disclaimer: I'm not an expert, and researching for this post has stretched the limits of my rusty C/Objective-C knowledge. I don't know how the following code is viewed by Apple for submissions to the App Store, so YMMV.
The following has been shamelessly ripped from Wikipedia and reworked:
#import <Foundation/Foundation.h>
/**
This will handle signals for us, specifically SIGTERM.
*/
void handleSignal(int sig) {
if (sig == SIGTERM) {
// Caught a SIGTERM
}
/*
SIGTERM is a clear directive to quit, so we exit
and return the signal number for us to inspect if we desire.
We can actually omit the exit(), and everything
will still build normally.
If you Force Quit the application, it will still eventually
exit, suggesting a follow-up SIGKILL is sent.
*/
exit(sig);
}
/**
This will let us set a handler for a specific signal (SIGTERM in this case)
*/
void setHandler() {
if (signal(SIGTERM, handleSignal) == SIG_ERR) {
NSLog(#"Failed to set a signal handler.");
} else {
NSLog(#"Successfully set a signal handler.");
}
}
Call only asynchronous-safe functions within signal handlers. See here.
You could place the above in a C/Objective-C file and use it in Swift via a Bridging Header. Call setHandler() somewhere in the beginning of your application's lifecycle, e.g. in applicationDidFinishLaunching, and you should now have a chance to do some work before your app is Force Quit. I don't know how much time you get here, so I'd keep the workload as light as possible (avoid starting mission-critical stuff here, I guess?).
Here's some background info:
In a typical Quit
The Quit procedure is actually a part of Apple Events.
If the application is NSDocument-based, the behavior depends on the saving parameter, which has one of these three values:
NSSaveOptionsNo: The application quits without sending a close message to any document.
NSSaveOptionsYes: Each unmodified document is sent a close message; each modified document is sent the following message:
saveDocumentWithDelegate:didSaveSelector:contextInfo:
NSSaveOptionsAsk: (This is the default value if no saving parameter is supplied in the event.) If there are modified documents
open, the NSDocumentController sends itself this message:
reviewUnsavedDocumentsWithAlertTitle:cancellable:delegate:didReviewAllSelector:contextInfo:
If the application is not NSDocument-based, the application delegate is sent this message (if it is implemented):
applicationShouldTerminate:
You can modify the default behavior by implementing this method.
Source: How Cocoa Applications Handle Apple Events
During Force Quit
The application is sent a SIGTERM which can be caught and dealt with by your application. Ideally, the application would clean up and exit as gracefully as possible, but this isn't required, and the signal can even be ignored.
An impatient user (and I strongly suspect that Force Quit does this eventually) may send a SIGKILL, which cannot be ignored and totally goes over anything you as the developer can stop.
Extra Information and Resources:
What does Force Quit do in OS X?
What signals does OS X send for the Quit and Force Quit commands?
How Cocoa Applications Handle Apple Events
SIGTERM vs. SIGKILL - major.io
C Signal Handling - Wikipedia
The application I am working on downloads and parses a large xml file to populate UI elements, namely search and a spinner. This is done through an async task. If the user decides to switch out of the application during this, the information is downloaded correctly, but then when the application is resumed, the UI will not be updated.
Is this because the changes can't be made while the application is not active? What is the best way to go about checking whether the UI was not updated on resume? Or instead should I be doing something with the Async task, checking whether the UI thread is available? I'm having a hard time debugging this because it involves leaving the application which ends the debugger.
You can achieve this scenario through the broadcast receive.
Follow the step:
Solution 1:
Step 1;
Register the broadcast receiver before executing the Asyntask.
Step 2:
send Broadcast in onPostExecute method of Asyntask.
step 3:
And then you can able receive your broadcast message in your activity.
and do whatever you want.
Solution 2:
Otherwise you can use Interface Call back for this Scenario.
Hope It will help you.
It should'nt. App being in background means View objects may or may not be there. Actually the entire process may be stopped or just deleted by android.
Use a Service to truly do processing in background. When processing is complete but UI is not there, post a notification to let user know, OR, save the results and provide it to UI the next time it binds to your service and ask for same thing (a.k.a caching).
The application in background may not be live. The O.S may destroy it in case of memory constrains.
The trick is to try an alternate logic.
When the application moves from background to foreground onresume() is called ,you could try saving the data to db and update the content on the resume call.
FYI.onPause() and OnResume() is called when the application goes background and foreground respectively.
I keep getting this error when I run my app that uses CoreBluetooth on an iPhone 5: <CBConcreteCentralManager: 0x2007d590> is not powered on
But when I call state on my program's one and only CBCentralManager object, it returns 5, which is CBCentralManagerStatePoweredOn. So it's powered on, yet I get this error. The iPhone's Bluetooth is also enabled.
Just in general, when would this ever happen? I don't even know what is going on when the program runs because I'm getting what looks like conflicting messages.
You have to initially wait until the centralManager gets the callback from centralManagerDidUpdateState: when you're app boots up. Then every other time, I recommend checking the state prior to doing any centralManager calls. You're most likely calling scan or retrieve before the central has had a chance to update. Ensure you only call methods after you know it's on. You will not get the error if you wrap each call in if statements that check the state first.
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
if(central.state==CBCentralManagerStatePoweredOn)
{
//Now do your scanning and retrievals
}
}
Otherwise just wrap your central inside a state check before each call:
if(yourCentral.state==CBCentralManagerStatePoweredOn)
{
//you're good to go on calling centralManager methods
}