IOS expo push notifications in app killed state - ios

To give more clarity on the issue, i am developing this for IOS using expo push notifications and react-navigation v6 along with expo sdk44 in my current project.
I am having an issue when the user interacts with the notification while the app is in a killed state (the notificatiosn arrives succesfully with all of the data), what im having trouble with is, i want to navigate the user to a specific screen. The problem is because my navigationRef is null. Here i need to mention i did not create a seperate RootNavigator. Like it is shown here https://reactnavigation.org/docs/navigating-without-navigation-prop/ .
What would be the "proper" way of handling this? So far i've tried putting all of the relevant push notification listener code inside NavigatonContainer's onReady callback ( this worked).
I also tried making a seperate useEffect and changing the state of a isReady variable and setting its new value in <NavigationContainer ref={navigationRef} onReady={() => {setNavigatorReady(true)}}> to force a rerender and thus running the code inside useEffect again. (this approach did not work)
Just to sum up my current problem. When the user interacts with a notification while the app is in a killed state i want them to be navigated to a specific screen.
Thanks in advance.

I too am using react-navigation 6.x and expo. To tackle this problem I pretty much followed this section on expo's docs: https://docs.expo.dev/versions/latest/sdk/notifications/#addnotificationresponsereceivedlistenerlistener-event-notificationresponse--void-void.
It shows you how to implement addNotificationResponseReceivedListener which is called whenever a user interacts with a notification. It works in all situations, even when the app is killed, which is the specific situation you are interested in.
The docs also show you how to integrate this listener with react-navigation. Link: https://docs.expo.dev/versions/latest/sdk/notifications/#handling-push-notifications-with-react-navigation

Related

Firestore local cache is stale when returning to mobile app - can I force a re-sync?

I'm watching a cloud firestore list for changes using query.onSnapshop in a react-native-firestore app, currently testing on iOS.
While my app is in the foreground, I can make data changes elsewhere (eg. in my companion web app) and the mobile app immediately updates as expected. Usually, if I make changes while the app is closed or offline, they get picked up no problem once it is re-opened or comes online again. Happy days.
However, sometimes, when the app is in the background (not closed, just some other apps have been used in the meantime), I'll make a change elsewhere (eg. add/delete a record which meets the query's criteria), then when I come back to the app, the list does not change - eg. it contains deleted records, or doesn't contain the new ones. Nothing I do on the app can change this - it remains out-of-sync, even if I make local changes, like editing one of the records (even a deleted one). Changing network conditions also does nothing (eg. switching airplane mode off/on again).
The only way the list will get back in sync is if I make another change elsewhere, while the app is still in the foreground, or if I force-close the app and re-open it again.
The issue seems to occur when connecting to both the emulator, and the actual firestore.
I don't think I'm doing anything fancy. Basically following the examples in the documentation:
import React, { useEffect, useState } from 'react';
import firestore from '#react-native-firebase/firestore';
const MyAssignments = (props) => {
const [records, setRecords] = useState([])
useEffect(() => {
const onSnapshot = (snapshot) => {
console.log(snapshot) // this IS triggered but data is stale
setRecords(snapshot)
}
return firestore()
.collection('assignments')
.where('assignedTo', 'array-contains', props.userId)
.onSnapshot(onSnapshot, console.error);
}, [props.userId]);
// render the list
return ...
}
I'm not sure if this is a general firestore issue, a react-native-firebase issue, an issue with the underlying firebase ios SDK, or just my own misunderstanding?
In either case, is there a way to force the local cache to re-sync programatically, ideally when the app regains focus? Or has anyone solved a similar issue or have any ideas what to try next?
Edit 1: Note the code example above is slightly simplified for readability, as parts are spread across a few files and typed with typescript. In reality, I'm using crashlytics.recordError(e) for error handling in production, but console logging, as above, in development.
Edit 2: To debug, I've tried the following:
Switch on debug logging:
import firebase from '#react-native-firebase/app';
firebase.firestore.setLogLevel('debug');
However, this gave no extra logs in my javascript console.
I found I could view native device logs by following this guide and then filtering for Firebase, like so: idevicesyslog --match Firebase
This still shows very few logs, so I don't think debug logging is switched on properly. However, it does log this error every time I foreground the app:
<Notice>: 8.9.1 - [Firebase/Firestore][I-FST000001] WatchStream (10c244d58) Stream error: 'Unavailable: Network connectivity changed'
This error happens every time though. Even when the onSnapshot successfully picks up changes

