How to create a hyperlink in Flutter widget?
Asked Answered
K

16

250

I would like to create a hyperlink to display in my Flutter app.

The hyper link should be embedded in a Text or similar text views like:

The last book bought is <a href='#'>this</a>

Any hint to do this?

Kiri answered 24/4, 2017 at 8:40 Comment(1)
Hyperlink in FlutterPriggery
S
338

Just wrap an InkWell around a Text widget and supply an UrlLauncher (from the service library) to the onTap attribute. Install UrlLauncher as a Flutter package before using it below.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';


void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('UrlLauncher'),
        ),
        body: new Center(
          child: new InkWell(
              child: new Text('Open Browser'),
              onTap: () => launch('https://docs.flutter.io/flutter/services/UrlLauncher-class.html')
          ),
        ),
      ),
    );
  }
}

You can supply a style to the Text widget to make it look like a link.

Update

After looking into the issue a little I found a different solution to implement the 'in line' hyperlinks you asked for. You can use the RichText Widget with enclosed TextSpans.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('UrlLauchner'),
        ),
        body: new Center(
          child: new RichText(
            text: new TextSpan(
              children: [
                new TextSpan(
                  text: 'This is no Link, ',
                  style: new TextStyle(color: Colors.black),
                ),
                new TextSpan(
                  text: 'but this is',
                  style: new TextStyle(color: Colors.blue),
                  recognizer: new TapGestureRecognizer()
                    ..onTap = () { launch('https://docs.flutter.io/flutter/services/UrlLauncher-class.html');
                  },
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

This way you can actually highlight one word and make a hyperlink out of it ;)

Sirdar answered 25/4, 2017 at 7:35 Comment(11)
UrlLauncher is no longer part of flutter, it was moved to a plugin and API changed.Shayshaya
Also we need to add imports: import 'package:flutter/gestures.dart'; import 'package:url_launcher/url_launcher.dart';Leastways
You are not handling the lifecycle of your TapGestureRecognizer correctly. You have to call dispose() method when RichText is no longer used. See here: api.flutter.dev/flutter/painting/TextSpan/recognizer.htmlNonaligned
@AlexSemeniuk In your example they are using a StatefulWidget, in the answer above it is a StatelessWidget. Are you sure we need to dispose the the case of a StatelessWidget?Deportation
@CoreyCole StatelessWidget will not magically dispose your TapGestureRecognizer for you. In fact, using StatelessWidget in this scenario is incorrect, since you can not dispose your rsources this way. And yes, you absolutely need to call dispose() method of TapGestureRecognizer, since it runs the internal timer which needs to be stopped.Nonaligned
Thanks Alex, yes I definitely noticed memory leak problems in logcat when I tried using a StatelesWidget for thisDeportation
Simpler than RichText: new InkWell(child: new Text('linktext', style: new TextStyle(color: Colors.blue, decoration: TextDecoration.underline),), onTap: () => {})),Demirelief
Gosh, flutter changed another api and broke working code? Who'da thought...? (sigh)Robertaroberto
the launch(' ') is deprecated use launchUrl(Uri.parse(' ')) the old: ..onTap = () { launch('docs.flutter.io/flutter/services/UrlLauncher-class.html'); }, New ..onTap = () { launchUrl(Uri.parse('androidride.com')); },Antimalarial
The URL in the example gives a HTTP 404. Also, the new method seems to be launchUrlString() instead of launch().Pomander
new is not needed in Dart these days...Idyll
F
113

Flutter doesn't have built-in hyperlink support but you can fake it yourself. There's an example in the Gallery's drawer.dart. They use a RichText widget containing a coloured TextSpan, which has a recognizer attribute to handle taps:

        RichText(
          text: TextSpan(
            children: [
              TextSpan(
                style: bodyTextStyle,
                text: seeSourceFirst,
              ),
              TextSpan(
                style: bodyTextStyle.copyWith(
                  color: colorScheme.primary,
                ),
                text: repoText,
                recognizer: TapGestureRecognizer()
                  ..onTap = () async {
                    final url = 'https://github.com/flutter/gallery/';
                    if (await canLaunch(url)) {
                      await launch(
                        url,
                        forceSafariVC: false,
                      );
                    }
                  },
              ),
              TextSpan(
                style: bodyTextStyle,
                text: seeSourceSecond,
              ),
            ],
          ),

enter image description here

enter image description here

Foote answered 24/4, 2017 at 15:27 Comment(8)
Thanks. But this is not really the thing I am looking for: I am looking beyond in-app navigation.Kiri
I'm not sure what you mean by "looking beyond in-app navigation." Do you want the link to open a browser?Foote
Yes, a link or alike that can be clicked to open in a browse.Kiri
That is what the sample I linked to does. I added some pictures to the answer to show it.Foote
The repo link is brokenOrthognathous
Actual code here would be appreciated since the link no longer worksFellows
How can this work with multiple languages though? Link might be in another location for example.Kinsella
New url_launcher usage: final url = Uri.parse('github.com/flutter/gallery/'); if (!await launchUrl(url)) { throw Exception('Could not launch $url'); }Takakotakakura
C
89

Update (Android >= 11):

  • iOS configuration:

    Open info.plist file and add:

    <key>LSApplicationQueriesSchemes</key>
    <array>
      <string>https</string>
    </array>
    
  • Android configuration:

    Open AndroidManifest.xml file located in app/src/main and add the following at the root:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
    
       <!-- Add this query -->
        <queries>
            <intent>
                <action android:name="android.intent.action.VIEW" />
                <data android:scheme="https" />
            </intent>
        </queries>
    
        <application ... />
    
    </manifest>
    

Flutter code:

Simply wrap your Text in a GestureDetector or InkWell and handle click in onTap() using url_launcher package.

InkWell(
  onTap: () => launchUrl(Uri.parse('https://www.google.com')),
  child: Text(
    'Click here',
    style: TextStyle(decoration: TextDecoration.underline, color: Colors.blue),
  ),
)

Screenshot:

enter image description here

Cofferdam answered 5/2, 2019 at 17:48 Comment(4)
You are right, your solution has less lines than others', but Inkwell is the widget for this especific job, so al least semantically I think It's the best solutionAudrey
@Audrey Yes, you can use InkWell instead of GestureDetector.Cofferdam
In web browsers GestureDetector does not change cursor and does not have onHover behavior. InkWell is a better solution.Varicella
Thanks, in my case .. this is the only code that works perfectly!Casiano
P
25

You can use package flutter_linkify
https://pub.dev/packages/flutter_linkify
Just want to provide another option.
Package will divide your text and highlight http/https automatically
Combine plugin url_launcher you can launch url
You can check example below:

enter image description here

full code below

import 'package:flutter/material.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'dart:async';

import 'package:url_launcher/url_launcher.dart';

void main() => runApp(new LinkifyExample());

class LinkifyExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'flutter_linkify example',
      home: Scaffold(
        appBar: AppBar(
          title: Text('flutter_linkify example'),
        ),
        body: Center(
          child: Linkify(
            onOpen: _onOpen,
            text: "Made by https://cretezy.com \n\nMail: [email protected] \n\n  this is test http://pub.dev/ ",
          ),
        ),
      ),
    );
  }

  Future<void> _onOpen(LinkableElement link) async {
    if (await canLaunch(link.url)) {
      await launch(link.url);
    } else {
      throw 'Could not launch $link';
    }
  }
}
Pulchritudinous answered 12/7, 2019 at 5:22 Comment(5)
How can you have 2 links on same widget? Like for example "by clicking this you accept terms of use and privacy policy" where we need to have them togetherNeuritis
Awesome package. Saves a lot of boiler plate codeStilu
@Neuritis you can add as many links as you want because the flutter linkify package treats every link different. I have tested the above snippet for more than 1 link and it works perfectlyCoeternal
Thanks. That is what I was searching forShiller
The API has changed so the example above doesn't work anymore - see github.com/Cretezy/flutter_linkify/issues/112 for an example using the new canLaunchUrl and launchUrl instead.Beacon
S
13

Adding one more simple and neat trick because the above ones are over-complicated for some use cases. I did it using RichText - WidgetSpan, TextButton and URL launcher package. Just modify the below example block according to your needs.

Result: Result

Code:

class UserAgreementText extends StatelessWidget {
  const UserAgreementText({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: RichText(
            textAlign: TextAlign.center,
            text: TextSpan(
              text: 'By logging in, you accept our ',
              style: Theme.of(context).textTheme.bodySmall,
              children: const <InlineSpan>[
                WidgetSpan(
                  alignment: PlaceholderAlignment.baseline,
                  baseline: TextBaseline.alphabetic,
                  child: LinkButton(
                      urlLabel: "Terms and Conditions",
                      url: "https://example.com/terms-and-conditions"),
                ),
                TextSpan(
                  text: ' and ',
                ),
                WidgetSpan(
                  alignment: PlaceholderAlignment.baseline,
                  baseline: TextBaseline.alphabetic,
                  child: LinkButton(
                      urlLabel: "Privacy Policy",
                      url: "https://example.com/privacy-policy"),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

link_button.dart

class LinkButton extends StatelessWidget {
  const LinkButton({Key? key, required this.urlLabel, required this.url})
      : super(key: key);

  final String urlLabel;
  final String url;

  Future<void> _launchUrl(String url) async {
    final Uri uri = Uri.parse(url);

    if (!await launchUrl(uri)) {
      throw 'Could not launch $uri';
    }
  }

  @override
  Widget build(BuildContext context) {
    return TextButton(
      style: TextButton.styleFrom(
        padding: EdgeInsets.zero,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(0),
        ),
        tapTargetSize: MaterialTapTargetSize.shrinkWrap,
        visualDensity: VisualDensity.compact,
        minimumSize: const Size(0, 0),
        textStyle: Theme.of(context).textTheme.bodySmall,
      ),
      onPressed: () {
        _launchUrl(url);
      },
      child: Text(urlLabel),
    );
  }
}

Note: If you get an error related to calling launchUrl, make sure you install the URL launcher package and then rebuild your application.

Sugarcoat answered 8/9, 2022 at 19:11 Comment(1)
If you receive the error flutter Intrinsics are not available for PlaceholderAlignment.baseline then remove alignment and baseline from WidgetSpan.Carillo
M
8

You can use Link Text https://pub.dev/packages/link_text and use it like

 final String _text = 'Lorem ipsum https://flutter.dev\nhttps://pub.dev'; 
 @override
 Widget build(BuildContext context) {
 return Scaffold(
     body: Center(
      child: LinkText(
        text: _text,
        textAlign: TextAlign.center,
      ),
    ),
  );
}
Mexicali answered 8/7, 2020 at 5:25 Comment(3)
Under Linux flutter pub get for it fails with Unable to find a plugin.vcxproj for plugin "url_launcher_windows"Demirelief
This is limited only to making links clickable. not a word, pointing to a linkMcgean
If the url itself is allowed to be visible, this is hands down the easiest solution to implement instead of an existing Text widget. If you need the url not visible but as a link behind other text, this does not work as is. Bonus: it also makes AlertDialog links clickable.Manicdepressive
P
8

flutter link widget

In Flutter 2.0, the Link widget was introduced. Use this widget to launch webpages and also navigate to new screens in your app. you need to use the url_launcher package before using it.

url_launcher: ^6.0.8

For More Information

Link(
              uri: Uri.parse('https://androidride.com'),
              //target: LinkTarget.self,
              builder: (context, followLink) {
                return RichText(
                  text: TextSpan(children: [
                    TextSpan(
                      text: 'Click here: ',
                      style: TextStyle(
                        fontSize: 20,
                        color: Colors.black,
                      ),
                    ),
                    TextSpan(
                      text: 'AndroidRide',
                      style: TextStyle(
                        color: Colors.blue,
                        decoration: TextDecoration.underline,
                        fontWeight: FontWeight.bold,
                        fontSize: 21,
                      ),
                      recognizer: TapGestureRecognizer()
                        ..onTap = followLink,
                    ),
                  ]),
                );
              }),
        ),
        SizedBox(
          height: 20,
        ),
        Link(
          uri: Uri.parse('/second'),
          builder: (context, followLink) {
            return InkWell(
              onTap: followLink,
              child: Text(
                'Go to Second Screen',
                style: TextStyle(
                  fontSize: 20,
                  color: Colors.blue,
                  decoration: TextDecoration.underline,
                ),
              ),
            );
          },
        ),
Priggery answered 8/7, 2021 at 1:41 Comment(1)
To clarify, the Link widget is part of the url_launcher package, not Flutter proper.Carillo
N
7

If you want to make it look even more like a link, you can add underline:

new Text("Hello Flutter!", style: new TextStyle(color: Colors.blue, decoration: TextDecoration.underline),)

and the result:

enter image description here

Nonalignment answered 2/9, 2018 at 22:56 Comment(3)
It is not clickable!Redivivus
Then wrap it around with Button widget. :)Nonalignment
This is not the answer to the question, but a comment to somebody's answer. Also button isn't inline text hyperlink.Ackley
C
5

You could use the flutter_html plugin if you want to have more advanced text capabilities:

Html(
  data: 'The last <i><u><b>book</b></u></i> bought is <a href="#">this</a>',
  onLinkTap: (url, context, attrs, element) {
    // Handle link tapped...
  },
)

This is just the tip of the iceberg of capabilities with this plugin.

Just a thought: You could even host parts of your flutter app online as html and render them in flutter as widgets with this plugin.

Collocation answered 14/2, 2022 at 14:43 Comment(0)
B
3

As of 14 Sep 2023, Flutter has an inbuilt LinkedText widget to handle link taps: https://github.com/flutter/flutter/pull/125927

(Example copied from the PR. Checkout the PR description for more examples.)

multiple link texts

LinkedText.textLinkers(
  text: text,
  textLinkers: <TextLinker>[
    TextLinker(
      rangesFinder: TextLinker.urlRangesFinder,
      linkBuilder: InlineLinkedText.getDefaultLinkBuilder(_onTapUrl),
    ),
    TextLinker(
      rangesFinder: TextLinker.rangesFinderFromRegExp(RegExp(r'@[a-zA-Z0-9]{4,15}')),
      linkBuilder: (String linkText) {
        return InlineLink(
          text: linkText,
          style: const TextStyle(
            color: Color(0xff00aaaa),
          ),
          onTap: _onTapTwitterHandle,
        );
      },
    ),
  ],
),
Bawcock answered 14/9, 2023 at 11:2 Comment(1)
Looks like they have reverted this - github.com/flutter/flutter/pull/134955Passim
J
2

The answers to this question that suggest using RichText with a TextSpan + GestureRecognizer are all functionally correct, but from the UX perspective, they do not provide feedback to the user responding to the touch. To maintain consistency with other Material widgets, you could use a similar approach, but use a WidgetSpan + InkWell instead.

This example uses the url_launcher package and an unstyled InkWell, but you can customize as you see fit:

import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';

class TextWithLink extends StatelessWidget {
  const TextWithLink({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    const textStyle = TextStyle(color: Colors.black); // default style for all text
    return RichText(
      text: TextSpan(
        style: textStyle,
        children: [
          const TextSpan(
            text: 'The last book bought is ',
          ),
          WidgetSpan(
              alignment: PlaceholderAlignment.middle,
              child: InkWell(
                  onTap: () => _launchUrl('https://url-to-launch.com'),
                  child: Text('this',
                      style: textStyle.merge(const TextStyle(
                          color: Colors.blue, fontWeight: FontWeight.bold))))), // override default text styles with link-specific styles
        ],
      ),
    );
  }

  _launchUrl(String url) async {
    if (await canLaunch(url)) {
      await launch(url);
    } else {
      throw 'Could not launch $url';
    }
  }
}
Jhvh answered 27/1, 2022 at 19:22 Comment(0)
C
2

Here is a turn-key package https://pub.dev/packages/link_text

child: LinkText(
  'Hello check http://google.com',
  textStyle: ...,
),

Safer to use a library for this kind of complexity

Pros:

  • takes in the entire string as a single param
  • parses the links and renders them into the UI as clickable , inline with the non-clickable text
  • no complex RichText structure required
Clamorous answered 27/4, 2022 at 0:19 Comment(0)
K
2

I see a lot of verbose answers here that don't work so well.

If you want to do this from user generated content you can use https://pub.dev/packages/flutter_markdown

This works on all platforms and is highly dynamic and customisable.

Kinsella answered 13/3, 2023 at 8:48 Comment(1)
Would you provide an example on how to use it for links?Seng
O
2

Somehow I liked to create my own solution to this to see what happens so here is the result,

TextSpan linkify(BuildContext context, String rawText) {
  final spans =
      rawText.split(RegExp('(?=https?://|mailto:|tel:)')).indexed.expand((e) {
    final (i, chunk) = e;
    final index = i == 0 ? 0 : chunk.indexOf(RegExp('\\s|\\)|\$'));
    final link = chunk.substring(0, index);
    return [
      if (i != 0)
        TextSpan(
          text: link.replaceFirst(RegExp('^(mailto|tel):'), ''),
          style: Theme.of(context).textTheme.bodySmall?.copyWith(
                decoration: TextDecoration.underline,
                color: Theme.of(context).colorScheme.primary,
              ),
          recognizer: TapGestureRecognizer()
            ..onTap = () => launchUrl(Uri.parse(link)),
        ),
      TextSpan(
        text: chunk.substring(index),
        style: Theme.of(context).textTheme.bodySmall,
      ),
    ];
  });
  return TextSpan(children: [...spans]);
}

Example:

const rawText = '''Here is a sample test http://example.com (https://example.com)

Contact: mailto:[email protected] and tel:+1-555-5555-555

https://example.com''';

RichText(text: linkify(context, rawText))

output from raw code

Note the tel: and mailto: are removed, one possibly could find mail and telephone themselves but I preferred to mark them explicitly then remove them.

Outrelief answered 13/6, 2023 at 13:46 Comment(0)
V
1

An alternative (or not) way to put clickable links in your app (for me it just worked that way):

1 - Add the url_launcher package in your pubspec.yaml file

(the package version 5.0 didn't work well for me, so I'm using the 4.2.0+3).

dependencies:
  flutter:
    sdk: flutter
  url_launcher: ^4.2.0+3

2 - Import it and use as below.

import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';

void main() {
  runApp(MaterialApp(
    title: 'Navigation Basics',
    home: MyUrl(),
  ));
}

class MyUrl extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Url Launcher'),
      ),
      body: Center(
        child: FlatButton(
          onPressed: _launchURL,
          child: Text('Launch Google!',
              style: TextStyle(fontSize: 17.0)),
        ),
      ),
    );
  }

  _launchURL() async {
    const url = 'https://google.com.br';
    if (await canLaunch(url)) {
      await launch(url);
    } else {
      throw 'Could not launch $url';
    }
  }
}
Voice answered 12/3, 2019 at 19:41 Comment(1)
If need a hyperlink between some text, you can use a FlatButton with same background and text colors as the rest of your texts, so format it whith TextDecoration.underline as the bartektartanus showed above...Voice
K
1

Let's separate this question to 2 things:

  1. How to embed a link inside a line of text
  2. How to launch a url in an external browser/window

For #2: As posted here - use url_launcher

For #1: I think using RichText is an overkill for this, I simply used Text's and TextButton, in a Wrap container like so:

Wrap(
  alignment: WrapAlignment.center,
  runAlignment: WrapAlignment.center,
  crossAxisAlignment: WrapCrossAlignment.center,
  children: [
    const Text('The last book bought is'),
    TextButton(
      onPressed: () {
        launchUrl(Uri.parse('https://someurl'), mode: LaunchMode.externalApplication);
      },
      child: const Text('this')),
  ],
)
Kohinoor answered 25/2, 2023 at 13:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.