Yusuf Ahmed Software engineer, technical writer, vibes.

Launching URLs in Flutter with url_launcher

5 min read 1439

The URL Launcher is a Flutter plugin that allows your applications to launch web browsers, map applications, dialer applications, mail applications, and so on. The URL Launcher plugin works by creating intents to open applications using different URL schemes.

In this article, we’ll build a simple project to demonstrate how the URL Launcher plugin can be used to open a web browser and dialer, mail, and map apps.

Prerequisites

You must have the following set up before you can complete this tutorial:

This tutorial was verified with Flutter v2.5.1 and Android Studio v3.5.

So, with everything out of the way, let’s get started.

Project setup

Create a new Flutter project by running the following command in your terminal:

$ flutter create url_launcher_example

Next, we need to add the url_launcher plugin as a dependency to our project. You can do that by running the following command in your terminal:

$ flutter pub add url_launcher

The url_launcher plugin will be added as a dependency to our pubspec.yaml file as a result.

Next, we need to build the UI of our application. Copy and paste the code below in your main.dart file.

import 'package:flutter/material.dart';
import 'package:url_launcher_example/custom_button.dart';
import 'package:url_launcher/url_launcher.dart';
void main() {
  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}
class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: const Color(0xFF3E1F92),
        body: SafeArea(
          child: Padding(
            padding: const EdgeInsets.all(20.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text('Get in touch',
                    style: TextStyle(
                        color: Colors.white,
                        fontSize: 30.0,
                        fontWeight: FontWeight.bold)),
                const SizedBox(height: 10.0),
                const Text(
                    "We'd love to hear from you. Our friendly team is always here to chat.",
                    style: TextStyle(
                        color: Color(0xFFA895D1),
                        fontSize: 15.0,
                        fontWeight: FontWeight.normal)),
                const SizedBox(height: 30.0),
                TextButton(
                  onPressed: () {
                    //logic  goes here
                  },
                  style: TextButton.styleFrom(
                    padding: const EdgeInsets.all(15),
                  ),
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: const [
                      Icon(Icons.phone, color: Color(0xFFED92A2)),
                      SizedBox(width: 20.0),
                      Text('+0123 4567 8910',
                          style: TextStyle(
                              color: Color(0xFFA294C2),
                              fontSize: 16.0,
                              fontWeight: FontWeight.w500)),
                    ],
                  ),
                ),
                TextButton(
                  onPressed: () {
                    //logic  goes here
                  },
                  style: TextButton.styleFrom(
                    padding: const EdgeInsets.all(15),
                  ),
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: const [
                      Icon(Icons.sms, color: Color(0xFFED92A2)),
                      SizedBox(width: 20.0),
                      Text('+0123 4567 8910',
                          style: TextStyle(
                              color: Color(0xFFA294C2),
                              fontSize: 16.0,
                              fontWeight: FontWeight.w500)),
                    ],
                  ),
                ),
                TextButton(
                  onPressed: () {
                    //logic  goes here
                  },
                  style: TextButton.styleFrom(
                    padding: const EdgeInsets.all(15),
                  ),
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: const [
                      Icon(Icons.mail, color: Color(0xFFED92A2)),
                      SizedBox(width: 20.0),
                      Text('[email protected]',
                          style: TextStyle(
                              color: Color(0xFFA294C2),
                              fontSize: 16.0,
                              fontWeight: FontWeight.w500)),
                    ],
                  ),
                ),
                TextButton(
                  onPressed: () {
                    //logic  goes here
                  },
                  style: TextButton.styleFrom(
                    padding: const EdgeInsets.all(15),
                  ),
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: const [
                      Icon(Icons.location_pin, color: Color(0xFFED92A2)),
                      SizedBox(width: 20.0),
                      Text('87 Summer St., Boston, MA 02110',
                          style: TextStyle(
                              color: Color(0xFFA294C2),
                              fontSize: 16.0,
                              fontWeight: FontWeight.w500)),
                    ],
                  ),
                ),
                TextButton(
                  onPressed: () {
                    //logic  goes here
                  },
                  style: TextButton.styleFrom(
                    padding: const EdgeInsets.all(15),
                  ),
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: const [
                      Icon(Icons.language, color: Color(0xFFED92A2)),
                      SizedBox(width: 20.0),
                      Text('blog.logrocket.com',
                          style: TextStyle(
                              color: Color(0xFFA294C2),
                              fontSize: 16.0,
                              fontWeight: FontWeight.w500)),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ));
  }
}

Now that we have created a basic UI for our application, we can run the code on either an emulator or a physical device. When the execution is complete, your device or emulator should display a screen similar to this:



Our basic UI loaded in our device or emulator

URL Launcher async functions

The URL Launcher plugin provides two asynchronous functions: canLaunch and launch. The canLaunch function returns a boolean value that indicates whether or not a device can handle a certain URL scheme. For example, if a device does not have an email app installed, it will be unable to launch a URL using the mailto scheme.

