Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] getExpoPushTokenAsync can't work on first time iOS . need restart ? #8084

Closed
timeturnback opened this issue Apr 30, 2020 · 17 comments · Fixed by #8608
Closed

[iOS] getExpoPushTokenAsync can't work on first time iOS . need restart ? #8084

timeturnback opened this issue Apr 30, 2020 · 17 comments · Fixed by #8608
Assignees

Comments

@timeturnback
Copy link

🐛 Bug Report

I have install expo-notification to use on my Bare workflow app .
On android it work as expected .
How ever , on iOS (xcode 11) . the getExpoPushTokenAsync just hang on the first time opening app ( notification is allowed ) .
The only way to get it work is restart the app ( kill app then re open ) .
Any one know the root cause . Please share

Environment

Expo CLI 3.19.2 environment info:
    System:
      OS: macOS 10.15.3
      Shell: 3.2.57 - /bin/bash
    Binaries:
      Node: 13.13.0 - /usr/local/bin/node
      Yarn: 1.22.4 - /usr/local/bin/yarn
      npm: 6.14.4 - /usr/local/bin/npm
      Watchman: 4.9.0 - /usr/local/bin/watchman
    IDEs:
      Android Studio: 3.6 AI-192.7142.36.36.6392135
      Xcode: 11.4/11E146 - /usr/bin/xcodebuild
    npmPackages:
      expo: ^37.0.8 => 37.0.8 
      react: 16.9.0 => 16.9.0 
      react-native: 0.61.4 => 0.61.4 
      react-navigation: ^4.2.2 => 4.2.2 
    npmGlobalPackages:
      expo-cli: 3.19.2

Steps to Reproduce

Open the app in iOS , log or show the push token . First time open app , request for notification allowed . No pushtoken . Debug show that getExpoPushTokenAsync hang .

Kill app then reopen . getExpoPushTokenAsync can get the push token and show as expected

Expected Behavior

Push token must be granted for the first time open app

RegisterPushToken code

import * as Notifications from "expo-notifications";
import Constants from "expo-constants";

export default async () => {

 await Notifications.requestPermissionsAsync();

  let experienceId;
  if (!Constants.manifest) {
    // Absence of the manifest means we're in bare workflow
    experienceId = "@redclouds/TappoMerchant";
  }
  const expoPushToken = await Notifications.getExpoPushTokenAsync({
    experienceId
  });

  console.log("expoPushToken.data", expoPushToken.data);
  __DEV__ && console.warn("ExponentPushToken", expoPushToken.data);

  return Promise.resolve(expoPushToken.data);
};

@gwendall
Copy link

gwendall commented Apr 30, 2020

+1, getting the error Another async call to this method is in progress. Await the first Promise. :

reject(@"E_AWAIT_PROMISE", @"Another async call to this method is in progress. Await the first Promise.", nil);

@bayoremit
Copy link

+1

@raffaelst
Copy link

Hi,

I am facing the same issue. Works perfectly on Android, but on ios it always returns this mesage. Is there any workaround?

@Enricopv
Copy link

Confirming also that it works for Android, but not for iOS with the same error as above:

Another async call to this method is in progress. Await the first Promise.

@tareksmoubarak
Copy link

Same error after ejecting

@raffaelst
Copy link

Hi all,

I was able to fix it by passing the DevicePushToken as a parameter to getExpoPushTokenAsync function.

@todorone
Copy link

todorone commented Jun 1, 2020

I'm not sure if it's the same issue but I guess they are related. I have bare workflow, unimodules.

Whole notifications flow works perfectly on Android(getting token, sending, receiving) but on iOS Notifications.getDevicePushTokenAsync(and as a result Notifications.getExpoPushTokenAsync) never resolves, it doesn't throw any errors(JS or native), just silently being run forever.

@raffaelst How did You fetch devicePushToken?

@sjchmiela
Copy link
Contributor

Thank you all for reports of this problem. Frankly speaking the being run forever makes the most sense—there is no way another Promise would be run if you didn't run it. I'll take a look at it as soon as possible, I've also heard @tsapeta has reproduced it—the Promise didn't resolve immediately and only after a long while the system permissions dialog showed up—maybe it's some kind of iOS bug? I'll let you know when I find out!

@todorone
Copy link

todorone commented Jun 1, 2020

Thanks for the update @sjchmiela, in my case the issue is that Notifications.getDevicePushTokenAsync does not throw any error at all(which is obviously there) so no chance to figure out the reason of fail, so please check that it throws when something goes wrong...

@sjchmiela
Copy link
Contributor

Hm, ok, so in general I've reread the documentation around configuring push notifications and the docs say:

If a cellular or Wi-Fi connection is not available, neither the application:didRegisterForRemoteNotificationsWithDeviceToken: [Promise resolves] method nor the application:didFailToRegisterForRemoteNotificationsWithError: [Promise rejects] method is called. For Wi-Fi connections, this sometimes occurs when the device cannot connect with APNs over the configured port. If this happens, the user can move to another Wi-Fi network that isn’t blocking the required port. On devices with a cellular radio, the user can also wait until the cellular data service becomes available.

In iOS and tvOS, you initiate APNs registration for your app by calling the registerForRemoteNotifications [getDevicePushTokenAsync] method of the UIApplication object. Call this method at launch time as part of your normal startup sequence. The first time your app calls this method, the app object contacts APNs and requests the app-specific device token on your behalf. The system then asynchronously calls one of two following app delegate methods, depending on success or failure: (…)