How to check application state under swift UI Test

Some background
I am currently writing a UI Test for a settings pane, and click on buttons to enable certain permissions such as push notifications and location services.
However, if the alert for the permission has been displayed before (regardless of the user allowing or denying access to the permission), the alert will not display again, and will just take the user to the settings app. Unfortunately, these settings do not reset, meaning the first time I run the UI tests, alerts will show; and on all subsequent UI test runs, the buttons will take me to the settings app unless I reset the device before the tests begin.
My issue
Thus, my test needs to know if the app went into the background, and attempt to foreground it to continue the testing:
if app.state == background {
foregroundApp()
}
// continue with other tests
Is there any way to determine if the app is in the background?
What I tried
I researched methods to determine the state of the application (running/background/etc) from a UI test, and was not able to find much. I tried to check whether certain elements exist:
if (app.navigationBars.element.exists) ...
but this gives me runtime errors[1] if the user is taken to the settings page because the app under test is in the background, and the test cannot lookup the navigationBars (or other elements).
I tried using some of the methods from Facebook's private headers for XCUIApplication() and XCUIElement().
XCUIApplication().state always returns 3 no matter what state the app is currently in, and any attempts to call XCUIApplication().resolve() to foreground the app give me the same errors as before[1]
I tried to rewrite the logic to foreground the app before resuming the tests, but methods such as XCUIApplication().launch() kill the app before restarting, which I cannot do. Only siri service seems to work for me, but I cannot access the siri service through the corporate proxy, and modifying proxy permissions is not possible.
Is there any other way to check the app state?
Errors
[1] This error is printed every time I try to do something involving state. I do not call snapshotView anywhere, and thus the suggestion to use afterScreenUpdates is useless.
Failure to get snapshot within 15.0s
Cannot snapshot view (<UIKeyboardImpl: 0x7febcc75d000; frame = (0 0;
414 226); layer = <CALayer: 0x608000625720>>) with
afterScreenUpdates:NO, because the view is not in a window. Use
afterScreenUpdates:YES.`
tl;dr
I need to check whether the app I am UI testing has entered the background (i.e. user pressed the home button). Checking for existence of particular elements such as navigation bars doesn't work, neither do most methods from Facebook's private headers for XCUIApplication/XCUIElement. Foregrounding the app also causes issues, and relaunching the app is not an option; neither is siri service.
You can do this in Swift 4, using XCUIApplication.state, which will give you information about the state of the app - whether it's in the foreground or background etc. however, it's not possible to find this information in Swift 3 and below. Essentially, UI testing in Swift 3 doesn't support leaving the app.

lua-lgi libnotify add_action callback not being called

I followed the hello world example on this page to set up lua-lgi and libnotify, successfully getting a notification that looks and acts the samea s if using os.execute("notify-send..."). Notify-send does not allow user actions, from what I've gathered, so I am attempting to directly use the libnotify library to display a notification with a button. When clicked in the notification, it should open a file. I am able to call the function described here with lua, using (building from the hello world example):
Hello:add_action("button", "Open", function(notification, action, user_data) os.open("gedit tmp") end)
which successfully displays a button with the label "Open" in the notification. However, the callback function is not called, so the file does not open. I also noticed when running the script, it actually finishes executing before the notification has fully appeared, so if the program is not running anymore when the button in the notification is clicked, then that's one reason why the callback isn't being called, if it's working correctly up until that point. This is my first experience with lua-lgi, so I'm not sure how these types of callback functions translate into lua, or if they're even supported, which is probably what I'm really trying to understand here. Any help is appreciated regarding this issue, or insight into an alternative to displaying a notification with a button and callback via other means.

Monaca Push Debug App

I am working on an app using Monaca where I need to be able to push notifications to users of the app. I tried getting the monaca push back-end working, and that wouldn't work, the app didn't register. I then moved to using the standard cordova plugin, so I can push directly from my servers, without using monaca's back-end. when I call the init function, normally a register event will fire, this works fine when I build the app. However I very much want to get the custom debugger working, I am able to build the custom debugger, and I see the plugin properly installed. Also the init function gets called, as soon as it is called I get the standard IOS security alert to grant the app permission, however the register event is not fired, therefore I am unable to get the device ID to push to the custom debugger app. Has anyone been able to use push notifications with the custom debugger application?
Best,
Peter
(https://github.com/phonegap/phonegap-plugin-push)
P.S, i calls to set the badge count setApplicationIconBadgeNumber works fine in the debugger. I think this is something simple, maybe with the certs? I dont get any errors fired, no notifications no clues using the standard debugger, how can I hunt down what the issue is?
I can get it work using ngCordova, which is AngularJS integration.
http://ngcordova.com/docs/plugins/pushNotifications/
Then, inject it like
var app = ons.bootstrap('myApp', ['onsen', 'ngCordova']);
Get back to me if you need further clarification.

Manually invoke state preservation in iOS 6 and onwards

Some background
I'm working on an iOS app where we want the state of the application to be preserved.
Before this app is out, iOS 7 is likely to have been released or is soon to be released and the majority seems to have moved away from iOS 5. We have therefore decided to develop for iOS 6 an onwards.
In iOS 6 there are some really nice functionality for preserving state. Just give all views in the storyboard unique IDs and implement these two functions in the "AppDelegate":
- (BOOL)application:(UIApplication*)application shouldSaveApplicationState:(NSCoder*)coder;
- (BOOL)application:(UIApplication*)application shouldRestoreApplicationState:(NSCoder*)coder;
iOS will then "automagically" preserve the navigation history of the app. The methods:
- (void)encodeRestorableStateWithCoder:(NSCoder*)coder;
- (void)decodeRestorableStateWithCoder:(NSCoder*)coder;
can then be used to store and retrieve data.
No problems there it works without issues. But, the methods that save the state are only triggered when the app entered the background.
Let's say we have one NavigationController with four ViewControllers: A, B, C and D. The user navigates from A to B, in B he switches over to Safari to google something. The application state is saved in B. The user then switches back to the app and navigates on to C and then to D. In D the app unfortunately encounters an exception and goes down. When the user restarts the app, iOS will try to restore the saved state. This state however, was saved in B. Which means that when the app launches, it doesn't start from the beginning, not where the user left it (D) and not even the previous view (C) but in B.
A possible solution
The above scenario could be avoided if the app saved its state at every new view. However there aren't (as far as I know) any public methods to trigger the state preservation process.
I have examined the call stack while debugging and found out that iOS calls the following method on the UIApplication object in iOS 6:
_saveApplicationPreservationState:
and the following method in iOS 7:
_saveApplicationPreservationState:viewController:sessionIdentifier:beginHandler:completionHandler:
There also seems to be another method that calls one of the above depending on the iOS version:
_saveApplicationPreservationStateIfSupported
By invoking this method like this:
if ([[UIApplication sharedApplication] respondsToSelector:#selector(_saveApplicationPreservationStateIfSupported)])
[[UIApplication sharedApplication] performSelector:#selector(_saveApplicationPreservationStateIfSupported)];
I can see that the expected methods are called.
The actual question
If I would go with the above solution could that get my app rejected from the App Store? I mean technically it's not a private method, it's just not exposed. By wrapping the call in "respondsToSelector" the app won't crash if the APIs are changed, it just won't save the state as often. But if it could get the app rejected it's not an option.
Or is there any other way to manually invoke the state preservation process other than the one described above? I would be nice to be able to use the built-in functionality rather than building a custom solution that saves the state to NSUserDefaults.
Event if the question is already 2 years old, i'll try my luck. Btw op, i guess, you'll have most likely solved it by now.
You're gonna for sure get rejected. They are scanning the sourcecode for methods you are calling. I had this several times, back when i was using the UDID methods. Hope it helped.

Resources