The launch function, on the other hand, requires a String as an argument that serves as the URL; it parses the given URL string and passes it to the underlying platform for processing. The launch function also has other named optional parameters that can be used to change specific settings on both Android and iOS platforms, some of which are as follows:

  • Android-only settings:
    • forceWebView – If set to null or false, the URL is opened in the device’s default browser; otherwise, the URL is launched in a WebView
    • enableJavaScript – If set to true, JavaScript is enabled in WebView
    • enableDomStorage – When the value is set to true, WebView enables DOM storage
  • iOS-only settings:
    • forceSafariVC – If set to true, it opens the URL in the Safari View Controller; otherwise, it uses the device’s default handler
    • statusBarBrightness – collects an enum value which can be either Brightness.dark or Brightness.light to set the status bar brightness of the application after opening a link on iOS devices

Now that we understand how the URL Launcher plugin works and what it provides, let’s look at some examples of how we can implement it in our application.

Launching a webpage with URL Launcher

The code below is quite self-explanatory; notice that we use the canLaunch function to check whether the device can launch a particular URL scheme before invoking the launch function.

TextButton(
  onPressed: () async {
      const url = 'https://blog.logrocket.com';
      if(await canLaunch(url)){
        await launch(url);
      }else {
        throw 'Could not launch $url';
      }
    },
    child: const CustomWidget(
      icon: Icons.language,
      label: 'Open a URL',
    ),
),

Run the code on your device and tap the Open a URL card in our original UI to launch the webpage.

When you launch a URL using the Open a URL card, it opens in a new browser

In a case where we want the browser to be embedded in our application, we set forceWebView to be true.

TextButton(
   onPressed: () async {
      const url = 'https://blog.logrocket.com';
      if(await canLaunch(url)){
        await launch(url, forceWebView = true);  //forceWebView is true now
      }else {
        throw 'Could not launch $url';
      }
    },
...//

Set forceWebView to be true in order to embed the browser in the app

We may choose to disable JavaScript on a website in order to increase our browsing speed and online activity; however, this comes with a disadvantage in that you may be unable to access certain features on a website if we set enableJavaScript to false. Consider Twitter’s official website, which is heavily reliant on JavaScript:

TextButton(
   onPressed: () async {
      const url = 'https://www.twitter.com';  //Twitter's URL
      if(await canLaunch(url)){
        await launch(
          url, 
          forceWebView = true,       //enables WebView
          enableJavaScript = false  //disables JavaScript
        );  
      }else {
        throw 'Could not launch $url';
      }
    },
...//

Opening a URL with JavaScript disabled will speed things up, but may also disable functionality on our destination webpage

With JavaScript enabled enableJavaScript = true, we get the following:

Same URL launched with JavaScript enabled

Launching a telephone dialer app

To initiate a phone call, we use the tel: URL scheme followed by a telephone number, as shown in the code below.

TextButton(
  onPressed: () async {
    String telephoneNumber = '+2347012345678';
    String telephoneUrl = "tel:$telephoneNumber";
    if (await canLaunch(telephoneUrl)) {
      await launch(telephoneUrl);
    } else {
      throw "Error occured trying to call that number.";
    }
    child: const CustomWidget(
      icon: Icons.call,
      label: 'Call a phone\nnumber',
    ),
),

Launching the telephone dialer

Launching a text message app

To send an SMS message, we use the sms: URL scheme and a similar implementation as above.

TextButton(
  onPressed: () async {
    String telephoneNumber = '+2347012345678';
    String smsUrl = "sms:$telephoneNumber";
    if (await canLaunch(smsUrl)) {
        await launch(smsUrl);
    } else {
        throw "Error occured trying to send a message that number.";
    }
    child: const CustomWidget(
      icon: Icons.textsms,
      label: 'Send an SMS',
    ),
),

Launch and send an SMS message

Launching a mail app

To send an email, we need to pass in the recipient’s email address, a subject line, the body of our email, and the mailto: URL scheme to the emailUrl widget in addition to the mailto: URL scheme. See below:

TextButton(
    onPressed: () async {
    String email = '[email protected]';
    String subject = 'This is a test email';
    String body = 'This is a test email body';   

    String emailUrl = "mailto:$email?subject=$subject&body=$body";

    if (await canLaunch(emailUrl)) {
      await launch(emailUrl);
    } else {
      throw "Error occured sending an email";
    }
  },
  child: const CustomWidget(
    icon: Icons.forward_to_inbox,
    label: 'Send an email',
  ),
),

Launching an email app

Launching maps

To view a location on a map, we need to pass the location’s latitude and longitude values to the mapUrl along with the geo: URL scheme.

TextButton(
  onPressed: () async {
    const String lat = "42.3540";
    const String lng = "71.0586";
    const String mapUrl = "geo:$lat,$lng";
    if (await canLaunch(mapUrl)) {
      await launch(mapUrl);
    } else {
      throw "Couldn't launch Map";
    }
  },
  child: const CustomWidget(
    icon: Icons.near_me,
    label: 'Check location',
  ),
),

Launching Google Maps

Conclusion

The URL Launcher plugin is incredibly handy when you need to interface with other applications from your application. We’ve learned what the URL Launcher plugin’s use-cases are and how to implement the plugin to fit each use-case.


More great articles from LogRocket:


The complete code of this project is available on GitHub. Thank you for reading.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Yusuf Ahmed Software engineer, technical writer, vibes.

Leave a Reply