How to build a mood tracker app in Flutter
Tags: flutter, appwrite, mobilefullstack, mobiledevelopment
Published: 2023-03-29 11:49:51

Over the years, founders, motivational speakers, and other experts have built tools and techniques to help people track their moods. It has helped people spot patterns, develop coping mechanisms, and answer some of their most profound questions about life.

In this post, we will learn how to build a personalized mood tracker using Appwrite’s database feature in a Flutter application. The project’s GitHub repository can be found [here](https://github.com/Mr-Malomz/mood_tracker/tree/dev).

## Prerequisites

To fully grasp the concepts presented in this tutorial, the following are required:

-   Basic understanding of Dart and Flutter
-   Flutter [SDK](https://flutter.dev/docs/get-started/install) installed
-   [Xcode](https://developer.apple.com/xcode/) with a developer account (for Mac users)
-   Either [i](https://developer.apple.com/xcode/)[OS Simulator](https://developer.apple.com/xcode/), [Android Studio](https://developer.android.com/studio), or [Chrome web browser](https://support.google.com/chrome/answer/95346?hl=en-GB&co=GENIE.Platform%3DDesktop) to run the application
-   An Appwrite instance; check out this [article](https://dev.to/hackmamba/create-a-local-appwrite-instance-in-3-steps-19n9?utm_source=hackmamba&utm_medium=hackmamba) on how to set up an instance locally or install it with one click on [DigitalOcean](https://marketplace.digitalocean.com/apps/appwrite) or [Gitpod](https://gitpod.io/#https://github.com/appwrite/integration-for-gitpod)

## Getting started

To get started, we need to clone the project by navigating to the desired directory and running the command below:

    git clone https://github.com/Mr-Malomz/mood_tracker.git

## Running the project

First, we need to install the project dependencies by running the command below:

```bash
    flutter pub get
```

Then, run the project using the following command:

```bash
    flutter run
```

The command above will run the application on the selected device.

![Home screen](https://paper-attachments.dropboxusercontent.com/s_B2AD076E5A37AF85117E8FEDFA25059F013D2E32D964A1C2F4599FABD78F13F2_1676551090116_Screenshot+2023-02-15+at+23.11.52.png)
![Trend screen](https://paper-attachments.dropboxusercontent.com/s_B2AD076E5A37AF85117E8FEDFA25059F013D2E32D964A1C2F4599FABD78F13F2_1676551101250_Screenshot+2023-02-16+at+04.18.40.png)

## Set up mood tracker service on Appwrite

To get started, we need to log into our Appwrite console, click the **Create project** button, input `mood-tracker` as the name, and then click **Create**.

![Create project](https://paper-attachments.dropboxusercontent.com/s_342E9FE96DE03D24E8AD672738F224A92FC913F6592531FAD506815CB3E2A539_1672866342209_Screenshot+2023-01-04+at+22.05.19.png)

**Create a database, collection, and attributes**

With our project created, we can set up our application database. First, navigate to the **Database** tab, click the **Create database** button, input `trackers` as the name, and then click **Create**.

![Create database](https://paper-attachments.dropboxusercontent.com/s_84688A49881ED7B894BB54FC885948D95F94AEE77178A12FAD5ABA3FBC753CC7_1674391810309_Screenshot+2023-01-22+at+13.49.05.png)

Secondly, we need to create a collection for storing a user’s mood. To do this, click the **Create collection** button, input `moods` as the name, and then click **Create**.

![Moods collection](https://paper-attachments.dropboxusercontent.com/s_B2AD076E5A37AF85117E8FEDFA25059F013D2E32D964A1C2F4599FABD78F13F2_1676553209004_Screenshot+2023-02-16+at+00.00.43.png)

Thirdly, we need to create attributes to represent our database fields. To do this, we need to navigate to the **Attributes** tab and create attributes for the collection, as shown below:

| Attribute key | Attribute type | Size | Required |
| ------------- | -------------- | ---- | -------- |
| rate          | integer        | —    | YES      |
| description   | string         | 5000 | YES      |
| createdAt     | datetime       | —    | YES      |

![Create attributes](https://paper-attachments.dropboxusercontent.com/s_B2AD076E5A37AF85117E8FEDFA25059F013D2E32D964A1C2F4599FABD78F13F2_1676553426323_Screenshot+2023-02-16+at+00.01.33.png)
![Moods collection attributes](https://paper-attachments.dropboxusercontent.com/s_B2AD076E5A37AF85117E8FEDFA25059F013D2E32D964A1C2F4599FABD78F13F2_1676553447313_Screenshot+2023-02-16+at+00.02.52.png)

We also need to update our collection permissions to manage them accordingly. To do this, navigate to the **Settings** tab, scroll to the **Update Permissions** section, select `Any`, mark accordingly, and then click **Update**.

![Select any](https://paper-attachments.dropboxusercontent.com/s_B2AD076E5A37AF85117E8FEDFA25059F013D2E32D964A1C2F4599FABD78F13F2_1676553562813_Screenshot+2023-02-16+at+00.04.52.png)
![Mark permission and update](https://paper-attachments.dropboxusercontent.com/s_B2AD076E5A37AF85117E8FEDFA25059F013D2E32D964A1C2F4599FABD78F13F2_1676553571563_Screenshot+2023-02-16+at+00.05.02.png)

**Add platform support**

To add support for our Flutter app, navigate to the **Home** menu and click the **Flutter App** button.

![Add platform](https://paper-attachments.dropboxusercontent.com/s_B2AD076E5A37AF85117E8FEDFA25059F013D2E32D964A1C2F4599FABD78F13F2_1676553641591_Screenshot+2023-02-16+at+00.07.39.png)

We can modify the Flutter application depending on the device used to run it, as shown below.

**iOS**

To obtain our Bundle ID, navigate to the path below:
**ios > Runner.xcodeproj > project.pbxproj**

Open the `project.pbxproj` file and search for `PRODUCT_BUNDLE_IDENTIFIER`.

![Select new Flutter app](https://res.cloudinary.com/dtgbzmpca/image/upload/v1677062661/ios_ip.png)

Next, open the project directory on [Xcode](https://developer.apple.com/xcode/), open the `Runner.xcworkspace` folder in the app's iOS folder, select the **Runner** project in the Xcode project navigator, select the **Runner** target in the primary menu sidebar, and then select **iOS 11** in the deployment info’s target.

![Change deployment target](https://paper-attachments.dropboxusercontent.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650208061820_Screenshot+2022-04-17+at+16.05.22.png)

**Android**

To get our package name, we can navigate to an XML file using the following path:
**android > app > src > debug > AndroidManifest.xml**

Open the `AndroidManifest.xml` file and copy the `package` value.

![Android](https://paper-attachments.dropboxusercontent.com/s_1F2738109066A0A5582E754A3908A7FD06BE17296D17A867AFE208C1CA00E9C0_1671389594719_Screenshot+2022-12-18+at+01.25.50.png)

Next, let’s modify the `AndroidManifext.xml` as shown below:

```xml
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.mood_tracker">
        <uses-permission android:name="android.permission.INTERNET"/>
        <application ...>
        <activity android:name="com.linusu.mobile_wallet.CallbackActivity" android:exported="true">
          <intent-filter android:label="mood_tracker">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="appwrite-callback-[PROJECT_ID]" />
          </intent-filter>
        </activity>
      </application>
    </manifest>
```

Also, the highlighted `[PROJECT_ID]` must be replaced with the actual Appwrite project ID.

## Building the mood tracker service

With all that done, let’s build the mood tracker service. First, we need to create models to convert the response sent from Appwrite to a Dart object. The models will also cater to JSON serialization. To do this, we need to create a `utils.dart` file in the `lib` folder and add the snippet below:

```dart
    class AppConstant {
      final String databaseId = "REPLACE WITH DATABASE ID";
      final String projectId = "REPLACE WITH PROJECT ID";
      final String collectionId = "REPLACE WITH COLLECTION ID";
      final String endpoint = "ENDPOINT";
    }

    class Mood {
      String? $id;
      int rate;
      String description;
      DateTime? createdAt;

      Mood({
        this.$id,
        required this.rate,
        required this.description,
        required this.createdAt,
      });

      Map<dynamic, dynamic> toJson() {
        return {
          "rate": rate,
          "description": description,
          "createdAt": createdAt!.toIso8601String(),
        };
      }

      factory Mood.fromJson(Map<dynamic, dynamic> json) {
        return Mood(
          $id: json['\$id'],
          rate: json['rate'],
          description: json['description'],
          createdAt: DateTime.tryParse(json['createdAt']),
        );
      }
    }
```

We’ll need to modify the `endpoint` property to work with our system's local network address. We can adjust accordingly. Below are the instructions for both iOS and Android.

**iOS**

Navigate to the **Network** section and copy the IP address.

![IP address](https://res.cloudinary.com/practicaldev/image/fetch/s--C8zE0vUD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropboxusercontent.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650365320233_Screenshot%2B2022-04-18%2Bat%2B19.38.08.png)

**Android**
We can connect our Android emulator to the system’s IP using the `10.0.2.2` IP address.

    final String endpoint = "http://10.0.2.2/v1";

**Note**: We can get the `databaseId`, ` project``I``d `, and `collectionId` by navigating through the Appwrite console.

Next, we must create a service file to separate the application core logic from the UI. To do this, create a `mood_service.dart` file inside the `lib` directory. Then, add the snippet below:

```dart
    import 'package:appwrite/appwrite.dart';
    import 'package:mood_tracker/utils.dart';

    class MoodService {
      Client _client = Client();
      Databases? _db;

      MoodService() {
        _init();
      }

      //initialize the application
      _init() async {
        _client
            .setEndpoint(AppConstant().endpoint)
            .setProject(AppConstant().projectId);
        _db = Databases(_client);
        //get current session
        Account account = Account(_client);
        try {
          await account.get();
        } on AppwriteException catch (e) {
          if (e.code == 401) {
            account
                .createAnonymousSession()
                .then((value) => value)
                .catchError((e) => e);
          }
        }
      }

      Future createMood(int rate, String description) async {
        try {
          Mood newMood = Mood(
            rate: rate,
            description: description,
            createdAt: DateTime.now(),
          );
          var data = await _db?.createDocument(
            databaseId: AppConstant().databaseId,
            collectionId: AppConstant().collectionId,
            documentId: ID.unique(),
            data: newMood.toJson(),
          );
          return data;
        } catch (e) {
          throw Exception('Error creating mood!');
        }
      }

      Future<List<Mood>> getMoodList() async {
        try {
          var data = await _db?.listDocuments(
            databaseId: AppConstant().databaseId,
            collectionId: AppConstant().collectionId,
          );
          var moodList =
              data?.documents.map((mood) => Mood.fromJson(mood.data)).toList();
          return moodList!;
        } catch (e) {
          throw Exception('Error getting list of moods!');
        }
      }
    }
```

The snippet above does the following:

-   Imports the required dependencies
-   Creates a `MoodService` class with `_client` and `_db` properties to connect to the Appwrite instance and the database
-   Creates an `_init` method that configures Appwrite using the properties and also conditionally creates an anonymous user to access the Appwrite database
-   Creates a `createMood` and `getMoodList` methods that use the `_db` property to create and obtain a list of saved moods accordingly

## Consuming the service

With that done, we can start using the service to perform the required operation.

**Creating moods**

To get started, we need to modify the `home.dart` file in the `screens` directory and update it by doing the following:

First, we need to import the required dependencies and create a method to save the current mood to the database:

```dart
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    import 'package:mood_tracker/mood_service.dart';

    class Home extends StatefulWidget {
      @override
      State<Home> createState() => _HomeState();
    }

    class _HomeState extends State<Home> {
      List<int> list = <int>[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
      final _formKey = GlobalKey<FormState>();
      final TextEditingController _description = TextEditingController();
      late int _rate;
      bool _isLoading = false;

      _createMood() {
        setState(() {
          _isLoading = true;
        });
        MoodService().createMood(_rate, _description.text).then((value) {
          setState(() {
            _isLoading = false;
          });
          _formKey.currentState!.reset();
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Mood saved successfully!')),
          );
        }).catchError((_) {
          setState(() {
            _isLoading = false;
          });
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Error saving mood!')),
          );
        });
      }

      @override
      Widget build(BuildContext context) {
        //UI code goes here
      }
    }
```

The snippet above does the following, broken down by lines:

-   **Lines** **1-3**: Import the required dependencies
-   **Lines** **13-15**: Create the `_description`, `_rate`, and `_isLoading` properties to manage the application state
-   **Lines** **17-38**: Create a `_createMood` method to save the current mood using the `MoodService().createMood` service, set states accordingly

Lastly, we need to modify the UI to use the method and states created to process the form.

```dart
    //import goes here

    class Home extends StatefulWidget {
      //code goes here
    }

    class _HomeState extends State<Home> {
      //states goes here

      _createMood() {
        //code goes here
      }

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 30.0),
            child: Form(
              key: _formKey,
              child: Column(
                children: [
                  const Text('How are you feeling today',
                      style: TextStyle(
                        fontSize: 18.0,
                        fontWeight: FontWeight.bold,
                      )),
                  const SizedBox(height: 30.0),
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      //title
                      Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          const Text(
                            'Rate your mood',
                            style: TextStyle(
                              color: Colors.grey,
                              fontSize: 14.0,
                            ),
                          ),
                          const SizedBox(height: 5.0),
                          DropdownButtonFormField(
                              items: list.map<DropdownMenuItem<int>>((int value) {
                                return DropdownMenuItem<int>(
                                  value: value,
                                  child: Text('$value'),
                                );
                              }).toList(),
                              validator: (value) {
                                if (value == null) {
                                  return 'Please rate your mood';
                                }
                                return null;
                              },
                              decoration: InputDecoration(
                                contentPadding: const EdgeInsets.symmetric(
                                    vertical: 10, horizontal: 20),
                                hintText: "select your mood",
                                focusedBorder: OutlineInputBorder(
                                  borderRadius: BorderRadius.circular(10),
                                  borderSide: const BorderSide(color: Colors.grey),
                                ),
                                enabledBorder: OutlineInputBorder(
                                  borderRadius: BorderRadius.circular(10),
                                  borderSide: const BorderSide(color: Colors.grey),
                                ),
                                errorBorder: OutlineInputBorder(
                                  borderRadius: BorderRadius.circular(10),
                                  borderSide: const BorderSide(color: Colors.red),
                                ),
                              ),
                              onChanged: ((value) {
                                setState(() {
                                  _rate = value!;
                                });
                              })),
                        ],
                      ),
                    ],
                  ),
                  const SizedBox(height: 30.0),
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text(
                        'Describing how you feel',
                        style: TextStyle(
                          color: Colors.grey,
                          fontSize: 14.0,
                        ),
                      ),
                      const SizedBox(height: 5.0),
                      TextFormField(
                        controller: _description,
                        validator: (value) {
                          if (value == null || value.isEmpty) {
                            return 'Please input your feeling';
                          }
                          return null;
                        },
                        inputFormatters: [LengthLimitingTextInputFormatter(70)],
                        decoration: InputDecoration(
                          contentPadding: const EdgeInsets.symmetric(
                              vertical: 10, horizontal: 20),
                          hintText: "describe your feeling",
                          fillColor: Colors.white,
                          focusedBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10),
                            borderSide: const BorderSide(color: Colors.grey),
                          ),
                          enabledBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10),
                            borderSide: const BorderSide(color: Colors.grey),
                          ),
                          errorBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10),
                            borderSide: const BorderSide(color: Colors.red),
                          ),
                        ),
                        minLines: 6,
                        keyboardType: TextInputType.multiline,
                        maxLines: null,
                      ),
                    ],
                  ),
                  const SizedBox(height: 30.0),
                  SizedBox(
                    height: 45,
                    width: double.infinity,
                    child: TextButton(
                      onPressed: _isLoading
                          ? null
                          : () {
                              if (_formKey.currentState!.validate()) {
                                _createMood();
                              }
                            },
                      style: ButtonStyle(
                        backgroundColor:
                            MaterialStateProperty.all<Color>(Colors.blue),
                      ),
                      child: const Text(
                        'Save',
                        style: TextStyle(
                          color: Colors.white,
                          fontWeight: FontWeight.bold,
                          fontSize: 14.0,
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
```

**Get the list of moods**

To obtain the list of moods, we need to modify the `trends.dart` file in the same `screens` directory as shown below:

```dart
    import 'package:fl_chart/fl_chart.dart';
    import 'package:flutter/material.dart';
    import 'package:mood_tracker/mood_service.dart';
    import 'package:mood_tracker/utils.dart';

    class Trends extends StatefulWidget {
      const Trends({Key? key}) : super(key: key);

      @override
      State<Trends> createState() => _TrendsState();
    }

    class _TrendsState extends State<Trends> {
      late List<Mood> moods;
      bool _isLoading = false;
      bool _isError = false;

      @override
      void initState() {
        _getMoodList();
        super.initState();
      }

      _getMoodList() {
        setState(() {
          _isLoading = true;
        });
        MoodService().getMoodList().then((value) {
          setState(() {
            moods = value;
            _isLoading = false;
          });
        }).catchError((e) {
          setState(() {
            _isLoading = false;
            _isError = true;
          });
        });
      }

      Widget build(BuildContext context) {
        return _isLoading
            ? const Center(
                child: CircularProgressIndicator(
                color: Colors.blue,
              ))
            : _isError
                ? const Center(
                    child: Text(
                      'Error getting list of transactions',
                      style: TextStyle(
                        color: Colors.red,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  )
                : Padding(
                    padding: const EdgeInsets.only(top: 30, bottom: 30, right: 30),
                    child: Container(
                        child: LineChart(
                      LineChartData(
                        minY: 0,
                        maxY: 10,
                        minX: 0,
                        maxX: 15,
                        borderData: FlBorderData(
                          show: true,
                          border: Border.all(),
                        ),
                        titlesData: FlTitlesData(
                          show: true,
                          topTitles: AxisTitles(
                            sideTitles: SideTitles(showTitles: false),
                          ),
                          rightTitles: AxisTitles(
                            sideTitles: SideTitles(showTitles: false),
                          ),
                          bottomTitles: AxisTitles(
                            sideTitles: SideTitles(showTitles: false),
                          ),
                        ),
                        lineBarsData: [
                          LineChartBarData(
                            spots: moods
                                .asMap()
                                .map((key, value) => MapEntry(key,
                                    FlSpot(key.toDouble(), value.rate.toDouble())))
                                .values
                                .toList(),
                            isCurved: true,
                            color: Colors.blueAccent,
                            barWidth: 2.5,
                            belowBarData: BarAreaData(
                              show: true,
                              color: Color.fromARGB(99, 142, 152, 169),
                            ),
                          ),
                        ],
                      ),
                    )),
                  );
      }
    }
```

The snippet above does the following:

-   **Lines** **1-4**: Import the required dependencies
-   **Lines** **14-16**: Create the `moods`, `_isLoading`, and `_isError` properties to manage the application state
-   **Lines** **18-40**: Create a `_getMoodList` method to get the list of moods using the `MoodService().getMoodList` service, set states accordingly, and use the `initState` method to call the `_getMoodList` method when the object is inserted into the tree
-   Modifies the UI widgets to use the data saved on Appwrite to plot a graph showing the mood trend

With that done, we restart the application using the code editor or run the command below:

```bash
    flutter run
```

![](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExMDEwNjM5ZTM0YTdiZjBhYzAwMDcyODJmNGQ1ZmRhZjQwM2Q1YTc5MCZjdD1n/eU2wGdy7HAiyN5SXC9/giphy.gif)

## Conclusion

This post discussed how to use Appwrite’s database features to build a personalized mood tracker in a Flutter application. We built a base implementation to demonstrate Appwrite's support for scaffolding a working prototype. We can further extend the application's functionality by leveraging features like real-time communication, localization, and authentication.

These resources may also be helpful:

-   [Appwrite official documentation](https://appwrite.io/docs?utm_source=hackmamba&utm_medium=hackmamba-blog)
-   [Appwrite Flutter SDK](https://appwrite.io/docs/getting-started-for-flutter?utm_source=hackmamba&utm_medium=hackmamba-blog)
-   [FL chart library](https://github.com/imaNNeo/fl_chart)