How to Create a Privacy Policy in Flutter

Whether you are creating a landing page, web application, or mobile application. There is a high probability that you will need a privacy policy. Having a privacy policy is even a requirement for the Google Play Store. Not only that, the privacy policy can only be added via a link. This means that the privacy policy should be accessible on the web. In this post, we will create a privacy policy together using Flutter and Markdown. The privacy policy will be optimized for the web.

Creating the Starter Project

In this tutorial we start with a new Flutter project, however, it is possible to follow along with an existing project. You can create a new project by executing the following command where privacy_policy is the name of the project:

flutter create privacy_policy

Once the project is created, open the project and replace the contents of the main.dart file with the following code:

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blueAccent),
        useMaterial3: true,
      ),
      home: Scaffold(
        body: PrivacyPolicy(),
      ),
    );
  }
}

We will create the PrivacyPolicy widget in the upcoming section, so for now, it is okay that it does not exist yet.

Markdown

The easiest approach to create a privacy policy is using Markdown. Markdown is a lightweight markup language ideal for structuring and styling long texts. If you are not familiar with Markdown you can have a look at the following website: Markdown Guide. In the next section, you can find the Markdown we will be using throughout this tutorial.

Creating privacy_policy.md

Now that we created our project we can continue by creating a new folder inside the root of our project called assets. The assets folder is commonly used to store all assets of your project, like images, translations, and in this case markdown files. To keep the assets structured we can add a markdown folder and inside this folder, we can create our privacy_policy.md file. Notice that the file ending of Markdown files is .md.

|-- assets/
  |-- markdown/
    |-- privacy_policy.md

Inside the new file, you can add your Markdown. If you want to follow this tutorial I recommend you to use the same Markdown as the one below. Later on, you can always switch to your Markdown.

# Privacy policy

**Last updated April 21, 2024**

## SUMMARY OF KEY POINTS

