How do I prompt for Push Notification permissions on Android?

@ryanvanderpol @dikaiosune

What version of android are you testing on? Android 6.0 and above will request most permissions at runtime, so you won’t see a list of permissions on the installation screen most of the time. If you’re running Android 5.x or below, you will see the full list of permissions when you install.

With that said, the most important thing I believe you’re missing is that push notifications are not opt-in for android. All apps should be able to receive push notifications without requesting extra permissions from the user. This is different from iOS. If you aren’t getting the token properly, there might be a different underlying issue. Did you override android.permissions in your app.json? You might need com.google.android.c2dm.permission.RECEIVE in there, but I haven’t fully tested that yet.

@boost So, what you’re saying is that there is no permission prompt for Android 6.0+ (during installation or otherwise)? Permission is granted by default? I think that actually solves my problem. I was expecting there to be a prompt somewhere and I wasn’t getting one but if there is no prompt expected then I think I’m okay.

@boost @jesse So I’m still confused on this. Are android users supposed to get an expo push notification token, something like ExponentPushToken[ALPHA_NUMERIC_STRING]? My android device does show that it allows notifications from my app but my backend does not have a token for android users. Furthermore, android users don’t get notifications while ios users do. The iOS app is distributed via Testflight while the android app is distributed via Google Play Alpha Testing. The device in questions runs android 8.1.0.

As I’m not sure how I’d make a Snack for this, here is the code we use to store push notification tokens and to send push notifications to our users:

STORE PN

class AllowNotificationsWall extends Component {
  state = {
    existingStatus: undefined,
  }

  async componentDidMount() {
    const { status: existingStatus } = await Permissions.getAsync(Permissions.NOTIFICATIONS);
    this.setState({ existingStatus });
  }

  handlePermissionsStatus() {
    this.setState({ existingStatus: 'granted' });
  }

  props: Props;

  render() {
    if (this.state.existingStatus === 'granted') {
      return this.props.renderAllowNotificationsScene();
    }

    return <Notifications changeStatus={() => this.handlePermissionsStatus()} />;
  }
}

class AllowNotifications extends Component {
  static propTypes = {
    changeStatus: PropTypes.func.isRequired,
    session: PropTypes.any,
    updateExpoPushNotificationToken: PropTypes.func,
  }

  static defaultProps = {
    session: undefined,
    updateExpoPushNotificationToken: undefined,
  }

  async registerForPushNotificationsAsync() {
    const { status: existingStatus } = await Permissions.getAsync(Permissions.NOTIFICATIONS);

    let finalStatus = existingStatus;

    // only ask if permissions have not already been determined, because
    // iOS won't necessarily prompt the user a second time.
    if (existingStatus !== 'granted') {
      // Android remote notification permissions are granted during the app
      // install, so this will only ask on iOS
      const { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS);
      finalStatus = status;
    }

    // Stop here if the user did not grant permissions
    if (finalStatus !== 'granted') {
      return;
    }

    // Get the token that uniquely identifies this device
    const token = await Notifications.getExpoPushTokenAsync();

    // POST the token to your backend server from where you can retrieve it to
    this.props.updateExpoPushNotificationToken({
      variables: {
        expoPushNotificationToken: token,
        userId: this.props.session.userId,
      },
    })
      .then(() => {
        this.props.changeStatus();
        console.log('Notification Token update successful');
      })
      .catch(() => {
        console.log('Notification Token update unsuccessful');
      });
  }

  render() {
    return (
        <TouchableWithoutFeedback onPress={() => this.registerForPushNotificationsAsync()}>
          <View style={styles.buttonContainer}>
            <Text style={styles.buttonText}>
              Allow Notifications
            </Text>
          </View>
        </TouchableWithoutFeedback>
    );
  }
}

SEND PN

import Expo from 'expo-server-sdk';
import { flatten, map } from "lodash";

const expo = new Expo();

export function createMessages(tokens, message) {
  return map(tokens, token => ({
    to: token,
    sound: 'default',
    body: message,
  }))
}

export function sendPushNotificationsAsync(messages, callback) {
  let chunks = expo.chunkPushNotifications(messages);
  let receipts = [];
  (async () => {
    for (let chunk of chunks) {
      var status = await expo.sendPushNotificationsAsync(chunk);
      receipts.push(status);
    }
  })().then(() => callback(null, {
        statusCode: 200,
        body: JSON.stringify({
          response: flatten(receipts)
        })
    }))
    .catch(error => callback(null, {
      statusCode: 400,
      body: JSON.stringify({
        response: flatten(receipts)
      })
    }));
}

@danstepanov Yea, android users will get a push token string like ExponentPushToken[ALPHA_NUMERIC_STRING] too. Here’s what I use to grab push tokens. You might want to add extra logging to see where it’s failing in your app. The overall structure looks right, so it’s not immediately obvious what’s wrong.

Are you getting requests to your push notif endpoint from android users at all? I also recommend storing (on your backend) whether the device is ios or android, as there’s no API to retrieve that fact, and you might want different logic for the different platforms (mostly regarding badging).

  async registerForPush() {
    let {status} = await Permissions.getAsync(
      Permissions.REMOTE_NOTIFICATIONS);
    Logger.log('Push permission getStatus ' + status);

    if (status !== 'granted') {
      // Android remote notification permissions are granted during the app
      // install, so this will only ask on iOS
      let askStatus = await Permissions.askAsync(
        Permissions.REMOTE_NOTIFICATIONS);
      Logger.log('Push permission status ' + askStatus.status);

      // Stop here if the user did not grant permissions
      if (askStatus.status !== 'granted') {
        throw new Error('User did not grant permission');
      }
    }

    // Get the token that uniquely identifies this device
    let token = await Notifications.getExpoPushTokenAsync();

    Logger.log('registering push token: ' + token);

    return await this.post('/register_push/', {token, platform: Platform.OS});
  }
1 Like