Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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))),
        ),
      ),
    );
  }
}
like image 898
Nikcio Avatar asked Aug 27 '20 22:08

Nikcio


2 Answers

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

like image 164
Johny B Avatar answered Sep 30 '22 07:09

Johny B


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.

like image 29
Wes1324 Avatar answered Sep 30 '22 07:09

Wes1324