Android Deeplinking how to fix asset verifier toggles between success & failure, SHA256 in firebase, min target 16, optimal intents & splash handling

0

I had app deeplinking working for iOS but for a long while it was only partially working for Android - it would show the dissambugity popup but wouldn't go directly into the app (as though autoVerify wasn't set to true). I share here my experience getting this working in this here, along with my outstanding questions for areas that I am not totally comfortable with still.

TLDR; Try clearing your Android HTTP / statementservice cache!!

Here's the other stuff I've fixed along the way to reach this point ...

1) This article here https://chris.orr.me.uk/android-app-linking-how-it-works/ told me about the command to clear the HTTP cache in Android, combining this with a full un-install afterwards and re-deploying was enough to get it working.

adb shell pm clear com.android.statementservice

2) When in doubt, always un-install fully, before installing and wait 15s before trying any ADB commands to verify things.

3) I had three domains I wanted working uat.myapp.com, www.myapp.com and myapp.com and I deployed my Android assetlinks.json incorrectly, it was deployed to all environments but during testing I only editing the one on UAT. It turns out that if any ONE domain fails, then it can cause the verification to fail outright in any subsequent verification attempts! I beleive fixing the assetlinks.json and clearing the cache in step 1 was enough to resolve auto verify.

4) The digital asset verifier from Google does not work in Safari on Mac, the buttons are disabled making it impossible to test. Once I used the Chrome browser instead I found my UAT file was being picked up okay, but my UAT and LIVE ones did not. It confirmed UAT worked but my two live domains did not.

5) I incorrectly put my apps namespace in the assetlinks.json, but it MUST be "android_app", this is hugely deceiving when you're trying yo follow documentation and populate it with your own namespace e.g.

  [
     {
        "relation":[
           "delegate_permission/common.handle_all_urls"
        ],
        "target":{
           "namespace":"android_app",
           "package_name":"com.mycompany.myapp",
           "sha256_cert_fingerprints":[
              "MY:REDACTED:SHA:WAS:HERE"
           ]
        }
     }
  ]

6) Be careful not to paste in your SHA1 instead of the SHA256 signing key that you use for your builds.

7) My clue was by running these ADB commands

  $ adb shell dumpsys package domain-preferred-apps

This returned 'ask' instead of the expected 'always'

  App verification status:

  ...
    Package: com.mycompany.myapp
    Domains: myapp.com www.myapp.com uat.myapp.com
    Status:  ask <-- should be always

  App linkages for user 0:
  ...

8a) Testing does not work if you paste the URL directly into the Chrome search bar 8b) Testing does not work if your site has a link to a deep-link URL hosted by the same site 8c) I tested by putting a public HTML file in my Amazon S3 bucket (this way the original domain was my s3.eu-west-2.amazonaws.com) 8d) This ADB command will help you test without any of the above complications

  adb shell am start -a android.intent.action.VIEW \
   -c android.intent.category.BROWSABLE \
   -d "https://uat.myapp.com/search/xxxxx"

This will result in output like the following

  Starting: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://uat.myapp.com/... }

The app should launch with no ambiguty / app popup. If you do set the popup then work backwards from here and ensure you've done all the above. It worked for me, but I can't promise it will for you. If it doesn't then there are some other good links that explain what else might be tripping you up. I've listed those at the end of this SO question.

9) Another clue was the ADB logs should show confirmation that the verification was okay, but instead it didn't say anything

  03-25 16:23:58.127: I/ActivityManager(769): START u0 {act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://uat.myapp.com/... flg=0x10000000 cmp=android/com.android.internal.app.ResolverActivity} from uid 2000
  03-25 16:23:58.244: I/MicroDetectionState(6889): Should stop hotword detection immediately - false
  03-25 16:23:58.296: I/ActivityManager(769): Displayed android/com.android.internal.app.ResolverActivity: +139ms
  03-25 16:23:58.315: W/ResourceType(20085): Found multiple library tables, ignoring...

10) For some reason you need a copy of your app already on the app store (I think they mean just on Google Play)

