How to add hyperlinks to Flutter's RichText widget
1 min read

How to add hyperlinks to Flutter's RichText widget

How I built Clearful's terms of service and privacy policy widget using Flutter's RichText class, with clickable hyperlinks.
Clearful's terms and privacy RichText widget with hyperlinks

Before building out the above terms of service and privacy policy widget, I hadn't delved much into Flutter's RichText class. Figuring out how to add the hyperlinks was a bit more involved than expected, but thanks to this StackOverflow thread, the widget was built quickly and worked well.

Note that opening the URLs requires using the url_launcher package and, in my case, a service class that wraps its functionality (not shown below).

Here's my implementation:

class TermsAndPrivacy extends StatefulWidget {
  @override
  _TermsAndPrivacyState createState() => _TermsAndPrivacyState();
}

class _TermsAndPrivacyState extends State<TermsAndPrivacy> {
  TapGestureRecognizer _termsOfServiceLink;
  TapGestureRecognizer _privacyPolicyLink;

  @override
  void initState() {
    super.initState();
    _termsOfServiceLink = TapGestureRecognizer()..onTap = _termsOfServiceOnTap;
    _privacyPolicyLink = TapGestureRecognizer()..onTap = _privacyPolicyOnTap;
  }

  @override
  void dispose() {
    _termsOfServiceLink.dispose();
    _privacyPolicyLink.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.only(left: 20, right: 20),
      child: Center(
        child: Column(
          children: [
            Text(
              'By using Clearful, you agree to our ',
              style: Theme.of(context).textTheme.overline,
            ),
            RichText(
              textAlign: TextAlign.center,
              text: TextSpan(
                children: [
                  TextSpan(
                    text: 'Terms of Service',
                    style: Theme.of(context)
                        .textTheme
                        .overline
                        .copyWith(fontWeight: FontWeight.w800),
                    recognizer: _termsOfServiceLink
                      ..onTap = _termsOfServiceOnTap,
                  ),
                  TextSpan(
                    text: ' and ',
                    style: Theme.of(context).textTheme.overline,
                  ),
                  TextSpan(
                    text: 'Privacy Policy',
                    style: Theme.of(context)
                        .textTheme
                        .overline
                        .copyWith(fontWeight: FontWeight.w800),
                    recognizer: _privacyPolicyLink..onTap = _privacyPolicyOnTap,
                  ),
                  TextSpan(
                    text: '.',
                    style: Theme.of(context).textTheme.overline,
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _termsOfServiceOnTap() async {
    await locator<UrlLauncher>().launchUrl(url: termsOfServiceUrl);
  }

  Future<void> _privacyPolicyOnTap() async {
    await locator<UrlLauncher>().launchUrl(url: privacyPolicyUrl);
  }
}