iOS requestTrackingAuthorization callback is not on the main thread - ios

The iOS ATT Alert needs to be shown on startup before an app starts, so the remaining app code needs to be run in the completion handler (eg initialising advert code and starting the main game).
However, this crashes because the completion handler callback is not on the main thread (after the alert is actually shown and Allow or Ask App Not to Track is selected).
In iOS (Xamarin C# code):
public override void OnActivated(UIApplication application) // RequestTrackingAuthorization must be called when app is Active
{
if (!this.shownAlert)
{
this.shownAlert=true;
Debug.WriteLine("1) ThreadId={0}", Environment.CurrentManagedThreadId); // ThreadId=1
ATTrackingManager.RequestTrackingAuthorization(delegate (ATTrackingManagerAuthorizationStatus trackingManagerAuthorizationStatus)
{
Debug.WriteLine("2) ThreadId={0}", Environment.CurrentManagedThreadId); // ThreadId=7
});
}
}
Prints:
1) ThreadId=1
2) ThreadId=7
And the similarly in Unity:
Debug.WriteLine("1) ThreadId={0}", Environment.CurrentManagedThreadId); // ThreadId=1
ATTrackingStatusBinding.RequestAuthorizationTracking(delegate (int status)
{
Debug.WriteLine("2) ThreadId={0}", Environment.CurrentManagedThreadId); // ThreadId=4
});
This seems like a bug to me. So how do all the apps and games out there get this to work? Somehow switch to the main thread in the callback? I don’t see any examples of this.

Related

How to configure Workmanager Flutter iOS correctly?

I create a background fecth with workmanager flutter and following as the instruction on their documentation [here]https://github.com/fluttercommunity/flutter_workmanager/blob/main/IOS_SETUP.md. On android is working properly, but I am not sure with ios.
I add this on my main.dart
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) {
switch (task) {
case Workmanager.iOSBackgroundTask:
stderr.writeln("The iOS background fetch was triggered");
break;
}
bool success = true;
return Future.value(success);
});
}
and call it on here
void main() async {
.....
//workmanager
if (Platform.isIOS) {
Workmanager().initialize(
callbackDispatcher, // The top level function, aka callbackDispatcher
isInDebugMode:
true // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
);
}
}
and I tested via simulator and simulate background fecth. And its print
The iOS background fetch was triggered
SwiftWorkmanagerPlugin - ThumbnailGenerator createThumbnail(with:) something went wrong creating a thumbnail for local debug notification
but the notification didn't appear as on the documentation did. Is my workmanager working?
Logically, I think is work, but im not sure. Also, it will running on background (even the apps is close/kill/terminated) every 15 minutes in iOS?

WKInterfaceMap blank rendering

WKInterfaceMap sometimes renders blank in a very simple app I am developing. The map appears completely empty (no grid of lines or anything similar). It looks as though the thread responsible for drawing the tiles gets blocked.
In order to reproduce the issue just add the code below to the extension delegate.
func applicationDidEnterBackground() {
let watchExtension = WKExtension.shared()
// Schedule the background refresh task.
watchExtension.scheduleBackgroundRefresh(withPreferredDate: Date().addingTimeInterval(15.0*60.0), userInfo: nil) { (error) in
// Check for errors.
if let error = error {
print("ExtensionDelegate: \(error.localizedDescription)")
return
}
print("ExtensionDelegate: Background Task scheduled successfuly")
}
}
Add a WKInterfaceMap to the main view and run once on the simulator. Close the app using the crown and stop it from XCode. Wait for at least 15 minutes and open the app again directly from the simulator.
The map renders then as in the image below.

UI (sometimes) freezes during an operation on iOS

So, on my app I have a feed that I want to update every time the user returns to the app.
I'm calling a routine to do this on applicationWillEnterForeground of my AppDelegate.
Everything is working fine but, sometimes, my UI freezes during this operation.
I was able to find where this occurs using a label to show the progress of this routine. The label is updated on three major points:
Before starting the routine
Inside the routine
When the routine ends
Sometimes, this workflow works fine, and I'm able to see the progress through this label.
But sometimes, the label only shows the first message, and messages that happen inside the routine does not appear. Besides that, I can't do anything on my app, because the UI is frozen. Once the routine is over, everything comes back to normal.
Here's a simplified version of the flow my app executes to call this routine:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
let mainViewController = MainViewController()
func applicationWillEnterForeground(_ application: UIApplication) {
mainViewController.refreshLatestVideos()
}
}
class MainViewController: UITabBarController {
private var subscriptionsController: SubscriptionsController! // initialized on viewDidLoad
func refreshLatestVideos() {
subscriptionsController.refreshLatestVideos(sender: nil)
}
}
class SubscriptionsController: UITableViewController {
private var subscriptionsModelController: SubscriptionsModelController! // received on constructor
#objc func refreshLatestVideos(sender:UIButton!) {
showMessage(message: "Updating subscriptions...") // this message is always shown to me
subscriptionsModelController.loadLatestVideos()
}
}
class SubscriptionsModelController {
func loadLatestVideos() {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
DispatchQueue.global(qos: .userInitiated).async {
// bunch of requests with Just
...
// update message
showMessage(message: "Updating subscription x of y") // this message sometimes doesn't appear, because the UI is frozen
// another requests
...
// update message
showMessage(message: "Updates completed")
}
}
}
As you can see, I'm executing the update inside the global queue, so I'm not blocking the main thread.
And again, this freezing of the UI only happens sometimes.
Is there any point which I can look at to find what's going on? Is it possible that the main thread is being blocked by something else?
Dispatch updating the UI to the main thread:
DispatchQueue.main.async { showMessage(message: "Updates completed") }
Everytime you anyhow access/modify the UI, do it on the main thread to avoid having unexpected problems (link to one of several resources on this topic, I suggest you google up and read more).
That applies to the rest of the code as well, if there is something that is related to UI, do the same for it - e.g., if after finishing the task you call tableView.reloadData, do it on the main thread too.