So, now I've helped you, perhaps you can help me understand a few bits?

Q1) Why is it the digital asset verifier sometimes saying it works okay, then immediately afterwards says it fails? Oddly the ones that work always have a maxAge less than 60, those that fail have a max age of 600 - but if I refresh the browser they reset back to 60 or 600. So it's not a caching issue in the browser (forced refresh) yet they seem to toggle between working and not still.

A) Working response ...

(Note it has an ERRORS banner but it says 'None')

  {
    "statements": [
      {
        "source": {
          "web": {
            "site": "https://uat.myapp.com."
          }
        },
        "relation": "delegate_permission/common.handle_all_urls",
        "target": {
          "androidApp": {
            "packageName": "com.mycompany.myapp",
            "certificate": {
              "sha256Fingerprint": "MY:REDACTED:SHA:WAS:HERE"
            }
          }
        }
      }
    ],
    "maxAge": "32.620904405s",
    "debugString": "********************* ERRORS *********************\nNone!\n********************* INFO MESSAGES *********************\n* Info: The following statements were considered when processing the request:\n\n---\nSource: Web asset with site https://uat.myapp.com. (which is equivalent to 'https://uat.myapp.com')\nRelation: delegate_permission/common.handle_all_urls\nTarget: Android app asset with package name com.mycompany.myapp and certificate fingerprint MY:REDACTED:SHA:WAS:HERE\nWhere this statement came from:\n  Origin of the statement: Web asset with site https://uat.myapp.com. (which is equivalent to 'https://uat.myapp.com')\n  Include directives followed (in order):\n    \u003cNone\u003e\nMatches source query: Yes\nMatches relation query: Yes\nMatches target query: Yes\n\n--- End of statement list. ---\n\n\n"
  }

B) Failing...

  {
    "maxAge": "599.999999944s",
    "debugString": "********************* ERRORS *********************\n* Error: unavailable: Error fetching statements from https://uat.myapp.com./.well-known/assetlinks.json (which is equivalent to 'https://uat.myapp.com/.well-known/assetlinks.json'): URL_ERROR/3 [10] while fetching Web statements from https://uat.myapp.com./.well-known/assetlinks.json (which is equivalent to 'https://uat.myapp.com/.well-known/assetlinks.json') using download from the web (ID 1).\n********************* INFO MESSAGES *********************\n* Info: No statements were found that match your query\n",
    "errorCode": [
      "ERROR_CODE_FETCH_ERROR"
    ]
  }

Q2) I've referenced the app signing key (SHA256) in the assetlinks.json, but within the Xamarin documentation https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/deep-linking#android it references Firebase and our firebase project that we use from app center for push notifications uses a completely different SHA key so the google-services.json doesn't feel complete (it doesn't reference our app-signing key - but from memory the push notification/app-center really doesn't care; only firebase invites or firebase API). When I tried to add my app-signing key to that same firebase project it nagged saying "oAuth2 client already exists for this package and SHA-1 in another project". In fact as I read that it mentions SHA1 and maybe I didn't paste in the SHA256... Should the firebase project reference the app signing key in order to get any of the indexing stuff working (I haven't done that bit yet).

Q3) Xamarin.Forms application indexing and deep linking functionality is only available on the iOS and Android platforms, and requires a minimum of iOS 9 and API 23 respectively. I have the minimum target set to 16 and target 28 (Android 9) - what will happen on API <23? Will only the app diassambiguity popup appear (no auto verify)?

