Nikcio
Nikcio

Reputation: 457

Flutter url_launcher Unhandled Exception: Could not launch youtube url (Caused by canLaunch)

I'm trying to use the url_launcher plugin to open youtube videos by link but the canLaunch function keeps throwing an error. I'm able to bypass this error only by completely removing the canLaunch function but can't figure out what is wrong.

Code not working:

_goToVideo(YoutubeVideoData video) async {
  if (await canLaunch(video.url)) {
    await launch(video.url);
  } else {
    throw 'Could not launch ${video.url}';
  }
}

Code working:

_goToVideo(YoutubeVideoData video) async {
  await launch(video.url);
}

I'm not quite sure why I can't use the canLaunch method as written in the README Example

Error:

E/flutter (12574): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Could not launch https://www.youtube.com/watch?v=-3g5WlqJtIo
E/flutter (12574): #0      _goToVideo (package:esfandapp/widgets/newsList/videoCard.dart:71:5)
E/flutter (12574): <asynchronous suspension>
E/flutter (12574): #1      VideoCard.build.<anonymous closure> (package:esfandapp/widgets/newsList/videoCard.dart:13:20)
E/flutter (12574): #2      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:992:19)
E/flutter (12574): #3      _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:1098:38)
E/flutter (12574): #4      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:184:24)
E/flutter (12574): #5      TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:524:11)
E/flutter (12574): #6      BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:284:5)
E/flutter (12574): #7      BaseTapGestureRecognizer.acceptGesture (package:flutter/src/gestures/tap.dart:256:7)
E/flutter (12574): #8      GestureArenaManager.sweep (package:flutter/src/gestures/arena.dart:158:27)
E/flutter (12574): #9      GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:224:20)
E/flutter (12574): #10     GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:200:22)
E/flutter (12574): #11     GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:158:7)
E/flutter (12574): #12     GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:104:7)
E/flutter (12574): #13     GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:88:7)
E/flutter (12574): #14     _rootRunUnary (dart:async/zone.dart:1206:13)
E/flutter (12574): #15     _CustomZone.runUnary (dart:async/zone.dart:1100:19)
E/flutter (12574): #16     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1005:7)
E/flutter (12574): #17     _invoke1 (dart:ui/hooks.dart:267:10)
E/flutter (12574): #18     _dispatchPointerDataPacket (dart:ui/hooks.dart:176:5)

Widget using the function:

class VideoCard extends StatelessWidget {
  final YoutubeVideoData video;
  VideoCard({this.video});

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () => _goToVideo(video),
      child: Container(
        child: Card(
          child: Container(
            child: Column(
              children: [
                Align(
                  child: Padding(
                    child: Text(
                      video.title,
                      style: TextStyle(
                        fontFamily: 'Roboto Condensed',
                        fontSize: 16,
                      ),
                    ),
                    padding: EdgeInsets.fromLTRB(15, 0, 15, 10),
                  ),
                  alignment: Alignment.centerLeft,
                ),
                Container(
                    child: Image.network(video.thumbnails[1], fit: BoxFit.cover,),
                  width: MediaQuery.of(context).size.width,
                ),
                Align(
                  child: Container(
                    child: Text(
                      video.date.toString() + "",
                      style: TextStyle(
                        fontFamily: 'Roboto Condensed',
                        fontSize: 14,
                        fontWeight: FontWeight.w300,
                      ),
                    ),
                    padding: EdgeInsets.fromLTRB(15, 5, 15, 0),
                  ),
                  alignment: Alignment.centerLeft,
                ),
              ],
            ),
            width: MediaQuery.of(context).size.width - 32,
            padding: EdgeInsets.symmetric(
              horizontal: 0,
              vertical: 10,
            ),
            alignment: Alignment.center,
          ),
          shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.all(Radius.circular(25))),
        ),
      ),
    );
  }
}

Upvotes: 35

Views: 38488

Answers (14)

I encountered a similar issue where my app was rejected on the Google Play Store due to the inclusion of the <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>

permission in the manifest.

