1

We have a React Native app which shows our mobile website and adds some extra features.

Since Android 12 App links (like domain.com) always open our app: https://developer.android.com/training/app-links

This behaviour is not always desirable, for example in this scenario:

  1. Customer is logged in and starts an order via their browser
  2. Customer needs to pay via an app from their bank
  3. After payment, the customer is redirected back to our website (domain.com/returnUrl)

Now the app is opened, instead of the browser, so the customer isn't logged-in and isn't allowed to view the page.

In this case, after payment started from the browser, we would like to redirect the customer back to the browser instead of the app.

Is there a way to open a link in the browser (ie. via domain.com/returnUrl?force-browser) instead of the app?

Related: Android App link - Open a url from app in browser without triggering App Link

user1255553
  • 960
  • 2
  • 15
  • 27

3 Answers3

2

Based on this answer, I've created a RN Native Module and instead of using await Linking.openURL(url) you can just use the Native Module's exposed method to open Android App links.

I've followed the official RN tutorial to make an Android Native Module.

So in summary, first you will have to create a Java class file inside android/app/src/main/java/com/your-app-name/folder. I've named the module DefaultBrowserModule so the path is src/main/java/com/your-app-name/DefaultBrowserModule.java. Here's how it looks like:

package com.your-app-name;

import android.content.Intent;
import android.net.Uri;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class DefaultBrowserModule extends ReactContextBaseJavaModule {
    private ReactApplicationContext _context;

    DefaultBrowserModule(ReactApplicationContext context) {
        super(context);
        this._context = context;
    }

    @NonNull
    @Override
    public String getName() {
        return "DefaultBrowserModule";
    }

    // This is the method that we're exposing
    @ReactMethod
    public void openUrl(String url) {
        Intent defaultBrowser = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER);
        defaultBrowser.setData(Uri.parse(url));
        // Through ReactApplicationContext's current activty, start a new activity
        this._context.getCurrentActivity().startActivity(defaultBrowser);
    }
}

After that we'll have to register the module with React Native. That can be done by adding a new Java class file to the android/app/src/main/java/com/your-app-name/ folder. I've named mine DefaultBrowserPackage: src/main/java/com/your-app-name/DefaultBrowserPackage.java:

package com.your-app-name;

import androidx.annotation.NonNull;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class DefaultBrowserPackage implements ReactPackage {
    @NonNull
    @Override
    public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new DefaultBrowserModule(reactContext));
        return modules;
    }

    @NonNull
    @Override
    public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

The last step is to register the DefaultBrowserPackage inside of MainApplication.java (android/app/src/main/java/com/your-app-name/MainApplication.java). Locate ReactNativeHost’s getPackages() method and add your package to the packages list

@Override
protected List<ReactPackage> getPackages() {
  @SuppressWarnings("UnnecessaryLocalVariable")
  List<ReactPackage> packages = new PackageList(this).getPackages();
  // below DefaultBrowserPackage is added to the list of packages returned
  packages.add(new DefaultBrowserPackage());
  return packages;
}

Now we are ready to use it inside of JS. So wherever you want to use it, you can do it like this:

import { Linking, NativeModules, Platform } from 'react-native';

// DefaultBrowserModule should be equal to the return value of the getName() method
// inside of the src/main/java/com/your-app-name/DefaultBrowserModule.java class
const { DefaultBrowserModule } = NativeModules;

export const openUrl = async (url) => {
  if (Platform.OS === 'android') {
    DefaultBrowserModule.openUrl(url);
  } else {
    await Linking.openURL(url);
  }
};

// And then use it like this
await openUrl('https://my-app-link-domain.com');
Kapobajza
  • 2,254
  • 15
  • 22
  • After following all the steps shared above, I am getting this error. Possible Unhandled Promise Rejection (id: 0): TypeError: Cannot read property 'openUrl' of null – Mohsin Nov 30 '22 at 10:33
  • DefaultBrowserModule is null. – Mohsin Nov 30 '22 at 10:34
  • @Mohsin which version of RN do you use? – Kapobajza Nov 30 '22 at 11:24
  • packages.add(new DefaultBrowserPackage()); was missing in Main Application.java. Please add these lines in your answer too. – Mohsin Nov 30 '22 at 12:17
  • One more thing, this thing works only with devices having chrome installed in them, if chrome browser is not installed, the app crashes with error no activity found to handle intent (act=android.intent.action.Main) android.intent.category.app_browser. I am trying that on amazon device. – Mohsin Nov 30 '22 at 12:21
  • @Mohsin Thank you. I have updated my answer and included the `packages.add(new DefaultBrowserPackage());` – Kapobajza Nov 30 '22 at 13:35
  • @Mohsin and that's the reason why I included the link for the official RN docs on how to create a native module in the first place. It's better covered and more detailed than my answer. – Kapobajza Nov 30 '22 at 13:37
  • @Mohsin as it is stated in my answer, I have based this solution on [this answer](https://stackoverflow.com/a/58801285/5049799). If you explore more answers to that question, you will maybe find more suitable options. That answer has also been updated to include the solution for Android 13 (API level 33). Maybe that could work on that Amazon device. – Kapobajza Nov 30 '22 at 13:41
  • why this happens for android only? on iOS this scenario is not reproduced. Any idea? – Mohsin Dec 01 '22 at 09:20
  • @Mohsin because iOS and Android handle deep links differently. – Kapobajza Dec 01 '22 at 09:49
1

The best possible solution for that can be using android:pathPattern in android manifest. Basically you have to provide path pattern (a sort regex) to match the valid links.

Documentation for that can be found here. https://developer.android.com/guide/topics/manifest/data-element

Mohsin
  • 263
  • 2
  • 19
  • Great solution. I could limit the deep lining just by adding an android:pathPrefix data element. – hlidka Jun 30 '23 at 00:25
0

Deep and universal linking happens on the operating level and it's hard to control the behavior of other app linking I think it should security breach as some apps try to override the deep link behaviors of another app.

Try to create your simple page with your custom URL https://my-domain.com which redirect to tour target URL without opening associated app.

Fiston Emmanuel
  • 4,242
  • 1
  • 8
  • 14
  • 1
    I'm afraid that doesn't work. Using a redirect (301) from `https://my-redirect.com -> https://my-domain.com` the link is still opened via the app instead of the browser. – user1255553 Feb 10 '22 at 16:22
  • The goal is to open a link in a browser then after a few seconds redirect to that original domain. There's no interference with device deep linking – Fiston Emmanuel Feb 10 '22 at 16:36