Reputation: 4013
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?
Upvotes: 258
Views: 300027
Reputation: 10727
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.)
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,
);
},
),
],
),
Upvotes: 3
Reputation: 11258
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))
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.
Upvotes: 3
Reputation: 7424
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.
Upvotes: 2
Reputation: 473
Let's separate this question to 2 things:
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')),
],
)
Upvotes: 1
Reputation: 1120
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.
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.
Upvotes: 15
Reputation: 268314
Open info.plist
file and add:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
</array>
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>
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),
),
)
Upvotes: 93
Reputation: 12093
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:
Upvotes: 2
Reputation: 9062
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.
Upvotes: 5
Reputation: 1217
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';
}
}
}
Upvotes: 2
Reputation: 116828
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,
),
],
),
Upvotes: 116
Reputation: 7978
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.
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 ;)
Upvotes: 354
Reputation: 405
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
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,
),
),
);
},
),
Upvotes: 9
Reputation: 91
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,
),
),
);
}
Upvotes: 8
Reputation: 54407
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:
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';
}
}
}
Upvotes: 25
Reputation: 8155
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';
}
}
}
Upvotes: 1
Reputation: 16080
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:
Upvotes: 7