20

Is there an easy way in Flutter to 'linkify' a text that might contain a mix of plain text, emails and web URLs? E.g. if my text is My phone number is 099 123 45 67 and my email is test@test.com the phone number and the email would be rendered as clickable links.

In Android it would be a one liner:

textView.setAutoLinkMask(Linkify.ALL);

I've seen that a similar question has been asked here. That solution would work fine for static texts, but for dynamic texts it would be a lot more complicated to parse the text, detect all URLs, phone numbers, emails etc. and use TextSpans to render them accordingly.

Egemen
  • 2,178
  • 5
  • 22
  • 32
  • I don't think so. Maybe you can copy the link parser from the android source code? – boformer Aug 23 '18 at 17:35
  • 2
    I also couldn't find any. I will probably use `RegExp` to get matches for phone numbers, emails and URLs, and use `TextSpan` to render them properly. Just wanted to know if there was some library I was missing before re-inventing the wheel. – Egemen Aug 24 '18 at 06:28
  • [Flutter Hyperlink example](https://androidride.com/flutter-hyperlink-text/) – Vijay Ram Jul 08 '21 at 01:43

6 Answers6

20

I created a new package for this: flutter_linkify. It currently only supports URLS, but you can always submit an issue on GitHub with feature requests.

Baisc usage:

import 'package:flutter_linkify/flutter_linkify.dart';

Linkify(
  onOpen: (url) => print("Clicked $url!"),
  text: "Made by https://cretezy.com",
);
Charles Crete
  • 944
  • 7
  • 14
9

Thank you @Charles Crete for creating that library.

I just want to add one more solution here by combining RichText, TextSpan and TapGestureRecognizer (all are in Flutter framework)

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

void main() {
  runApp(HyperLinkDemo());
}

class HyperLinkDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: RichText(
                text: TextSpan(children: [
              TextSpan(
                  text: 'This is a very long text, but you can not click on it. ',
                  style: TextStyle(fontSize: 20, color: Colors.black)),
              TextSpan(
                  text: 'And this is a clickable text',
                  style: TextStyle(
                      fontSize: 20,
                      color: Colors.blue,
                      decoration: TextDecoration.underline),
                  recognizer: TapGestureRecognizer()
                    ..onTap = () {
                      print('You clicked on me!');
                    })
            ])),
          ),
        ),
      ),
    );
  }
}

Here is the result

enter image description here

Phuc Tran
  • 7,555
  • 1
  • 39
  • 27
  • 4
    I believe the OP is asking about code to automatically detect phone numbers, addresses, or links, not just hardcoding some text as 'clickable.' – gannonbarnett Apr 20 '19 at 15:13
  • You may be correct. I just wanted to provide an additional method, so someone could use it. Anyway, why vote down? :) – Phuc Tran Apr 21 '19 at 03:16
  • My vote down is for not handling life cycle of `TapGestureRecognizer` correctly (you are not alone in this). You have to explicitly call `dispose()` method as is stated in the docs: https://api.flutter.dev/flutter/painting/TextSpan/recognizer.html – Alex Semeniuk Aug 20 '19 at 15:09
  • how can we do it in Dart only, not in Flutter? Expected output: `Made by https://cretezy.com`. Thanks a lot. – Kamlesh Aug 14 '21 at 10:50
8

Here is how I implemented it - use the buildTextWithLinks function to get a Text component with links.

It uses url_launcher and currnetly supports URL, mail and phone links, but can easily be expanded by adding more RegExps and handlers.

import 'package:url_launcher/url_launcher.dart';

Text buildTextWithLinks(String textToLink) => Text.rich(TextSpan(children: linkify(textToLink)));

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

const String urlPattern = r'https?:/\/\\S+';
const String emailPattern = r'\S+@\S+';
const String phonePattern = r'[\d-]{9,}';
final RegExp linkRegExp = RegExp('($urlPattern)|($emailPattern)|($phonePattern)', caseSensitive: false);

WidgetSpan buildLinkComponent(String text, String linkToOpen) => WidgetSpan(
    child: InkWell(
      child: Text(
        text,
        style: TextStyle(
          color: Colors.blueAccent,
          decoration: TextDecoration.underline,
        ),
      ),
      onTap: () => openUrl(linkToOpen),
    )
);

