tl;dr
Hyperlinks can be quite easily integrated in Flutter apps with the help of certain packages:
- Use url_launcher to react on users tapping on links and launch a browser
- Use RichText if you have a static text containing links
- Use flutter_linkify for dynamic links (like in chat messages or other user generated content)
In the web context, creating links is as easy as utilizing an <a>
tag.
As hyperlinks are an integral part of the web platform, this is one of the first things you learn to implement as a developer and to make use of as a user.
Flutter foremost being a UI framework is not so much connected to hyperlinks. At least outside of web views.
Let’s discuss how to create hyperlinks inside of widgets.
Types of embedding
There are different ways in which a Flutter widget can be linked to a website. One way is to make a whole widget like InkWell or TextButton a link. This is something you see more often in apps because the finger needs bigger areas to interact than mouse cursors.
Another way that is mostly seen on websites is an inline link. A word or phrase that is linked inside a text paragraph - often with its characteristic styling: blue and underlined.
Let’s have a look at how we can achieve both ways of linking.
Standalone
The “standalone” approach is to provide a weblink to an app user is by creating a button that contains a Text
widget whose style mimics the well-known hyperlink style.
The whole buttons is linked to a website and tapping the button connects the user via the default browser to that website.
1InkWell(
2 child: const Padding(
3 padding: EdgeInsets.all(16.0),
4 child: Text(
5 'Open Browser',
6 style: TextStyle(
7 color: Colors.blue,
8 decoration: TextDecoration.underline,
9 ),
10 ),
11 ),
12 onTap: () => launchUrlString('https://www.example.com'),
13);
Note that we are using the url_launcher package to launch the browser. We are going to talk about this in detail in the chapter Was of launching.
This is how it looks like:
Inline texts
Sometimes you don’t want the whole text to be a link. Instead, you want the link to be embedded inline like an tag in the web within a paragraph.
We can either do a static or a dynamic linking. Static linking means that the whole text is known at development time and the hyperlinks are set manually.
Dynamic linking is the opposite: instead of knowing beforehand where links are, these are created during runtime by analysing a text and looking for containing web links.
The former is great for things like help messages that contain a web link.
The latter can be used for dynamic user generated content like chat messages or something that is loaded during runtime via a web API.
Static linking with RichText
In order to combine differently styled texts in one widget, the RichText widget can be used. It creates a subtree with a list of TextSpan widgets.
Lucky for us, the TextSpan
widget has a recognizer
property. This enables us to react on the user interacting with the text with a gesture.
1 RichText(
2 text: TextSpan(
3 children: <TextSpan>[
4 const TextSpan(
5 text: 'This way we can embed ',
6 style: TextStyle(color: Colors.black87),
7 ),
8 TextSpan(
9 text: 'a link',
10 style: const TextStyle(
11 color: Colors.blue,
12 decoration: TextDecoration.underline,
13 ),
14 recognizer: TapGestureRecognizer()
15 ..onTap = () => launchUrlString('https://www.example.com'),
16 ),
17 const TextSpan(
18 text: ' into a paragraph',
19 style: TextStyle(color: Colors.black87),
20 )
21 ],
22 ),
23);
The above code results in the following behavior:
Dynamic text linking with flutter_linkify
The package makes us really easy for us to turn links inside a text into tappable hyperlinks.
We just need to wrap our text with the Linkify
widget.
A disadvantage is that we can’t decide to disguise our link with a different text. The best we can do is to make the package strip the protocol (http, https) from the link, making it look less technical.
1Linkify(
2 text: 'This way we can embed a link into a paragraph.\n'
3 'We can\'t disguise the link: https://www.example.com',
4 textAlign: TextAlign.center,
5 onOpen: (LinkableElement link) async {
6 await launchUrlString(link.url);
7 },
8),
The code produces the following result:
Ways of launching
The concept of launching an URL when the user taps it is pretty clear on the web. Since we are already in the context of an application whose main purpose is to display websites (the browser), the only necessary decision is whether to display it in the existing window or create a new one (e. g. a new browser tab).
Inside a mobile app, things are more complicated.
A user could expect different places for the link to be opened:
- In the OS-specific default browser (e. g. Chrome or Safari) as a new app instance
- In the currently running app as a new page (using the navigation stack)
- In the currently running app as a modal (e. g. as a BottomSheet)
- In an external application the user can choose (e. g. a third party browser)
In any case, as a developer, it’s useful to know, how to trigger which behavior.
LaunchMode
of url_launcher
The url_launcher
has a built-in possibility to control the way the URL is opened.
For this, the launchUrlString()
and launchUrl
function offers an optional mode
argument, which expects a type LaunchMode
.
LaunchMode
is an enum
that has four possible values:
LaunchMode.inAppWebView
, like the name implies, opens a new route in the app that contains the native in-app web view (e.g., Safari View Controller). For non-web URLs, this functions throwsArgumentError
LaunchMode.platformDefault
on mobile OS is equal toLaunchMode.inAppWebView
LaunchMode.externalApplication
is used to launch an external app (browser) to open the given URL. Import on iOS if sharing cookies is required as Safari View Controller is unable to share the browser’s contextLaunchMode.externalNonBrowserApplication
links can also be non-web. An example is a phone number. For these cases, this setting can be used to require to open in a non-browser application
Own implementation
Instead of relying on the url_launcher
package, we can also launch a new context in which we display the web content.
Let’s implement a way of opening a BottomSheet in which we embed a in-app web view.
1import 'package:flutter_inappwebview/flutter_inappwebview.dart';
2
3class HyperlinkButton extends StatelessWidget {
4 const HyperlinkButton({
5 super.key,
6 });
7
8 @override
9 Widget build(BuildContext context) {
10 return InkWell(
11 child: const Padding(
12 padding: EdgeInsets.all(16.0),
13 child: Text(
14 'Open BottomSheet',
15 style: TextStyle(
16 color: Colors.blue,
17 decoration: TextDecoration.underline,
18 ),
19 ),
20 ),
21 onTap: () => showModalBottomSheet(
22 context: context,
23 builder: (BuildContext context) {
24 return InAppWebView(
25 initialUrlRequest: URLRequest(
26 url: Uri.parse('https://www.example.com'),
27 ),
28 );
29 },
30 ),
31 );
32 }
33}
In this example, we utilize the flutter_inappwebview package to embed the web view in our BottomSheet
for which we use showModalBottomSheet in order to open it.
The result looks as follows:
The main advantage is that we have everything under control. If we decide to show it smaller, faster or customize it in any way, we’re totally free to do it.
Setting permissions
In order for iOS and Android (API 30 and higher) apps to be allowed to handle web links, you need to add the proper configuration.
This configuration tells the underlying OS to treat http and https as a whitelisted protocol for external links.
The iOS configuration of the Info.plist
file looks as follows:
1<key>LSApplicationQueriesSchemes</key>
2<array>
3 <string>https</string>
4 <string>http</string>
5</array>
In the Android ecosystem the analogous configuration needs to be made in the AndroidManifest.xml
.
The following queries
element needs be added as a new child of the root element of the xml:
1<queries>
2 <intent>
3 <action android:name="android.intent.action.VIEW" />
4 <data android:scheme="https" />
5 </intent>
6</queries>
Conclusion
Although hyperlinks are an integral part of the web platform, they become important for App development whenever the UX concept is to merge the app and web worlds.
There are different cases here.
If it is dynamic data such as chat messages, simply using flutter_linkify all text links can be replaced with tappable links.
If the texts are already known at compile time, you can integrate the links with RichText.
For all other cases, you can get creative with a combination of url_launcher and flutter_inappwebview.
Comment this 🤌