iOS 15 ATT permission not showing up
VickyStash opened this issue · 22 comments
Bug summary
ATT permission prompt not showing on iOS 15
Before it was working fine for iOS 14.5 and I didn't change anything.
Library version
3.0.3
Environment info
SDKs:
iOS SDK:
Platforms: iOS 14.0, DriverKit 19.0, macOS 10.15, tvOS 14.0, watchOS 7.0
Android SDK:
API Levels: 23, 25, 26, 27, 28, 29, 30
Build Tools: 27.0.3, 28.0.2, 28.0.3, 29.0.2
System Images: android-26 | Google APIs Intel x86 Atom, android-30 | Google APIs Intel x86 Atom
Android NDK: Not Found
IDEs:
Android Studio: 4.0 AI-193.6911.18.40.6626763
Xcode: 12.0.1/12A7300 - /usr/bin/xcodebuild
Languages:
Java: 1.8.0_222 - /usr/bin/javac
Python: 2.7.15 - /usr/local/bin/python
npmPackages:
@react-native-community/cli: Not Found
react: 16.13.1 => 16.13.1
react-native: 0.63.4 => 0.63.4
react-native-macos: Not Found
Please respect the issue template.
@zoontek I've updated the description a little
I'm not sure if it's a problem with the lib or with ios 15
Are you using an iPhone? Which one? The simulator?
@zoontek I'm checking on iPhone 8 Plus, before update to ios 15 permission was showed as expected
Also please see https://www.reddit.com/r/iOSProgramming/comments/pt41jz/att_prompt_not_showing_on_ios_15/
@zoontek any ideas?
Reading the Reddit thread, it seems that Apple broke the feature. The code is super basic: https://github.com/zoontek/react-native-permissions/blob/master/ios/AppTrackingTransparency/RNPermissionHandlerAppTrackingTransparency.m#L38
The application is active too since the app JS bundle is loaded.
We are effected here as well, when trying to release our app 4 days ago we got hit with a rejection. Apple responded with:
We're looking forward to completing our review, but we need more information to continue.
Your app uses the AppTrackingTransparency framework, but we are unable to locate the App Tracking Transparency permission request.
Right now, we are calling request(PERMISSIONS.IOS.APP_TRACKING_TRANSPARENCY)
during the on mount of the very root index.js file in our app and it's still not good enough.
The prompt appears in iOS 14, but does not occur at all in iOS 15. I've tried AppState and it does not fulfill this scenario, since it does not register on a first launch of the app.
Is it possible for us to tap into applicationDidBecomeActive
somehow in React Native?
On further studying, it would seem that Apple has made it so that iOS 15 and onwards the app must be in an active
state before the app tracking prompt can be fired.
That means if we're trying to fire it off on the on mount of the very first screen/component/file that gets loaded, it's going to fail. The app is not yet in an active state during the launch period despite those screens counting as being mounted.
What seems to work for us right now is to just put the logic within AppState event listener, and on active
fire off the permission request.
An example of what we're doing in the on mount event of the very root index file of our RN app.
checkPermission = async () => {
const result = await check(PERMISSIONS.IOS.APP_TRACKING_TRANSPARENCY);
if (result === RESULTS.DENIED) {
await request(PERMISSIONS.IOS.APP_TRACKING_TRANSPARENCY);
}
};
handleAppStateChange = (nextState) => {
if (nextState === "active") {
if (Platform.OS === "ios") {
// Only once app has become active
// can we prompt app tracking permission (Apple iOS 15 requirement)
this.checkPermission();
}
}
};
componentDidMount() {
// Add a listener
AppState.addEventListener("change", this.handleAppStateChange);
};
Just to note, we're using a rather outdated version of React Native here. This project is still stuck on RN 0.61.5, hence the this
keyword and ES6 React class lifecycle methods still being in use.
In RN 0.65.1 (latest at this point in time), AppState has undergone functionality removal for listeners. You may need to fiddle around with AppState differently in 0.65 onwards.
The above solution seems to work in both iOS 14.5 on an iPhone 12 Pro Max, and also in iOS 15 on an iPhone 13 Pro Max.
I just found a better way base on @sabun123 's answer.
You can put this code anywhere, not required to the root index file.
const state = useAppState();
useEffect(() => {
if (state === 'active' && Platform.OS === "ios") {
request(PERMISSIONS.IOS.APP_TRACKING_TRANSPARENCY)
.then((result) => {
console.log(result);
})
.catch((err) => {
console.log(err);
});
}
return () => {};
}, [state]);
This could perhaps be added to the documentation somewhere near the ATT information? Could probably help a lot of people, it looks really clean to me
having the same issues
Using useAppState
makes your component re-render each time its status change. You can use:
useEffect(() => {
const listener = AppState.addEventListener("change", (status) => {
if (Platform.OS === "ios" && status === "active") {
request(PERMISSIONS.IOS.APP_TRACKING_TRANSPARENCY)
.then((result) => console.log(result))
.catch((error) => console.log(error));
}
});
return listener.remove;
}, []);
Using
useAppState
makes your component re-render each time its status change. You can use:[code snippet]
That seems like a good enough workaround (or solution, even), but this definitely needs to go into the docs as a "Note: On iOS 15+ ..." imho.
That seems like a good enough workaround (or solution, even), but this definitely needs to go into the docs as a "Note: On iOS 15+ ..." imho.
easily rewritten as:
"That seems like a good enough workaround (or solution, even), I'll propose a PR to the docs as a "Note: On iOS 15+ ...".
That seems like a good enough workaround (or solution, even), but this definitely needs to go into the docs as a "Note: On iOS 15+ ..." imho.
easily rewritten as:
"That seems like a good enough workaround (or solution, even), I'll propose a PR to the docs as a "Note: On iOS 15+ ...".
You're absolutely right. Please see #657. I sometimes forget, that "the docs" is just the README, which, in turn, is just a part of the repo like everything else!
You can create a PR to add it.
I would recommend not to specify on "iOS 15+" since there is no downside on using this method for all ATT permission requests. Just saying that in case people would add conditions to use AppState on iOS > 15 and another way on iOS =< 15.
mehh still have this issue on iOS15 but it is location_permission
Using
useAppState
makes your component re-render each time its status change. You can use:useEffect(() => { const listener = AppState.addEventListener("change", (status) => { if (Platform.OS === "ios" && status === "active") { request(PERMISSIONS.IOS.APP_TRACKING_TRANSPARENCY) .then((result) => console.log(result)) .catch((error) => console.log(error)); } }); return listener.remove; }, []);
This workaround does not trigger the request function when the app is first launched. It does however trigger it if you put it to background and then open the app again. But I don't understand why this would be needed if we run it from useEffect. It won't trigger even if I execute it in a useEffect later in the app. Something seems broken and I don't think this issue should be closed.
useEffect(() => {
if (Platform.OS !== "ios") {
return; // don't create listeners on other platforms
}
const requestPermission = () => {
request(PERMISSIONS.IOS.APP_TRACKING_TRANSPARENCY)
.then((result) => console.log(result))
.catch((error) => console.log(error));
};
// if the app is active when the effect is fired, request immediately
if (AppState.currentState === "active") {
return requestPermission();
}
// otherwise setup a listener…
const listener = AppState.addEventListener("change", (status) => {
if (status === "active") {
// …which will perform the request once the app is active
requestPermission();
}
});
return listener.remove;
}, []);
Calls to the API only prompt when the application state is
UIApplicationStateActive
So no, this issue can stay closed 😄