My iOS app freezes but no error appears

Does any body know what I need to check if app freezes after some time? I mean, I can see the app in the iPhone screen but no view responds.
I did some google and i found that, i've blocked the main thread somehow.
But my question is how to identify which method causes blocking of main thread? is there any way to identify?
Launch your app and wait for it to freeze. Then press the "pause" button in Xcode. The left pane should show you what method is currently running.
Generally, it is highly recommended to perform on the main thread all animations method and interface manipulation, and to put in background tasks like download data from your server, etc...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//here everything you want to perform in background
dispatch_async(dispatch_get_main_queue(), ^{
//call back to main queue to update user interface
});
});
Source : http://www.raywenderlich.com/31166/25-ios-app-performance-tips-tricks
Set a break point from where the freeze occurs and find which line cause that.
Chances may be,Loading of large data,disable the controls,overload in main thread,Just find out where that occurs using breakpoints and rectify based on that.
I believe it should be possible to periodically check to see if the main thread is blocked or frozen. You could create an object to do this like so:
final class FreezeObserver {
private let frequencySeconds: Double = 10
private let acceptableFreezeLength: Double = 0.5
func start() {
DispatchQueue.global(qos: .background).async {
let timer = Timer(timeInterval: self.frequencySeconds, repeats: true) { _ in
var isFrozen = true
DispatchQueue.main.async {
isFrozen = false
}
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + self.acceptableFreezeLength) {
guard isFrozen else { return }
print("your app is frozen, so crash or whatever")
}
}
let runLoop = RunLoop.current
runLoop.add(timer, forMode: .default)
runLoop.run()
}
}
}
Update October 2021:
Sentry now offers freeze observation, if you don't wanna roll this yourself.
I reached an error similar to this, but it was for different reasons. I had a button that performed a segue to another ViewController that contained a TableView, but it looked like the application froze whenever the segue was performed.
My issue was that I was infinitely calling reloadData() due to a couple of didSet observers in one of my variables. Once I relocated this call elsewhere, the issue was fixed.
Most Of the Time this happened to me when a design change is being called for INFINITE time. Which function can do that? well it is this one:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
Solution is to add condition where the function inside of viewDidLayoutSubviews get calls only 1 time.
It could be that another view is not properly dismissed and it's blocking user interaction! Check the UI Debugger, and look at the top layer, to see if there is any strange thing there.

Trouble bringing a Blackberry App to Foreground

I have an app that is listening in background and when the user clicks "send" it displays a dialogue. However I need to bring my app to foreground so the user answers some questions before letting the message go. but I haven't been able to do this, this is the code in my SendListener:
SendListener sl = new SendListener(){
public boolean sendMessage(Message msg){
Dialog myDialog = new Dialog(Dialog.D_OK,
"message from within SendListener",
Dialog.OK,Bitmap.getPredefinedBitmap(Bitmap.EXCLAMATION),
Dialog.GLOBAL_STATUS)
{
//Override inHolster to prevent the Dialog from being dismissed
//when a user holsters their BlackBerry. This can
//cause a deadlock situation as the Messages
//application tries to save a draft of the message
//while the SendListener is waiting for the user to
//dismiss the Dialog.
public void inHolster()
{
}
};
//Obtain the application triggering the SendListener.
Application currentApp = Application.getApplication();
//Detect if the application is a UiApplication (has a GUI).
if( currentApp instanceof UiApplication )
{
//The sendMessage method is being triggered from
//within a UiApplication.
//Display the dialog using is show method.
myDialog.show();
App.requestForeground();
}
else
{
//The sendMessage method is being triggered from
// within an application (background application).
Ui.getUiEngine().pushGlobalScreen( myDialog, 1,
UiEngine.GLOBAL_MODAL );
}
return true;
}
};
store.addSendListener(sl);
App is an object I created above:
Application App = Application.getApplication();
I have also tried to invoke the App to foreground using its processID but so far no luck.
i have managed to achieve something similar to what you're describing but the difference is, my dialogs are displayed asynchronously, which might actually be easier... so in your case..
the first i could suggest you try is get the event lock before pushing the screen, ala:
synchronized(Application.getEventLock()){
final UiEngine ui = Ui.getUiEngine();
ui.pushGlobalScreen(theScreen, 1, UiEngine.GLOBAL_MODAL);
}
I would also just create a custom class of type MainScreen and push that instead of plain Dialog.
There, that's better (now with code formatting).
public class MYSendListener implements SendListener {
private UiApplication _myApp;
public MySendListener(UiApplication myApp) {
_myApp = myApp;
}
public boolean sendMessage(Message m) {
...
_myApp.requestForeground();
}
}
Cache your app instance inside your send listener when you construct it, and use that when sendMessage is fired.
Application.getApplication() only gets you the app of the calling thread.

Resources