List<InlineSpan> linkify(String text) {
  final List<InlineSpan> list = <InlineSpan>[];
  final RegExpMatch match = linkRegExp.firstMatch(text);
  if (match == null) {
    list.add(TextSpan(text: text));
    return list;
  }

  if (match.start > 0) {
    list.add(TextSpan(text: text.substring(0, match.start)));
  }

  final String linkText = match.group(0);
  if (linkText.contains(RegExp(urlPattern, caseSensitive: false))) {
    list.add(buildLinkComponent(linkText, linkText));
  }
  else if (linkText.contains(RegExp(emailPattern, caseSensitive: false))) {
    list.add(buildLinkComponent(linkText, 'mailto:$linkText'));
  }
  else if (linkText.contains(RegExp(phonePattern, caseSensitive: false))) {
    list.add(buildLinkComponent(linkText, 'tel:$linkText'));
  } else {
    throw 'Unexpected match: $linkText';
  }

  list.addAll(linkify(text.substring(match.start + linkText.length)));

  return list;
}
splintor
  • 9,924
  • 6
  • 74
  • 89
  • 2
    Great! thanks for this answer. You save our one more dependency. Just one thing here is that `urlPattern` wasn't working by copying this code, though it worked when I changed it to `const String urlPattern = 'https?:/\/\\S+';` – Smit Thakkar Oct 23 '19 at 16:00
  • @Smit Not sure what you changed. Removed the raw prefix (`r`)? – splintor Oct 26 '19 at 21:59
  • Yes. that's correct. Can you please explain what is the significance of `r` ? – Smit Thakkar Oct 27 '19 at 21:54
  • The `r` prefix indicates a [raw string](https://dart.dev/guides/language/language-tour#strings). When defining a [RegExp](https://api.dartlang.org/stable/2.6.0/dart-core/RegExp-class.html) it is usually a good idea to use raw strings, so special RegExp charachters are treated properly, and not as a string escape sequnces. – splintor Oct 29 '19 at 15:20
3

This function can auto detect what is hyper link in textview. You can modify Regx pattern depend on your requirements.

List<TextSpan> extractText(String rawString) {
List<TextSpan> textSpan = [];

final urlRegExp = new RegExp(
    r"((https?:www\.)|(https?:\/\/)|(www\.))[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9]{1,6}(\/[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)?");

getLink(String linkString) {
  textSpan.add(
    TextSpan(
      text: linkString,
      style: new TextStyle(color: Colors.blue),
      recognizer: new TapGestureRecognizer()
        ..onTap = () {
          Fluttertoast.showToast(msg: linkString);
        },
    ),
  );
  return linkString;
}

getNormalText(String normalText) {
  textSpan.add(
    TextSpan(
      text: normalText,
      style: new TextStyle(color: Colors.black),
    ),
  );
  return normalText;
}

rawString.splitMapJoin(
  urlRegExp,
  onMatch: (m) => getLink("${m.group(0)}"),
  onNonMatch: (n) => getNormalText("${n.substring(0)}"),
);

return textSpan;}

Usages

child: SelectableText.rich(
                TextSpan(
                    children: extractText(dummyText),
                    style: TextStyle(fontSize: _fontSize)),
              )

Here is result

Wai Han Ko
  • 760
  • 5
  • 16
2

Check this library flutter_autolink_text. Is very similar to flutter_linkify but it adds support to phone number as well.

This is how to use it.

import 'package:flutter_autolink_text/flutter_autolink_text.dart';

AutolinkText(
    text: ...,
    textStyle: TextStyle(color: Colors.black),
    linkStyle: TextStyle(color: Colors.blue),
    onWebLinkTap: (link) => print('Clicked: ${link}'),
    onEmailTap: (link) => print('Clicked: ${link}'),
    onPhoneTap: (link) => print('Clicked: ${link}')
);
velval
  • 3,072
  • 36
  • 45
0

This plugin named linker (https://github.com/best-flutter/linker/blob/master/example/lib/main.dart)

is going to help you.

You can open phone number to call

you can open other system apps or system settings

You can open any 3rd party apps using this package..

you can open email

This package is available in pub.dev ...Search Linker to get it

raman raman
  • 1,655
  • 4
  • 22
  • 34