Q4) I'm using multiple intent filters, but I'm not 100% confident that they don't overwrite each other.

    // *** HTTP Variants***

    // Deep Links - uat.myapp.com
    [IntentFilter(new[] { Intent.ActionView },
                  Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault },
                  DataScheme = "http",
                  DataHost = "uat.myapp.com",
                  DataPathPrefix = "/search/",
                  AutoVerify = true)]

    // Deep Links - uat.myapp.com
    [IntentFilter(new[] { Intent.ActionView },
                  Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault },
                  DataScheme = "https",
                  DataHost = "uat.myapp.com",
                  DataPathPrefix = "/search",
                  AutoVerify = true)]

    // Deep Links - myapp.com
    [IntentFilter(new[] { Intent.ActionView },
                  Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault },
                  DataScheme = "http",
                  DataHost = "myapp.com",
                  DataPathPrefix = "/search/",
                  AutoVerify = true)]

    // Deep Links - www.myapp.com
    [IntentFilter(new[] { Intent.ActionView },
                  Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault },
                  DataScheme = "http",
                  DataHost = "www.myapp.com",
                  DataPathPrefix = "/search/",
                  AutoVerify = true)]

    //*** HTTPS Variants***

    // Deep Links - myapp.com
    [IntentFilter(new[] { Intent.ActionView },
                  Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault },
                  DataScheme = "https",
                  DataHost = "myapp.com",
                  DataPathPrefix = "/search/",
                  AutoVerify = true)]

    // Deep Links - www.myapp.com
    [IntentFilter(new[] { Intent.ActionView },
                  Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault },
                  DataScheme = "https",
                  DataHost = "www.myapp.com",
                  DataPathPrefix = "/search/",
                  AutoVerify = true)]

I also note that you can use arrays in the DataHost and DataScheme, so can this above be safely reduced to the following...

    [IntentFilter(new[] { Intent.ActionView },
                  Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault },
                  DataSchemes = new string[] {
                      "http",
                      "https"
                  },
                  DataHosts = new string[] {
                      "myapp.com",
                      "uat.myapp.com",
                      "www.myapp.com"
                  },
                  DataPathPrefix = "/search/",
                  AutoVerify = true)]

Q5) In my activity I use the launch mode of LaunchMode.SingleTask, will this be okay for deep-linking and app indexing combinations?

    // WARNING - Don't set this to be singleInstance as this might affect the ProxyAndroidActivityResult function
    // refer to https://groups.google.com/forum/#!topic/android-platform/gcgxOSBZNpY
    [Activity(Label = "My App", Icon = "@mipmap/ic_launcher", RoundIcon = "@mipmap/ic_round_launcher", WindowSoftInputMode = SoftInput.AdjustResize, MainLauncher = false, LaunchMode = LaunchMode.SingleTask, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, ScreenOrientation = ScreenOrientation.Portrait)]
    public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity

Q6) My IntentFilter is registered on my MainActivity, but I also use a SplashScreenActivity that launches my MainActivity. When the deep-linking launches the app I'm wondering whether to add the IntentFilter annotations to the splash, but then a) what happens if the app is already running - will the splash show then invoke the main activity and will that mean I need to pass data from the splash activity to the MainActibity or will the deep-links still be available directly from the MainActivity?

Q7) During setting up the project I needed to upgrade to the latest firebase nugets (due to a build issue) and add the https://www.nuget.org/packages/Xamarin.Forms.AppLinks/ nuget. We use ProGuard in our project and I'm worried that I might need to add something to prevent ProGuard stripping something out?

Resources used

Xamarin Specific - https://docs.microsoft.com/en-us/xamarin/android/platform/app-linking#configure-intent-filter

Xamarin Specific - https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/deep-linking

Very good - https://medium.com/@ageitgey/everything-you-need-to-know-about-implementing-ios-and-android-mobile-deep-linking-f4348b265b49

App links intent filters in assetlinks.json not working on Android

https://developers.google.com/digital-asset-links/tools/generator (only works in Chrome for me, not Safari on Mac - the buttons remain disabled)

https://chris.orr.me.uk/android-app-linking-how-it-works/

android
firebase
xamarin
xamarin.forms
deep-linking
asked on Stack Overflow Mar 25, 2020 by Devology Ltd • edited Mar 25, 2020 by Devology Ltd

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0