After successful APNs registration, the app object contacts APNs only when the device token has changed; otherwise, calling the registerForRemoteNotifications method results in a call to the application:didRegisterForRemoteNotificationsWithDeviceToken: [Promise resolves] method which returns the existing token quickly.

which would explain why on the second call (after restarting the application) it may be quicker.

In a Troubleshooting document one can find suggestions that:

  • registering for push notifications consists of:

    • first, asking for user's permissions to display notifications (Notifications.requestPermissionsAsync)
    • second, registering for push notifications (Notifications.getDevicePushTokenAsync)

    on one hand that sounds reasonable, on the other hand I think push notifications don't necessarily have to be shown, so developer shouldn't need to ask for permissions to show them first, but maybe if that's the only way to it may make sense.

  • If neither delegate callback application:didRegisterForRemoteNotificationsWithDeviceToken: [Promise resolves] nor application:didFailToRegisterForRemoteNotificationsWithError: [Promise rejects] is called, that means that this connection has not yet been established.

    This is not necessarily an error condition. The system may not have Internet connectivity at all because it is out of range of any cell towers or Wi-Fi access points, or it may be in airplane mode. Instead of treating this as an error, your app should continue normally, disabling only that functionality that relies on push notifications.

    Maybe it just can take a long time between calling the method and fulfilling the Promise?

    Keep in mind that network availability can change frequently. Once the persistent connection to the push service succeeds, one of the previously-mentioned application delegate methods will be called.

    Then on one hand

    When the first push-capable app is installed, iOS or OS X attempts to establish a persistent network connection to the push service that will be shared by all push-capable apps on the system.

    but on the other

    Note: There is a separate persistent connection to the push service for each environment. The operating system establishes a persistent connection to the sandbox environment for development builds, while ad hoc and distribution builds connect to the production environment.

    so the fact that we already have apps with push notifications enabled installed on our devices doesn't mean that our apps in development mode have the connection too.


Does it make sense? Is there anyone who could confirm that, probably, the issue is only reproducible while the device has not yet made connection to APNS for development environment or that is more likely to happen on an unreliable connection?


When it comes to getting the

Another async call to this method is in progress. Await the first Promise.

error, I'm worried I can't tell you anything apart from are you sure you're not calling getDevicePushTokenAsync or getExpoPushTokenAsync simultaneously?

The very first thing getExpoPushTokenAsync does is call getDevicePushTokenAsync

export default async function getExpoPushTokenAsync(options: Options = {}): Promise<ExpoPushToken> {
const devicePushToken = options.devicePushToken || (await getDevicePushTokenAsync());

so calling those two methods at the same time will definitely make one of them reject. If you want to fetch both device push token and Expo push token I would recommend calling them either in sequence

const devicePushToken = await Notifications.getDevicePushTokenAsync();
const expoPushToken = await Notifications.getExpoPushTokenAsync();

or reuse the device push token as in

const devicePushToken = await Notifications.getDevicePushTokenAsync();
const expoPushToken = await Notifications.getExpoPushTokenAsync({ devicePushToken });

which makes sense when you see the first line of the implementation of getExpoPushTokenAsync method. 🙂

@RupamShaw
Copy link

RupamShaw commented Jun 2, 2020

Notifications.getDevicePushTokenAsync() also gives same error

 Another async call to this method is in progress. Await the first Promise.

I tried withoutCalling

 await Notifications.getExpoPushTokenAsync();

getDevicePushTokenAsync doesn't return keeps on waiting.
and on refresh gives error

 Another async call to this method is in progress. Await the first Promise.

sjchmiela added a commit that referenced this issue Jun 2, 2020
)

# Why

May make `get[Expo|Device]PushTokenAsync` more resilient by both:
- letting those two methods share a single request to fetch device push token
- handling Fast Refresh better by sharing the single request.

Should fix #8084.

# How

Save the `Promise` that will resolve with device push token.

# Test Plan

- [x] added a new test case and verified that it passes as expected
tsapeta pushed a commit that referenced this issue Jun 2, 2020
)

# Why

May make `get[Expo|Device]PushTokenAsync` more resilient by both:
- letting those two methods share a single request to fetch device push token
- handling Fast Refresh better by sharing the single request.

Should fix #8084.

# How

Save the `Promise` that will resolve with device push token.

# Test Plan

- [x] added a new test case and verified that it passes as expected
@RupamShaw
Copy link

Thanks for fix. When it will get release?

@sjchmiela
Copy link
Contributor

It should be available as expo-notifications@0.3.1! If you're on an older version I recommend you check out changelog—there haven't been many breaking changes (in fact, there was one and only "half-breaking"). Let me know if the upgrade or the explanatory comment have helped!

@aeife
Copy link

aeife commented Jun 10, 2020

We experience a similar behavior with a required restart before getExpoPushTokenAsync works but in the managed workflow. Any information on that? I guess the fix here is just for the bare workflow?

@SeoungJinKim
Copy link

Hello, I experience this error in build ios app. How can I fix it?

@tfisc
Copy link

tfisc commented May 18, 2022

I Face the same issue in a bare workflow app with SDK 42, any news about it ?

@kubmir
Copy link

kubmir commented Aug 17, 2022

Hello, I spent quite a lot of time trying to fix this issue with SDK 41. It was caused by two different version of expo-notifications installed - one from package.json and second one as dependency of expo-file-dl (you can check it using yarn.lock or package-lock.json). Two different versions of package probably caused to call token retrieval more than once even if there was only one function call in the app code.

FYI @tfisc @SeoungJinKim

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.