Here's how I resolved it:

  1. Remove the QUERY_ALL_PACKAGES permission from the Android manifest to comply with Google's Play Store policies.
  2. Downgrade the URL_launcher package to version 6.0.0, which aligns with the required permissions.
  3. Additionally, you can update your code like this to avoid potential issues:
 if (await launchUrl(Uri.parse(url))) {
                  await launch(url);
                }
                else {
                  throw 'Could not launch $url';
                }

This resolved the rejection issue and ensured my app functioned properly. Hope this helps others facing a similar challenge!

Upvotes: -1

Anandh Krishnan
Anandh Krishnan

Reputation: 6022

QUERY_ALL_PACKAGES (not recommended)

Because Google could stop letting people use it in the future as mentioned in the official document

if you use launch any url from your app just use

<manifest package="com.example.game">
    <queries>
        <package android:name="com.android.chrome" />
        
    </queries>
    ...
</manifest>

likewise you can add <queries> depends on your need.

ex.

<queries>
        <!-- If your app opens https URLs -->
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="https" />
        </intent>
        <!-- If your app makes calls -->
        <intent>
            <action android:name="android.intent.action.DIAL" />
            <data android:scheme="tel" />
        </intent>
        <!-- If your app emails -->
        <intent>
            <action android:name="android.intent.action.SEND" />
            <data android:mimeType="*/*" />
        </intent>
    </queries>

Upvotes: 0

Anandh Krishnan
Anandh Krishnan

Reputation: 6022

For Android Just add in your androidmanifest.xml file

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>

For iOS add these in your info.plist file

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>https</string>
    <string>http</string>
    <string>tel</string>
    <string>mailto</string>
</array> 

Upvotes: 0

 if (await canLaunchUrl(Uri.parse(url))) {
      await launchUrl(Uri.parse(url),
      mode: LaunchMode.externalApplication, );
    } 

Upvotes: 0

Imran Sefat
Imran Sefat

Reputation: 773

Make sure to add in the android manifest the below line

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>

Don't forget about the Info.plist as well

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>https</string>
    <string>http</string>
    <string>tel</string>
    <string>mailto</string>
</array> 

Upvotes: 3

Sangeetha Sakthivel
Sangeetha Sakthivel

Reputation: 439

I spent nearly 2 hours to find what is wrong and after I closed the application from the recent tab and re-run the app it worked perfectly fine.

Things you might missed,

  1. adding Url_launcher dependency in pubspec.yaml
  2. Adding queries in Android manifest file after tag

<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" />
    </intent>
</queries>

  1. Adding keys in the info.plist file under ios directory

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>https</string>
    <string>http</string>
</array> 

  1. Just try to uninstall app and run the code

Upvotes: 2

For Android Use the below code in Android/main/res/AndroidManifest.xml like the showed in the image

enter image description here

and here is the code

<intent>
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="https" />
</intent>
<intent>
    <action android:name="android.intent.action.DIAL" />
    <data android:scheme="tel" />
</intent>
<intent>
    <action android:name="android.intent.action.SEND" />
    <data android:mimeType="*/*" />
</intent>

For IPhone ios/Runner/Info.plist like the showed in the image

enter image description here

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>https</string>
    <string>http</string>
    <string>tel</string>
    <string>mailto</string>

Upvotes: 4

Gojko Galonja
Gojko Galonja

Reputation: 71

If none of the above works, try adding this in the AndroidManifest.xml (main one if you have that). Add this code snipped just above the <application> tag.

<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" />
    </intent>
</queries>

Upvotes: 0

almamon Rasool Abd Ali
almamon Rasool Abd Ali

Reputation: 685

i faced same error using win and linux for android developments and solve it with these steps the idea is to add the to AndroidManifest.xml in app/src/main/AndroidManifest.xml to do so and to make it work without error you must update the gardle version so here is the steps 1-updated my gradle-wrapper.properties to gradle-6.7.1-all.zip

distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip

2- updated my classpath in android/build.gradle to com.android.tools.build:gradle:4.2.1

