Deferred Deep Linking in iOS using Branch.io in a React Native App - react-native

We have implemented deferred deep linking in our branch using Branch.io. The flow works correctly in our Android build but not in iOS. When clicking on a deep link in an iOS device the app is correctly installed but the deferred content piece of our deep link is not working. The branch validate gem is all green.
The relevant code is:
branch.subscribe(async ({ error, params, uri }) => {
if (error) {
console.error(`Error from Branch: ${error}`);
return;
}
if (params['+non_branch_link']) {
return;
}
if (!params['+clicked_branch_link']) {
return;
}
const deepLink = params.$deeplink_path;
if (deepLink) {
Linking.openURL(deepLink).catch((e) => { console.log('[Branch Error]', e); });
}
});

Never was able to solve this with existing paradigm. Ended up just setting state inside of my if block and redirecting the user on the incorrect screen if the state was set. Must be some kind of race condition.
¯\ (ツ)/¯

Did you try to set a initSessionTtl time of about 10 seconds?
componentDidMount() {
branch.initSessionTtl = 10000;
branch.subscribe(({ error, params, uri }) => {
//Handle the deep link here
});
}

Related

Deep linking - doesn't work if app is closed

I'm implementing deep linking with expo in my react native app. I've managed to do it using this code with this tutorial and this documentation for adjusting it to my nested stacks:
const linking = {
prefixes:[prefix],
config: {
screens: {
Drawer: {
screens: {
Tabs: {
screens: {
Profile:"profile"
}
}
}
},
}
}
}
return (
<NavigationContainer linking={linking}>
<RootStackScreen actions={actions} showLoader={showLoader} user={user} {...props} />
</NavigationContainer>
)
}
If I use myscheme://profile it works as expected, but only if the app is opened in the background. When the app is closed, then it just open it in my initial home screen, I tried googling and searching but couldn't find any explanation that fits what I did. I also tried adding the getInitialRoute function to linking, which triggers when the app was closed and was opened from a deep link, but couldn't figure how I can use it to activate the navigation.
async getInitialURL() {
const url = await Linking.getInitialURL(); // This returns the link that was used to open the app
if (url != null) {
//const { path, queryParams } = Linking.parse(url);
//console.log(path,queryParams)
//Linking.openURL(url)
return url;
}
},
I suppose that you confirmed that your function getInitialURL is getting called when your app is launched? Also, the commented code within the if (url != null) { aren't supposed to be commented right?
If the above is fine then the issue could be related to the debugger being enabled. As per React Native's documentation (https://reactnative.dev/docs/linking#getinitialurl):
getInitialURL may return null while debugging is enabled. Disable the debugger to ensure it gets passed.
I was experiencing this same issue and doing the following helped me
From the component at the root of your navigation stack, where you configure deep linking, add the following code:
const ApplicationNavigator = () => {
useEffect(() => {
// THIS IS THE MAIN POINT OF THIS ANSWER
const navigateToInitialUrl = async () => {
const initialUrl = await Linking.getInitialURL()
if (initialUrl) {
await Linking.openURL(initialUrl)
}
}
navigateToInitialUrl()
}, [])
const linking = {
prefixes: ['<your_custom_scheme>://'],
config: {
/* configuration for matching screens with paths */
screens: {},
},
}
return (
// Your components/navigation setup
)
}
So apparently, your app received the url but somehow "uses" it to wake the app up from background. When it is in the foreground, the useEffect runs and uses the URL to navigate to the intended screen.
PS: Make sure that your linking tree matches your app tree
There are a couple of things you can check.
Verify that the structure for linking.config matches your navigation structure. I've had a similar issue in the past, and resolved it by making sure my config structure was correct.
Ensure that the linking object is setup properly. Refer to the docs to verify. From the looks of it, the linking object you've showed doesn't have the getInitialURL property in it.
Confirm that you've setup the native side of things as documented.
Hopefully something works out! Let me know if it doesn't. 🙂
Based on https://documentation.onesignal.com/v7.0/docs/react-native-sdk#handlers
Deep linking in iOS from an app closed state
You must be Modify the application:didFinishLaunchingWithOptions in your AppDelegate.m file to use the following:
NSMutableDictionary *newLaunchOptions = [NSMutableDictionary dictionaryWithDictionary:launchOptions];
if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
NSDictionary *remoteNotif = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if (remoteNotif[#"custom"] && remoteNotif[#"custom"][#"u"]) {
NSString *initialURL = remoteNotif[#"custom"][#"u"];
if (!launchOptions[UIApplicationLaunchOptionsURLKey]) {
newLaunchOptions[UIApplicationLaunchOptionsURLKey] = [NSURL URLWithString:initialURL];
}
}
}
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:newLaunchOptions];
also in reactnavigation:
https://reactnavigation.org/docs/deep-linking/
const linking = {
prefixes: ["https://example.com", "example://"],
config,
async getInitialURL() {
const url = await Linking.getInitialURL();
if (url != null) {
return url;
}
},
};
<NavigationContainer linking={linking}>
...
</NavigationContainer>
I was having the same problem. In iOS(flutter build) I solved this by adding "Content Available." The article is here: Apple Content Available Document. I am using OneSignal so in the api I added that field. Now even if the app is forced closed it awakes and deep links work. For Onesignal I had to use "content_available" : true. The complete Onesignal postman code is:
{
"app_id": "1234",
"included_segments": ["Test"],
"content_available" : true,
"contents": {
"en": "Hi"
},
"data": {
"dynamic_link": "https://google.com"
},
"headings": {
"en": "Testing"
}
}

React Native Expo In App Purchase setPurchaseListener position

I am implementing In App Purchase function in Expo bare workflow using Expo-In-App-Purchase library.
In their documentation we need to implement setPurchaseListener on a global scope.
My question would be, anyone know where does global scope means?
Globally typically means in App.js. Using the template from Expo Documentation, it should look like something below in your App.js file (I am using class components).
setUpIAP = async() => {
// Set purchase listener
await InAppPurchases.connectAsync();
await InAppPurchases.setPurchaseListener(({ responseCode, results, errorCode }) => {
// Purchase was successful
if (responseCode === InAppPurchases.IAPResponseCode.OK) {
results.forEach(purchase => {
if (!purchase.acknowledged) {
console.log(`Successfully purchased ${purchase.productId}`);
// Process transaction here and unlock content...
// Then when you're done
InAppPurchases.finishTransactionAsync(purchase, true);
}
});
}
// Else find out what went wrong
if (responseCode === InAppPurchases.IAPResponseCode.USER_CANCELED) {
console.log('User canceled the transaction');
} else if (responseCode === InAppPurchases.IAPResponseCode.DEFERRED) {
console.log('User does not have permissions to buy but requested parental approval (iOS only)');
} else {
console.warn(`Something went wrong with the purchase. Received errorCode ${errorCode}`);
}
});
}
componentDidMount() {
this.setUpIAP();
}

React-Native OneSignal Android error: OneSignal.init has not been called

I'm trying to use the OneSignal react native package to send push notifications to my users. This is working fine for iOS. However, on Android, every call to the OneSignal library times out. When I run adb logcat I see the following message in my console:
E/OneSignal: OneSignal.init has not been called. Could not get OSPermissionSubscriptionState
According to the documentation (https://documentation.onesignal.com/v5.0/docs/react-native-sdk-setup), this is not a step I should do manually, but is rather performed by the library itself when setting up the subscription. I have written some helper functions for OneSignal that are throwing the error above:
getPermissionSubscriptionState = () => {
return new Promise((resolve, reject) => {
OneSignal.getPermissionSubscriptionState((state) =>{
console.log(state);
resolve(state);
});
});
}
enablePushnotifications = async () => {
OneSignal.registerForPushNotifications();
}
ensureSubscription = async () => {
const status = await this.getPermissionSubscriptionState();
console.log(status);
if(!status.notificationsEnabled && !status.hasPrompted) {
this.enablePushnotifications();
}
return status;
}
In my root component, I invoke the ensureSubscription method like so: await OneSignalHelper.ensureSubscription();. Inside ensureSubscription() I invoke the getPermissionSubscriptionState(), but as soon as I do that the above error is printed.
Any help would be greatly appreciated.

trigger branch io without open url

I'm trying to implement in my app in react native app which I can trigger the branch without open url when I scan QR.
here I'm register with the branch
componentDidMount() {
this._unsubscribeFromBranch = branch.subscribe(({ error, params }) => {
if (error) {
console.error("Error from Branch: " + error)
return
}
console.log("Branch params: " + JSON.stringify(params));
if (params) {
this.setState({ scan: { ...this.state.scan, glassHash: params.hash } }, () => {
this._getCurrentPosition();
});
}
});
}
when I scan with Qr it run the function onSuccess and I want to trigger this branch.subscribe without openURL. if i openURL it works fine but it's not what i want
onSuccess(e) {
console.log(e);
// here i want to trigger the branch
this.setState({ barcodeText: e.data });
console.log(e);
}
i found on react-native-branch-deep-links docs about BranchEvent but I don't see any example about it.
I found this
new BranchEvent("UserScannedItem", buo).logEvent()
but not sure how to implement my custom event
Jackie from Branch here.
Branch links do function properly and retain the data when they are used with QR scanners, including dynamic query parameters (params appended behind the URL). If a QR code was used to open the app, you can retrieve the session data by using getLatestReferringParams(). Here's a sample code snippet on reading deep link data:
branch.subscribe(({ error, params }) => {
if (error) {
console.error('Error from Branch: ' + error)
return
}
// params will never be null if error is null
})
let lastParams = await branch.getLatestReferringParams() // params from last open
let installParams = await branch.getFirstReferringParams() // params from original install
More information on using getLatestReferringParams to handle link opens: https://docs.branch.io/pages/apps/react-native/#using-getlatestreferringparams-to-handle-link-opens
Regarding custom events, here's a sample class for generating standard and custom events with the Branch SDK: https://github.com/BranchMetrics/react-native-branch-deep-linking/blob/63cfc566ea45a6af0663fc7530c36fdb5dbf75e6/src/BranchEvent.js
If you are still having issues, please send over a screenshot of an example QR code with the associated Branch link directly to support#branch.io and I'd be happy to do some testing!
Best,
Jackie

How to navigate screen on notification open in React Native with One Signal?

Here is my code, how can I navigate user to the desired screen when clicked on a notification or button in a notification.
componentWillMount() {
OneSignal.addEventListener('received', this.onReceived);
OneSignal.addEventListener('opened', this.onOpened);
OneSignal.addEventListener('registered', this.onRegistered);
OneSignal.addEventListener('ids', this.onIds);
OneSignal.inFocusDisplaying(2);
OneSignal.requestPermissions({
alert: true,
badge: true,
sound: true
});
}
componentWillUnmount() {
this.isUnmounted = true;
OneSignal.removeEventListener('received', this.onReceived);
OneSignal.removeEventListener('opened', this.onOpened);
OneSignal.removeEventListener('registered', this.onRegistered);
OneSignal.removeEventListener('ids', this.onIds);
}
onReceived(notification) {
console.log("Notification received: ", notification);
}
onOpened(openResult) { // HERE I WANT TO NAVIGATE TO ANOTHER SCREEN INSTEAD OF HOME SCREEN
this.isNotification = true;
let data = openResult.notification.payload.additionalData;
let inFocus = openResult.notification.isAppInFocus;
console.log('Message: ', openResult.notification.payload.body);
console.log('Data: ', openResult.notification.payload.additionalData);
console.log('isActive: ', openResult.notification.isAppInFocus);
console.log('openResult: ', openResult);
}
onRegistered(notifData) {
console.log("Device had been registered for push notifications!", notifData);
}
onIds(device) {
try {
AsyncStorage.setItem("#SC:deviceInfo", JSON.stringify(device));
} catch (error) {
console.log(error);
}
}
Do anyone have knowledge about all this, React Native + OneSignal + React Navigation + Redux. Please help!
To achieve the desired behavior you can do couple of things. You can manually check the notification and state of the router and if its necessary redirect the user to the screen or you can use the Deep Linking functionality.
To use Deep Linking you attach url parameter to your notification while sending it. To direct user to the correct screen in your app you can use react-navigation deep linking functionality.
From One Signal Documentation
url string The URL to open in the browser when a user clicks on the
notification. Example: http://www.google.com
Note: iOS needs https or updated NSAppTransportSecurity in plist
From React Navigation Documentation
Deep Linking
In this guide we will set up our app to handle external URIs. Let's start with the SimpleApp that we created in the
getting started guide. In this example, we want a URI like
mychat://chat/Taylor to open our app and link straight into Taylor's
chat page.
You can dispatch a NavigationAction or perform a navigate action when onOpened is fired. Following snippet should work:
componentWillMount() {
OneSignal.inFocusDisplaying(0);
OneSignal.removeEventListener('opened', this.onOpened.bind(this));
OneSignal.addEventListener('opened', this.onOpened.bind(this));
}
onOpened(openResult) {
let data = openResult.notification.payload.additionalData;
// ScreenName is the name of the screen you defined in StackNavigator
this.props.navigation.navigate('ScreenName', data)
}
In search for the solution I landed on this question and I think most of the answers are now old. So, in case anyone looking for the solution can try this.
OneSignal.setNotificationOpenedHandler((notificationResponse) => {
const { notification } = notificationResponse;
if (notification) {
const { additionalData = null } = notification;
if (additionalData) {
const { type } = additionalData;
navigateToScreen(type);
}
}
});
const navigateToScreen = (type) => {
switch (type) {
case "post":
case "track":
navigation.navigate("SinglePost");
return;
default:
return;
}
};
In case someone else comes with a similar problem to mine I want to add onto what #Mostafiz Rahman said. The app I was working on had a bunch of nested stacks and tabs (react-navigation v1) inside of a drawer, and if Stack1 was backgrounded and the notification was for Stack2 I couldn't get them to jump around.
I ended up putting the logic as described by Mr. Rahman in every one of the stacks' first screens -- 1st screen of Stack1, 1st screen of Stack2, etc -- and that did it!