I can successfully register my app for push notifications:
UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(
UIRemoteNotificationType.Alert
| UIRemoteNotificationType.Badge
| UIRemoteNotificationType.Sound);
But whenever I execute this, my UI hangs for usually 2-3 seconds. Even if I have it early in the app lifecycle as recommended (e.g., even WillFinishLaunching) my UI still hangs once my first ViewController loads.
My first thought was to execute the registration in a separate thread, but MonoTouch prevents this:
// From MonoTouch.UIKit
public virtual void RegisterForRemoteNotificationTypes(UIRemoteNotificationType types)
{
UIApplication.EnsureUIThread();
My next thought was to at least pop up a UIAlertView box to let the user know something's going on, but for some reason it didn't display until after the registration took place, resulting in the UIAlertView opening and immediately closing!
modalPopup = new UIAlertView("Working", "The application is loading...", null, null);
modalPopup.Show();
// It doesn't show here!
RegisterForRemoteNotificationTypes();
// It shows here!
modalPopup.DismissWithClickedButtonIndex(0, true);
How can I either
Stop the push notification registration from tying up my UI thread
cover up the UI freeze?
Did you override public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
or
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
I've had similar problem where within Registered... function, I called a web service to upload the device token. But the web call wasn't within another thread so it caused to freezes UI.
Related
Issue: Different Behavior In 3 Different Contexts
Ok so Ok, in iOS it seems three different things can happen regarding Push Notifications:
When a Push Notification is received when the app is not in the foreground
something shows up in Notification Center
if the app is opened by tapping the notification, either AppDelegate.DidReceiveRemoteNotification(...) or AppDelegate.ReceivedRemoteNotification(...) is called, apparently depending on which one is implemented (??).
if the app is opened without tapping the notification, only AppDelegate.WillEnterForeground(...), is called, without any explicit mention of the notification, and nothing else happens to acknowledge that a notification was received.
When a Push Notification is received when the app is in the foreground it causes the UNUserNotificationCenterDelegate, if there is one, to execute UNUserNotificationCenterDelegate.WillPresentNotification(...).
Approach: Routing To One Method From All Contexts
So to cover all bases with Push I need to implement something in all three methods: AppDelegate.DidReceiveRemoteNotification(...) / AppDelegate.ReceivedRemoteNotification(...), AppDelegate.WillEnterForeground(...), and UNUserNotificationCenterDelegate .WillPresentNotification(...).
Here are some stubs to show my approach to all this.
First, I created a custom UNUserNotificationCenterDelegate, with a Shared static member:
public class IncomingNotificationHandler : UNUserNotificationCenterDelegate
{
public static IncomingNotificationHandler Shared = new IncomingNotificationHandler();
...
}
Second, inside that class I made a handler that I can route to in every case (again, this is just a stub for debugging purposes):
//sets all parameters to null by default, so it can be called from methods
//that don't know anything about notifications:
public void HandleNotificationsIfAny(UIApplication application = null,
NSDictionary userInfo = null,
Action<UIBackgroundFetchResult> completionHandler = null)
{
//checks if userInfo is null, and logs its conclusions about that:
if (userInfo == null)
{
//In the null case, we can get pending notifications from
//UNUserNotificationCenter:
UNNotification[] pendingNotifications = new UNNotification[] { };
UNUserNotificationCenter.Current.GetDeliveredNotifications(returnedValue => pendingNotifications = returnedValue);
//Then we log the number of pending notifications:
Debug.WriteLine("IncomingNotificationHandler: HandleNotificationsIfAny(...): delivered notification count: " + pendingNotifications.Length);
//And make note of where this was probably called from:
Debug.WriteLine("IncomingNotificationHandler: HandleNotificationsIfAny(...): may have been called from this.WillPresentNotification(...) OR AppDelegate.WillEnterForeground(...)");
return;
});
}
else
{
//In the non-null case, we log the userInfo
Debug.WriteLine("IncomingNotificationHandler: HandleNotificationsIfAny(...): just got info: " + userInfo);
//And make note of where this was probably called from:
Debug.WriteLine("IncomingNotificationHandler: HandleNotificationsIfAny(...): may have been called from AppDelegate.DidReceiveRemoteNotification(...)");
}
}
Third, inside the same class, I implemented the single method that's required by UNUserNotificationCenterDelegate, and I routed to the handler from it:
public override void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
{
HandleNotificationsIfAny();
}
Fourth, and last, inside AppDelegate, I routed to the same handler from both relevant methods:
//I prefer using DidReceiveRemoteNotification because in my experience
//the other one is sometimes not reliable:
public override void DidReceiveRemoteNotification(UIApplication application,
NSDictionary userInfo,
Action<UIBackgroundFetchResult> completionHandler)
{
//Simply passing on all the parameters called in this method:
IncomingNotificationHandler.Shared.HandleNotificationsIfAny(application, userInfo, completionHandler);
}
//WillEnterForeground also calls the handler without any parameters
//because it doesn't automatically know anything about notifications:
public override void WillEnterForeground(UIApplication application)
{
IncomingNotificationHandler.Shared.HandleNotificationsIfAny();
}
With that, as it stands, I think I'm handling a notification event in the same way no matter how my app is alerted about it, and even when it's not alerted at all.
Does anyone know if I now have it covered, or if there's some other cases I need to handle?
For the first scenario: AppDelegate.ReceivedRemoteNotification
It reflects the objective c method: application:didReceiveRemoteNotification:, but this event has been deprecated since iOS 10: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623117-application?language=objc. So I think there's no need to handle this event.
For the second scenario: AppDelegate.DidReceiveRemoteNotification
You can still utilize it to handle notifications now if you haven't implemented UNUserNotificationCenter and please notice it is only valid after iOS 7+. Moreover, this event will be triggered when app is on the foreground and if your app is on the background, this event only fires when the user clicks the notification to open your application. And there's no way to access the notification's information if the user clicks the icon to open the app.
I don't think handling AppDelegate.WillEnterForeground is a good approach, as it will be called each time the app resumes from background to foreground even though there are no notifications.
For the scenario: UNUserNotificationCenterDelegate
You could only use this feature after iOS 10. Once you have implemented it on the device iOS 10+, DidReceiveRemoteNotification and ReceivedRemoteNotification will never be triggered. WillPresentNotification will be called when app is on the foreground. DidReceiveNotificationResponse will be fired when the app is on the background and user clicks notifications to open it.
As a conclusion, if you want to easily handle the notification AppDelegate.DidReceiveRemoteNotification is enough. If you want to consume the new features of UNUserNotificationCenter, AppDelegate.DidReceiveRemoteNotification and UNUserNotificationCenter should be both involved. The prior one for the iOS 7+ devices and the later one for iOS 10+ devices.
Update:
For iOS 10+, you could use UNUserNotificationCenter.Current.GetDeliveredNotifications to obtain the notifications that are still displayed in Notification Center. And if you only want to support iOS version 10 and later. I think UNUserNotificationCenter is enough, there's no need to implement AppDelegate.DidReceiveRemoteNotification(...) or AppDelegate.ReceivedRemoteNotification(...).
If the app is on background / killed state and the user clicks notification to
open the app, DidReceiveNotificationResponse will be called.
If the
user clicks icon to open your app and the app is killed you should
place your logic code in FinishedLaunching.
If the user clicks icon
to open your app and app is on background, you can handle
WillEnterForeground as you did before.
If the app is on foreground,
handle WillPresentNotification.
I have an adobe air application - AS3 for iOs and Android.
Whenever the user clicks the home button, and thus the application is now in the background, the application automatically stops, which is the expected behavior. Now, if the user is in the application, and he double clicks his home button, showing all the multiple windows, the application continues running, which is not what i want. How can i access that state ( Not Background, not foreground )? If i can access it, i would then put my pausing code into that state, but how can i access that particular state?
When the user clicks the home button the app is moved to the background and suspended. The app isn't closed. The OS can close the app to free memory. If your app is a memory hog you'll see this happening.
You use events dispatched by the NativeApplication object. Below is example code to listen and handle these events.
import flash.events.Event;
import flash.desktop.NativeApplication;
import flash.desktop.SystemIdleMode;
// create listeners to NativeApplication
private var naApplication: NativeApplication;
naApplication = NativeApplication.nativeApplication;
naApplication.addEventListener(Event.ACTIVATE, eActivate);
naApplication.addEventListener(Event.DEACTIVATE, eDeactivate);
naApplication.addEventListener(Event.EXITING, eExiting);
private function eActivate(e: Event): void {
// app has opened or resumed
application.systemIdleMode = SystemIdleMode.KEEP_AWAKE;
}
private function eDeactivate(e: Event): void {
// app is going to be moved to background
application.systemIdleMode = SystemIdleMode.NORMAL;
}
private function eExiting(e: Event): void {
// app is going to be closed by user or by the OS (usually to free up memory)
// do whatever exit code here then remove all listeners (to be clean don't rely on OS to close them)
application.removeEventListener(Event.ACTIVATE, eActivate);
application.removeEventListener(Event.DEACTIVATE, eDeactivate);
application.removeEventListener(Event.EXITING, eExiting);
application.systemIdleMode = SystemIdleMode.NORMAL;
removeEventListener(Event.ENTER_FRAME, eMainTimer);
}
The systemIdleMode and ENTER_FRAME are just examples of typical code. Let me know of any questions.
We're trying to make a multipeer connection between two devices using MPC framework in libgdx game.
What generally we've done successfully:
Devices are connecting, session is establishing correctly.
After session is established nearBrowser and nearAdvertiser stop
doing their stuff.
Then we do transition to the game scene. In the new scene one device
can send a message to another.
DidReceiveData method from Session Delegate is called and there
we've got right messages for both devices.
After this we send to libgdx message for updating content (in main
gdx thread).
BUT after a while when some device received data it immediately crashes. Sometimes it happens on 10th receiving, sometimes after 200th. Crash appears only on the device that received message. It doesn't matter how long they are connected. Crash appears after all methods have done their work with data. So we don't know where exactly error happens.
// MCSession delegate method
public void didReceiveData(MCSession session, NSData data, MCPeerID peerID) {
//there we make userInfoData
//
DispatchQueue.getMainQueue().async(new Runnable() {
#Override
public void run() {
NSNotificationCenter.getDefaultCenter().postNotification(new NSString("didReceiveData"), null, userInfoData);
}
});
}
// Register observer in NSNotificationCenter
// NSNotificationCenter.getDefaultCenter().addObserver(this, Selector.register("updateDataWithNotification:"), new NSString("didReceiveData"), null);
// This method is called when device has received new data
#Method
private void updateDataWithNotification(NSNotification notification){
userInfoDict = notification.getUserInfo();
data = (NSData) userInfoDict.get(new NSString("data"));
strBytes = new String(data.getBytes());
// i'm not sure this Gdx.app.postRunnable is really needed
Gdx.app.postRunnable(new Runnable() {
#Override
public void run() {
SBGlobalMessanger.getInstance().readBluetoothMessage(BluetoothData.RC_MESSAGE, strBytes);
}
});
}
The questions are:
Where is the bug? And how can we fix it?
The problem was in robovm plugin. In debug mode it made build that crushed. After making release build bug disappeared. The thing i have learned after working with robovm + libgdx is if you have strange bug just make a release build. It seems that this kind of bugs was eliminated with the last release of robovm 1.3 (i haven't try it out yet).
I am developing an app which blocks incoming calls. Currently, when an incoming call arrives on the device, it is blocked. But after returning from the blocked call, the screen turns to the dial call screen, and shows a dialog to alert that you have a missed call.
I want to block the incoming call, then when hung up, the screen is the home screen. How do I make this happen?
My second question: what is the permission in blocking incoming call? How do I add it to my app? I added "ApplicationPermissions.PERMISSION_IDLE_TIMER" but it's not useful.
Edit1:
this is my code in my application.
private void blockincomingcall(){
int master_volume= net.rim.device.api.system.Alert.getVolume(); //net.rim.device.api.notification.NotificationsManag er.getMasterNotificationVolume();
System.out.println("Master Volume "+master_volume);
net.rim.device.api.system.Alert.setVolume(0);
int alert_volume = Alert.getVolume();
Main.log("Master Volume after setting "+alert_volume);
int notifi_volume = NotificationsManager.getMasterNotificationVolume();
Main.log("Master Volume 1 after setting "+notifi_volume);
EventInjector.KeyCodeEvent ev1 = new EventInjector.KeyCodeEvent(EventInjector.KeyCodeEvent.KEY_DOWN, ((char) Keypad.KEY_END), KeypadListener.STATUS_ALT, 100);
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
EventInjector.invokeEvent(ev1);
EventInjector.invokeEvent(ev1);
net.rim.device.api.system.Alert.setVolume(master_volume);
//System.out.println("Master volume 2 "+master_volume);
requestBackground();
}
when, it runs on os5.0 it can block calls. but the screen will turn to the dial screen,and show a notify dialog that a new incoming call. and the volume set is no effect. it runs ok on os 7.0 and 6.0 but no effect on volume set. what should i do ,thank you
Thats a good piece of malware, but anyway:
Detect incoming call
Terminate it.
Put your app on foreground again.
For #1 You need to detect active calls (use PhoneListener class). #2 is the most difficult step and you are going to need key injection to accomplish it. It is a bit hackish:
EventInjector.KeyCodeEvent ev = new EventInjector.KeyCodeEvent(EventInjector.KeyCodeEvent.KEY_DOWN, ((char)Keypad.KEY_END), KeypadListener.STATUS_ALT);
EventInjector.invokeEvent(ev);
The #3 point can be done in two different ways:
3.1: Pass a reference to your app to the PhoneListener implementation and then call <YourUiApplication>.requestForeground()
3.2: Given that code in PhoneListener runs inside the phone app (this should answer your second question), call:
UiApplication.getUiApplication().requestBackground();
I am trying to establish a PIM listener that will update a MainScreen where all the contacts of the phone are listed.
What I am doing is the following:
I am loading for one time only a form called ContactsForm and I am storing it into the RuntimeStore
I created a PIMListListener to listen for all the changes that will occur in the address book.
When a contact is added, I am adding it to the contactsForm successfully
When a contact is removed, I am facing a big problem deleting it :S!!!
I am getting this exeption: "IllegalArgumentException"; this exception's text is : UiEngine accessed without holding the event lock. I know such errors and I know how to resolve them. So I used the following code:
UiApplication.getUiApplication().invokeLater( new Runnable() { public void run() {
synchronized(UiApplication.getEventLock()) {
uiContacts.vm.delete(uiContacts.vm.getField(j));
}
}});
This should resolve the problem. But I keep getting this error again and again. How to resolve this?
Listeners, like the PIMListListener, do not receive their callbacks in the same Application context as your UiApplication. So, in your code, UiApplication.getUiApplication() doesn't really work the way you'd expect it to.
The best thing to do would be to store a reference to your UiApplication in a place where the callback can reach it (during initialization of the UiApplication, perhaps), and then replace UiApplication.getUiApplication().invokeLater(...) with myUiApp.invokeLater(...), where myUiApp is the reference to your UiApplication which you stored earlier.