Search code examples
flutterdartwidget

Flutter: require URL parameter in LaunchURL widget to make it reusable


I currently have a widget that I use to open a specific webpage using the LaunchUrl feature from the URL_Launcher package.

Throughout my app there are several instances where I'd like to open a webpage using this exact method, but I suppose it's not very efficient to copy-paste the widget I created for this over and over again, and simply replacing the URL.

This is the widget I have written, which is working. But this one is opening a specific URL.

Future<void> openHCReservationPortal(String url) async {
  final url = Uri.parse('https://portal.mijnhandicart.nl/site/login');
  if (!await launchUrl(url, mode: LaunchMode.platformDefault)) {
    throw Exception('Kon $url niet openen');
  }
}

I'd like to have the url be a parameter, so I can reuse the widget.

I have tried to create a statefull widget with a required parameter but got all sorts of errors, so I must be missing something or doing something wrong.

This is what I wrote:

class OpenWebPageWidget extends StatefulWidget {
  final String url;
  const OpenWebPageWidget({super.key, required this.url});

  @override
  State<OpenWebPageWidget> createState() => _OpenWebPageWidgetState();
}

class _OpenWebPageWidgetState extends State<OpenWebPageWidget> {
  @override
  Widget build(BuildContext context) {
    Future<void> openWebPageWidget(String url) async {
      final url = Uri.parse(url);
      if (!await launchUrl(url, mode: LaunchMode.platformDefault)) {
        throw Exception('Kon $url niet openen');
      }
    }
  }
}

Flutter is complaining that:

  • The body might complete normally, causing 'null' to be returned, but the return type, 'Widget', is a potentially non-nullable type. Try adding either a return or a throw statement at the end.
  • The declaration 'openWebPageWidget' isn't referenced.

Solution

  • You can easily pass the URL as a string to the simple function as below:

    Future<void> launchMyURL(String url) async {
      if (url.isEmpty) {
        throw 'Given URL is empty';
      }
    
      final Uri uri = Uri.parse(url);
    
      if (await canLaunchUrl(uri)) {
        await launchUrl(uri);
      } else {
        throw 'Could not launch $url';
      }
    }
    

    Then you can call the function to launch the URL from anywhere you like, e.g. from an elevated button:

    ElevatedButton(
      onPressed: () async {
        try {
          await launchMyURL("https://www.google.com/");
        } catch (e) {
          print("#### Caught exception: ${e.toString()}");
        }
      },
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.blue,
        textStyle: const TextStyle(fontSize: 16.0),
      ),
      child: const Text(
        "Click to launch URL",
        style: TextStyle(
          color: Colors.white,
          fontWeight: FontWeight.normal,
          fontSize: 16.0,
        ),
      ),
    ),
    

    Currently in your code, your build method has a Widget return type, but you are not returning any widget from the method, and only calling a function of Future<void> type, so the warning.

    And, your method openHCReservationPortal can be slightly modified to achieve what you need. Here is the modified version:

    Future<void> openHCReservationPortal(String url) async {
      final urlLaunch = Uri.parse('https://portal.mijnhandicart.nl/site/login');
      if (!await launchUrl(urlLaunch, mode: LaunchMode.platformDefault)) {
        throw Exception('Kon $url niet openen');
      }
    }
    

    You can modify your method with the above changes and also include the check with canLaunchUrl instead of directly calling launchUrl.