LaunchUri - Fuse App on Android crashes if no activity can be found to handle intent

OS: Android

OS Version Tested: 5.0.2 & 5.0.1

Device Tested: Samsung Galaxy S6 & Samsung Galaxy S4

Fuse App Compiled On: Windows 10

Fuse version: 0.12.2 (build 6303)

Issue:
The Android version of the app will crash when using the UX native trigger <LaunchUri> if the requested activity cannot be found to handle intent.

Debug info from Android:

DEBUG|04-25 13:20:27.820|27664|27664||Instrumentation|checkStartActivityResult  :Intent { act=android.intent.action.VIEW dat=purple://company.purple.com }

WARNING|04-25 13:20:27.830|27664|27664||System.err|android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=purple://company.purple.com }

DEBUG|04-25 13:20:27.850|27664|27664||fuseapp|android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=purple://company.purple.com }

INFO|04-25 13:20:27.850|27664|27664||fuseapp|android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=purple://company.purple.com}

INFO|04-25 13:20:27.850|27664|27664||fuseapp|android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=purple://company.purple.com }

WARNING|04-25 13:20:27.850|27664|27664||System.err|java.lang.RuntimeException: android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=purple://company.purple.com }

Debug Output From Fuse:

The txt file had to be zipped as the post went over its word limit, can be downloaded here: Debug Output From Fuse

Expected Outcome:
The Android version of the app should not crash when the UX native trigger <LaunchUri> is triggered when there is no activity found to handle intent. Nothing should happen when triggered (similar to iOS).

To Recreate:

  1. Install below test app on device
  2. Tap on “Open App” button
  3. If requested app is not installed on Android the app will crash

Test App:

<App Theme="Basic">
    <Button Text="Open App">
        <Tapped>
            <LaunchUri Uri="purple://company.purple.com" />
        </Tapped>
    </Button>
</App>

Our current rushed workaround for this is below. I wanted to create a custom TriggerAction such as <LaunchApp Uri="purple://company.purple.com" /> but have not figured out how to do that yet. In the example below the third button will not crash app but the other two will if no intent is found.

A couple of questions:

  • If you uncomment Launcher.LaunchUri(new Uno.Net.Http.Uri(appPath)); an error will be thrown E3111: 'Uno.Net' does not contain type or namespace 'Http'. Could you be missing a package reference? what would be causing that?
  • On catch how would you activate the toast? Is my context reference not correct?
  • How could I turn this into a TriggerAction instead?

MainView.ux

<App Theme="Basic">
    <LaunchApp ux:Global="LaunchApp" />
    <JavaScript>
        var interApp = require('FuseJS/InterApp');
        var launchApp = require("LaunchApp");

        function clickMe() {
            interApp.launchUri ('purple://company.purple.com')
        }

        function unoClick() {
            launchApp.open('purple://company.purple.com')
        }

        module.exports = {
            clickMe: clickMe,
            unoClick: unoClick
        };
    </JavaScript>
    <StackPanel>
        <Button Text="Open App (UX)">
            <Tapped>
                <LaunchUri Uri="purple://company.purple.com" />
            </Tapped>
        </Button>
        <Button Text="Open App (JS)">
            <Tapped>
                <Callback Handler="{clickMe}" />
            </Tapped>
        </Button>
        <Button Text="Open App (Uno)">
            <Tapped>
                <Callback Handler="{unoClick}" />
                <!-- LaunchApp Uri="purple://company.purple.com" /-->
            </Tapped>
        </Button>
    </StackPanel>
</App>

LaunchApp.uno

using Uno;
using Uno.Collections;
using Fuse;
using Fuse.Scripting;
using Fuse.Reactive;
using Uno.Compiler.ExportTargetInterop;

[ForeignInclude(Language.Java, "com.fuse.Activity", "android.content.Context", "android.widget.Toast", "android.content.Intent", "android.net.Uri")]
public class LaunchApp : NativeModule
{
    public LaunchApp()
    {
        AddMember( new NativeFunction("open", (NativeCallback)Open) );
    }

    static object Open(Context c, object[] args)
    {
        if (args.Length > 0)
        {
            var appPath = args[0] as string;
            Open(appPath);
        }
        return null;
    }

    [Foreign(Language.Java)]
    static extern(Android) void Open(string appPath)
    @{
        try {
            Uri uri = Uri.parse(appPath);
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(uri);
            intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
            Activity.getRootActivity().startActivity(intent);
        } catch (Exception e) {
            //e.printStackTrace();
            //Context context = Activity.getRootActivity();
            //CharSequence text = "Hello toast!";
            //int duration = Toast.LENGTH_SHORT;
            //Toast toast = Toast.makeText(context, text, duration);
            //toast.show();
        }
    @}

    static extern(!Android) void Open(string appPath)
    {    
        //Launcher.LaunchUri(new Uno.Net.Http.Uri(appPath));
    }
}

Quick update: Adding Uno.Net.Http to .unoproj solves ‘Uno.Net’ error when calling Launcher.LaunchUri(new Uno.Net.Http.Uri(appPath));

Thanks for reporting. We have opened an internal ticket for tihs issue, and will hopefully get a fix for this ready for the 0.12.3 release.

Thank you, it is greatly appreciated.

We updated our workaround to use a custom TriggerAction which displays an alert (iOS) or toast message (Android) if the app fails to launch (i.e. <LaunchApp Uri="purple://company.purple.com" />).

using Uno;
using Uno.Collections;
using Fuse;
using Fuse.Triggers.Actions;

using Uno.Compiler.ExportTargetInterop;

[ForeignInclude(Language.Java, "com.fuse.Activity", "android.content.Context", "android.content.Intent", "android.net.Uri", "android.widget.Toast")]
public class LaunchApp : TriggerAction
{
    String _uri = "";

    public String Uri
    {
        get { return _uri; }
        set { _uri = value; }
    }

    protected override void Perform(Node target)
    {
        if defined(Android || iOS)
        {
            // Do Android/iOS Stuff
            OpenApp();
        } else {
            Launcher.LaunchUri(new Uno.Net.Http.Uri(Uri));
        }
    }

    [Foreign(Language.Java)]
    public extern(Android) void OpenApp()
    @{
        try {
            Uri uri = Uri.parse(@{LaunchApp:Of(_this).Uri:Get()});
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(uri);
            intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
            Activity.getRootActivity().startActivity(intent);
        } catch (Exception e) {
            Context context = Activity.getRootActivity();
            CharSequence text = "Hello toast!";
            int duration = Toast.LENGTH_SHORT;
            Toast toast = Toast.makeText(context, text, duration);
            toast.show();
        }
    @}

    [Foreign(Language.ObjC)]
    public extern(iOS) void OpenApp()
    @{    
        NSURL *url = [NSURL URLWithString:@{LaunchApp:Of(_this).Uri:Get()}];
        if([[UIApplication sharedApplication] openURL:url]) {
            NSLog(@"App opened");
        } else {
            UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"My Alert" message:@"This is an alert." preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}];

            [alert addAction:defaultAction];
            [[[UIApplication sharedApplication] keyWindow].rootViewController presentViewController:alert animated:YES completion:nil];
        }
    @}

}

A fix for this issue is included in Fuse 0.12.3, which is now available on the QA channel. It is scheduled for official release tomorrow.

https://www.fusetools.com/downloads/channel/qa

Fuse 0.12.3 is now officially released: https://www.fusetools.com/downloads