***This summary provides key points from our privacy notice, but you can find out more details about any of these topics by clicking the link following each key point or by using our*** [***table of contents***](#table-of-contents) ***below to find the section you are looking for.***  

**What are your rights?** \
Depending on where you are located geographically, the applicable privacy law may mean you have certain rights regarding your personal information. Learn more about [your privacy rights](#3-what-are-your-privacy-rights).  

**How do you exercise your rights?** \
The easiest way to exercise your rights is by emailing us at info@yournews.app. We will consider and act upon any request in accordance with applicable data protection laws.

## TABLE OF CONTENTS

[1. WHAT INFORMATION DO WE COLLECT?](#1-what-information-do-we-collect)\
[2. HOW DO WE PROCESS YOUR INFORMATION?](#2-how-do-we-process-your-information)\
[3. WHAT ARE YOUR PRIVACY RIGHTS?](#3-what-are-your-privacy-rights)  

### 1. WHAT INFORMATION DO WE COLLECT?

#### Personal information you disclose to us

**Sensitive Information.** We do not process sensitive information.

* For investigations and fraud detection and prevention

* For business transactions provided certain conditions are met

### 2. HOW DO WE PROCESS YOUR INFORMATION?

| Category | Examples | Collected |
|----------|----------|-----------|
| A. Identifiers | Contact details, such as real name, alias, postal address, telephone or mobile contact number, unique personal identifier, online identifier, Internet Protocol address, email address, and account name | NO |
| B. Personal information as defined in the California Customer Records statute | Name, contact information, education, employment, employment history, and financial information | NO |

### 3. WHAT ARE YOUR PRIVACY RIGHTS?

If you are located in the EEA or UK and you believe we are unlawfully processing your personal information, you also have the right to complain to your [Member State data protection authority](https://ec.europa.eu/justice/data-protection/bodies/authorities/index_en.htm) or [UK data protection authority](https://ico.org.uk/make-a-complaint/data-protection-complaints/data-protection-complaints/).

If you are located in Switzerland, you may contact the [Federal Data Protection and Information Commissioner](https://www.edoeb.admin.ch/edoeb/en/home.html).

If you have questions or comments about your privacy rights, you may email us at info@yournews.app.

The above Markdown is a shortened version of the privacy policy from the Your News application. I have taken the important elements you might need for your privacy policy to make the tutorial easy to follow, like the table of contents, list, table, different headers, and internal and external links.

Adding Markdown Assets

After creating our markdown file, we have to ensure that we can access it inside our Flutter widgets. We can do this by adding our assets to the pubspec.yaml file.

flutter:
  uses-material-design: true
  assets:
    - assets/markdown/

Afterward, ensure to execute the following command to update the dependencies:

flutter pub get

Installing the Packages

To show our privacy policy in Flutter we will need the following packages:

  • Markdown Widget: We will need this package to show our privacy policy using Markdown.
  • Url Launcher: This package is necessary if you want to have internal navigation links in your privacy policy.

To install the above packages run the following command

flutter pub add markdown_widget && flutter pub add url_launcher

Once the command is executed, check your pubspec.yaml file for the added dependencies. The Markdown Widget and Url Launcher package should be included in the dependencies section, like this:

dependencies:
  markdown_widget: ^2.3.2+6
  url_launcher: ^6.2.6

Privacy Policy in Flutter

With the assets added and the packages installed, we can finally create our PrivacyPolicy widget. This widget can be stateless.

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

class PrivacyPolicy extends StatelessWidget {
  const PrivacyPolicy({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: Center(
        child: SizedBox(
          width: 1000,
          child: FutureBuilder(
            future: rootBundle.loadString('assets/markdown/privacy_policy.md'),
            builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
              if (snapshot.hasData) {
                return MarkdownWidget(
                  data: snapshot.data!,
                );
              }

              return const Center(child: CircularProgressIndicator());
            },
          ),
        ),
      ),
    );
  }
}

In the above code snippet, we start by returning a Scaffold because we want the privacy policy to have its own screen. We have added a centered SizedBox widget on the body property of the Scaffold. The SizedBox widget is optional, in my opinion, reducing the width of the privacy policy to 1000 will make it easier to read.

Inside the SizedBox widget, we have a FutureBuilder. The builder is needed because loading the assets from the rootBundle needs to be awaited. If our assets are successfully loaded we will display the privacy policy using the MarkdownWidget. Otherwise, we will display a CircularProgressIndicator.

We can now build our application for the web using the following command:

flutter run -d chrome

Where chrome is the browser of choice, if you are using another browser make sure to adjust the command.

After executing the command, you will see the following result in your browser:

Adjusting the Style of the Markdown

In the previous section, we rendered the Markdown without making any changes to the styling. However, the MarkdownWidget has an optional config property that can be used to style the different elements.

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

class PrivacyPolicy extends StatelessWidget {
  const PrivacyPolicy({super.key});

  @override
  Widget build(BuildContext context) {
    const headerTextStyle = TextStyle(height: 2, fontWeight: FontWeight.w600);

    return Scaffold(
      backgroundColor: Colors.white,
      body: Center(
        child: SizedBox(
          width: 1000,
          child: FutureBuilder(
            future: rootBundle.loadString('assets/markdown/privacy_policy.md'),
            builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
              if (snapshot.hasData) {
                return MarkdownWidget(
                  config: MarkdownConfig(configs: [
                    const PConfig(textStyle: TextStyle(fontSize: 15)),
                    H1Config(style: headerTextStyle.copyWith(fontSize: 26)),
                    H2Config(style: headerTextStyle.copyWith(fontSize: 22)),
                    H3Config(style: headerTextStyle.copyWith(fontSize: 18)),
                    H4Config(style: headerTextStyle.copyWith(fontSize: 17)),
                    const TableConfig(
                      columnWidths: {0: FractionColumnWidth(0.25)},
                    ),
                    ListConfig(
                      marker: (isOrdered, depth, index) => const Padding(
                        padding: EdgeInsets.only(top: 6),
                        child: Icon(Icons.circle, size: 6),
                      ),
                    ),
                  ]),
                  data: snapshot.data!,
                );
              }

              return const Center(child: CircularProgressIndicator());
            },
          ),
        ),
      ),
    );
  }
}

In the MarkdownWidget we added the config property, which takes a MarkdownConfig instance. The MarkdownConfig takes a list of WidgetConfig instances. We have used this to add configs for the paragraphs, headers, tables, and lists. For the paragraphs and headers, we adjusted the fontSize. For the table, we ensured that the first column uses 25% of the table’s width. At last, we made the bullets of the list smaller.

At last, after adding the styling to our privacy policy in Flutter, we can move on to the last step, which is handling navigation. Right now, when you click external links you will be redirected to the new website. However, if you click an internal link, for example, the “table of contents”, the privacy policy will be refreshed. Instead of refreshing the page, we want the page to navigate to the part of the privacy policy the link is referring to.

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

class PrivacyPolicy extends StatelessWidget {
  PrivacyPolicy({super.key});

  final TocController _controller = TocController();

  void _handleLinks(String url) {
    if (url.contains('#')) {
      _controller.jumpToIndex(
        switch (url) {
          '#table-of-contents' => 6,
          '#1-what-information-do-we-collect' => 8,
          '#personal-information-you-disclose-to-us' => 9,
          '#2-how-do-we-process-your-information' => 12,
          '#3-what-are-your-privacy-rights' => 14,
          _ => 14
        },
      );
    } else {
      launchUrl(Uri.parse(url));
    }
  }

  @override
  Widget build(BuildContext context) {
    const headerTextStyle = TextStyle(height: 2, fontWeight: FontWeight.w600);

    return Scaffold(
      backgroundColor: Colors.white,
      body: Center(
        child: SizedBox(
          width: 1000,
          child: FutureBuilder(
            future: rootBundle.loadString('assets/markdown/privacy_policy.md'),
            builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
              if (snapshot.hasData) {
                return MarkdownWidget(
                  config: MarkdownConfig(configs: [
                    const PConfig(textStyle: TextStyle(fontSize: 15)),
                    H1Config(style: headerTextStyle.copyWith(fontSize: 26)),
                    H2Config(style: headerTextStyle.copyWith(fontSize: 22)),
                    H3Config(style: headerTextStyle.copyWith(fontSize: 18)),
                    H4Config(style: headerTextStyle.copyWith(fontSize: 17)),
                    const TableConfig(
                      columnWidths: {0: FractionColumnWidth(0.25)},
                    ),
                    ListConfig(
                      marker: (isOrdered, depth, index) => const Padding(
                        padding: EdgeInsets.only(top: 6),
                        child: Icon(Icons.circle, size: 6),
                      ),
                    ),
                    LinkConfig(onTap: (url) => _handleLinks(url)),
                  ]),
                  data: snapshot.data!,
                  tocController: _controller,
                );
              }

              return const Center(child: CircularProgressIndicator());
            },
          ),
        ),
      ),
    );
  }
}

In the above code, we started by adding a _controller variable which is set to TocController. Because _controller is initialized with a non-constant value we have to remove the const keyword from our constructor. The _controller variable is then added to the MarkdownWidget.

We also created a new function called _handleLinks which will take care of both internal and external links. We can differentiate between internal and external links by the # symbol. Only internal links will have this symbol included in their link. If it is an internal link we will use the _controller to jump to the correct index of our privacy policy using the jumpToIndex function. Inside the function, we provide the index of the link. To find the indexes in your Markdown file you have to count the sections. Each section no matter the length is equal to 1.

For external links, we use the LaunchUrl function from the Url Launcher package to navigate to the right website.

Finally, we need to add the LinkConfig configuration to make sure that all the links use our new function.

If the internal links are not working for you, try to decrease the height of your screen or rebuild your application.

Hosting the Privacy Policy for Free using Firebase

After creating the privacy policy, you might wonder how to deploy it on the web. Check out the following article to deploy your privacy policy for free using Firebase: How to Host Flutter Web Apps for Free

Conclusion

Creating a privacy policy is often mandatory for your applications. Creating a privacy policy in Flutter can be a great option. Especially, if you are creating your application in Flutter anyway.

In this post, you learned how to use Markdown inside Flutter to create a privacy policy. We also discussed styling and navigation to make your privacy policy more user-friendly.

Tijn van den Eijnde
Tijn van den Eijnde
Articles: 40

Leave a Reply

Your email address will not be published. Required fields are marked *