buildscript {
    ext.kotlin_version = '1.3.50'
    repositories {
        google()
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:4.2.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

rootProject.buildDir = '../build'
subprojects {
    project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
    project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

3- add the above to my AndroidManifest.xml in app/src/main/AndroidManifest.xml

   <queries>
        <!-- If your app opens https URLs -->
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="https" />
        </intent>
        <!-- If your app makes calls -->
        <intent>
            <action android:name="android.intent.action.DIAL" />
            <data android:scheme="tel" />
        </intent>
        <!-- If your app emails -->
        <intent>
            <action android:name="android.intent.action.SEND" />
            <data android:mimeType="*/*" />
        </intent>
    </queries>

and now everything work fine

Upvotes: 0

Vasudev dadhich
Vasudev dadhich

Reputation: 71

bro only put '!' before "if (!await canLaunch(url))" Use this -->

 if (!await canLaunch(url)){
  await launch(
    url,
    forceSafariVC: false,
    forceWebView: false,
    headers: <String, String>{'my_header_key': 'my_header_value'},
  );
} else {
  
  throw 'Could not launch $url';
}

Upvotes: 7

Pratik Butani
Pratik Butani

Reputation: 62429

Even after trying accepted answer, If its not working for you then Please try followed code.

PRE STEPS: Do as accepted answer suggested.

Add following tags in AndroidManifest.xml before <application/>

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>

<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="https" />
    </intent>
    <intent>
        <action android:name="android.intent.action.DIAL" />
        <data android:scheme="tel" />
    </intent>
    <intent>
        <action android:name="android.intent.action.SEND" />
        <data android:mimeType="*/*" />
    </intent>
</queries>

NOW WHAT I HAVE DONE

Create one method:

Future<void> _makeSocialMediaRequest(String url) async {
    if (await canLaunch(url)) {
      await launch(url);
    } else {
      throw 'Could not launch $url';
    }
  }

and Called it by following way:

  //FOR EMAIL
  final Uri _emailLaunchUri = Uri(
      scheme: 'mailto',
      path: '[email protected]',
      queryParameters: {'subject': 'Pratik Butani'});
  _makeSocialMediaRequest(_emailLaunchUri.toString());

  //FOR PHONE NUMBER:
  final Uri _phoneLaunchUri =
      Uri(scheme: 'tel', path: postOffice.mobileNo);
  _makeSocialMediaRequest(_phoneLaunchUri.toString());

  //FOR ANY URL.. YOU CAN PASS DIRECT URL..
  _makeSocialMediaRequest("http://pratikbutani.com");

Its working for me. Hopefully it will work for you too. Thank you.

Upvotes: 10

Wes1324
Wes1324

Reputation: 1123

Personally, I don't like the uncertainty that seems to come with using the QUERY_ALL_PACKAGES permission (because Google could stop letting people use it in the future). For that reason, I did some investigation and found that adding the following to my AndroidManifest.xml allows my app to open the browser, phone app and email app on API 30:

<manifest>

    <!-- Nest within the manifest element, not the application element-->
    <queries>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="https" />
        </intent>
        <intent>
            <action android:name="android.intent.action.DIAL" />
            <data android:scheme="tel" />
        </intent>
        <intent>
            <action android:name="android.intent.action.SEND" />
            <data android:mimeType="*/*" />
        </intent>
    </queries>

    <application>
        ....
    </application>
</manifest>

To get the same functionality working on iOS, it may be necessary to add the following to your info.plist file:

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>https</string>
    <string>http</string>
    <string>tel</string>
    <string>mailto</string>
</array> 

Just wanted to share in case it helps someone else out.

Upvotes: 75

mohmdezeldeen
mohmdezeldeen

Reputation: 449

This worked for me

if (!url.contains('http')) url = 'https://$url';

Complete Method:

launchURL(String url) async {
  if (!url.contains('http')) url = 'https://$url';
  if (await canLaunch(url)) {
    await launch(url);
  } else {
    throw 'Could not launch $url';
  }
}

Upvotes: -4

Johny B
Johny B

Reputation: 1064

Starting from API30 (Android 11), your Android app has to list all apps it interacts with.

You can add:

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>

in your android manifest to bypass it or specifically list them.

For more info: https://developer.android.com/about/versions/11/privacy/package-visibility

Upvotes: 80

Related Questions