Build a Notepad App in Flutter
Tags: appwrite, flutter, fullstack
Published: 2022-05-16 11:13:38

---
title: Build a Notepad App in Flutter
published: true
description: this post discuss how to create a note-taking mobile application using Flutter and Appwrite.
tags: #Appwrite #flutter #fullstack
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/74qw7gpoz5gxiryamr0c.jpg
---
Note-taking is an essential part of learning. It involves users writing down everything they hear and read. Research has shown that note-takers remember more essential ideas and retain knowledge.

In this post, we will learn how to create a note-taking mobile application using Flutter. This application doesn’t require a custom backend server.

## Prerequisites

To fully grasp the concepts presented in this tutorial, we require the following:

- Basic understanding of Dart and Flutter
- Flutter [SDK](https://flutter.dev/docs/get-started/install) installed
- [Xcode](https://developer.apple.com/xcode/) with developer account (for Mac users)
- Either [IOS Simulator](https://developer.apple.com/documentation/xcode/running-your-app-in-the-simulator-or-on-a-device), [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 our application
- Docker [installation](https://www.docker.com/get-started/)

[Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) is a development platform that provides a powerful API and management console for building backend servers for web and mobile applications.

## Getting Started

In this post, we will focus on implementations only. The project UI has already been set up. Design resources used are also available [here](https://www.figma.com/community/file/1097305692552888102/flutter_appwrite).

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

```bat
    git clone https://github.com/Mr-Malomz/note_app.git && cd note_app
```

The complete source code is also available on the [**dev**](https://github.com/Mr-Malomz/note_app/tree/dev) branch of the same repository.

## Folder Structure
![folder structure](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650154759431_Screenshot+2022-04-17+at+01.16.37.png)


Let’s go over some of the key directories and files:

- `screens:` a folder to store the screens of our application
- `utils:` a folder to store reusable classes
- `widgets:` a folder to store building blocks of our application
- `main.dart:` the entry point of our application
## Running the Project

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

```bat
    flutter pub get
```
Then run the project using the command below:
```bat
    flutter run
```
The command above will run the application on the selected device.


![demo](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650155925476_Simulator+Screen+Shot+-+iPhone+12+Pro+Max+-+2022-04-17+at+01.36.33.png)
![demo](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650155925600_Simulator+Screen+Shot+-+iPhone+12+Pro+Max+-+2022-04-17+at+01.37.02.png)
![demo](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650155925892_Simulator+Screen+Shot+-+iPhone+12+Pro+Max+-+2022-04-17+at+01.37.07.png)

# Setting up Appwrite

[Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) is a development platform that provides a powerful API and management console for building backend servers for web and mobile applications. We’ll use Appwrite to manage all backend logic including storage.
To set up our backend services using Appwrite, we first need to start up Docker, navigate to the desired directory, and then install Appwrite on our machines using any of the applicable commands below:

**Unix command (Mac/Linux PC)**
```bat
    docker run -it --rm \
        --volume /var/run/docker.sock:/var/run/docker.sock \
        --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
        --entrypoint="install" \
        appwrite/appwrite:0.13.4
```
**Windows command (Windows PC)**
```bat
    docker run -it --rm ^
        --volume //var/run/docker.sock:/var/run/docker.sock ^
        --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
        --entrypoint="install" ^
        appwrite/appwrite:0.13.4
```
**Powershell (PCs running Powershell)**
```bat
    docker run -it --rm ,
        --volume /var/run/docker.sock:/var/run/docker.sock ,
        --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
        --entrypoint="install" ,
        appwrite/appwrite:0.13.4
```

The command will ask us some questions on how to configure our application. We can answer the questions as shown below:
```bat
    Choose your server HTTP port: (default: 80): <PRESS ENTER>
    Choose your server HTTPS port: (default: 443): <PRESS ENTER>
    Choose a secret API key, make sure to make a backup of your key in a secure location (default: 'your-secret-key'): <PRESS ENTER>
    Enter your Appwrite hostname (default: 'localhost'): <PRESS ENTER>
    Enter a DNS A record hostname to serve as a CNAME for your custom domains.
    You can use the same value as used for the Appwrite hostname. (default: 'localhost'): <PRESS ENTER>
```
The selected options will install and run Appwrite on our machine. We test our application by opening the URL below on our browser.
***PS****: Installation and running might take some time.*
```bat
    http://localhost:80
```

![Appwrite running](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650158621491_screencapture-localhost-auth-signup-2022-04-17-02_22_56.png)

## Creating a new project

To create a project, we need to create a new account by signing up. On the console, click on the **Create Project** button, input `flutter_appwrite` as the name, and click **Create**.

![create project](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650159125946_Screenshot+2022-04-17+at+02.28.39.png)
![enter project name](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650159125957_Screenshot+2022-04-17+at+02.30.52.png)


Next, we need to create a database to save our notes. Navigate to the **Database** tab, click on **Add Collection**, input `flutter_appwrite_col` as the collection name, and click on **Create**.

![navigate to Database Tab](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650179872117_Screenshot+2022-04-17+at+02.34.59.png)
![click on create project](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650179872134_Screenshot+2022-04-17+at+08.09.34.png)
![enter project name & create](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650179872126_Screenshot+2022-04-17+at+08.15.42.png)


Appwrite has an advanced yet flexible way to manage access to users, teams, or roles to access specific resources. We will modify the permission `role:all` to enable access from any application. Then click on **Update** to save changes.

![modify permission](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650180737706_Screenshot+2022-04-17+at+08.31.11.png)

## Add Attributes

Attributes are fields that our database will have. Navigate to the Attributes tab, click on **Add Attributes**, add a string attribute for both **title** and **note** fields, **mark as required**, and click on **Create**.

![select attribute](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650181607632_Screenshot+2022-04-17+at+08.41.22.png)
![create field](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650181638011_Screenshot+2022-04-17+at+08.45.03.png)

## Add Sample Data

To get a feel of our database, we can add sample data by navigating to the **Documents** tab, clicking on **Add Document**, inputting the required fields, and clicking on **Create**.

![add document](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650182250008_Screenshot+2022-04-17+at+08.55.08.png)
![input fields and create](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650182250000_Screenshot+2022-04-17+at+08.56.56.png)

# Integrating Appwrite with Flutter.

To add support for our Flutter app, navigate to the **Home** menu, click on **Add Platform** button, and select **New Flutter App**.

![Add Platform](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650183238195_Screenshot+2022-04-17+at+09.11.20.png)
![select New Flutter App](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650183238206_Screenshot+2022-04-17+at+09.12.40.png)


Depending on the device we are running our Flutter application on, we can modify it as shown below:

**IOS**
To get our Bundle ID, we can navigate using the path below, open the `project.pbxproj` file, and search for `PRODUCT_BUNDLE_IDENTIFIER`.
**ios > Runner.xcodeproj > project.pbxproj**

![ios](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650183909235_Screenshot+2022-04-17+at+09.24.33.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 Runner target in the main menu sidebar, and select **IOS 11** in the deployment info’s target.

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


**Android**
To get our package name, we can navigate using the path below, open the `AndroidManifest.xml` file, and copy the `package` value.
**android > app > src > debug > AndroidManifest.xml**

![android](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650187914980_Screenshot+2022-04-17+at+10.30.35.png)


Next, we need to modify the `AndroidManifext.xml` as shown below:
```xml
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.note_app">
        <uses-permission android:name="android.permission.INTERNET"/>
        <application ...>
        <activity android:name="com.linusu.flutter_web_auth.CallbackActivity" android:exported="true">
          <intent-filter android:label="flutter_web_auth">
            <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>
```
We also need to modify the highlighted code with our Appwrite’s **Project ID**. Navigate to the **Settings** menu to get the **Project ID**.


![project id](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650189388982_Screenshot+2022-04-17+at+10.54.17.png)


We can learn more about Appwrite’s Flutter platform-specific support [here](https://appwrite.io/docs/getting-started-for-flutter?utm_source=hackmamba&utm_medium=hackmamba-blog).

## Install Appwrite Flutter SDK

Next, we need to install the required dependency by navigating to the root directory, open the `pubspec.yaml` file, and then add the Appwrite’s SDK to the `dependency` section.
```bat
    appwrite: ^4.0.2
```

![add dependency](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650209857949_Screenshot+2022-04-17+at+16.36.49.png)


***PS:*** *An editor like Visual Studio Code automatically installs the dependencies for us when we save the file. We might need to stop our project and run* `*flutter pub get*` *to install the dependency manually for other editors.*

## Connecting Appwrite with Flutter

Next, we need to navigate the `utils` folder inside the `lib` directory and create a `setup.dart` file, and add the snippet below:
```dart
    class AppConstant {
      final String projectId = "REPLACE WITH YOUR PROJECT ID";
      final String endpoint = "REPLACE WITH YOUR ENPOINT";
      final String collectionId = "REPLACE WITH YOUR COLLECTION ID";
    }
```
Navigate to the settings menu for the project and database to copy the **Project ID**, **API Endpoint**, and **Collection ID**.

![](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650212096876_Screenshot+2022-04-17+at+17.11.09.png)
![](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650212096890_Screenshot+2022-04-17+at+17.14.26.png)


For the `endpoint` property, we need to modify it to work with our system's local network address. We can adjust accordingly:

**IOS**
Navigate to the **Network** section, copy the IP address, and modify as shown below:
```dart
    class AppConstant {
      final String projectId = "REPLACE WITH YOUR PROJECT ID";
      final String endpoint = "http://192.168.1.195/v1";
      final String collectionId = "REPLACE WITH YOUR COLLECTION ID";
    }
```
![](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650365320233_Screenshot+2022-04-18+at+19.38.08.png)


**Android**
We can connect our Android emulator to the system’s IP using `10.0.2.2` IP address.
```dart
    class AppConstant {
      final String projectId = "REPLACE WITH YOUR PROJECT ID";
      final String endpoint = "http://10.0.2.2/v1";
      final String collectionId = "REPLACE WITH YOUR COLLECTION ID";
    }
```

## Creating a Model

Next, we need to create a model to convert the response sent from Appwrite to a Dart object. The model will also cater to JSON serialization. To do this, create a `models` folder inside the `lib` directory; in this folder, create a `note_model.dart` file and add the snippet below:
```dart
    class Note {
      final String? $id;
      final String title;
      final String note;
    
      Note({this.$id, required this.title, required this.note});
    
      factory Note.fromJson(Map<dynamic, dynamic> json) {
        return Note($id: json['\$id'], title: json['title'], note: json['note']);
      }
    
      Map<dynamic, dynamic> toJson() {
        return {'title': title, 'note': note};
      }
    }
```
The snippet above does the following:

- Creates a `Note` class with required properties
- Adds a constructor with unrequired and required parameters
- Create a `fromJson` and `toJson` method for JSON serialization

**PS**: `factory` in dart lets us return an existing class instance instead of creating a new one. This process helps us improve application performance.

## Creating a Service

One of the best practices when writing reusable and maintainable code is to use a service. A service helps separate the application core logic from the UI. To do this, we need to navigate to the `utils` folder, and in this folder, create a `note_service.dart` file and add the snippet below:
{% gist
https://gist.github.com/Mr-Malomz/a6c034496354d0dd9c5f6bb766bfe8d9
%}

The snippet above does the following:

- Imports the required dependencies
- Creates a `NoteService` class with `client` and `db` properties to connect to the Appwrite instance and the database, respectively
- Creates an `_init` method that configures the `client` and the `db` instances using the `AppConstants` defined earlier and initializes the method in the class constructor
- Creates a `getAllNotes` method that uses the configured database’s `listDocuments` function to get a list of notes and converts the returned JSON to a list using the `Note.fromJson` method
- Creates a `createNote` method that takes in the `title` and `note` parameter and uses the `createDocument` function to create a note which creates an instance of the note using the parameters, passes in the `unique()` flag as the `documentId`; which tells Appwrite to auto-generate a unique ID, and passes in the new note by converting the Dart object to JSON using the `newNote.toJson()` method
- Creates a `getANote` method that takes in an `id` as a parameter and uses the `getDocument` function to get the matching note and converts the returned JSON to a Dart object using the `Note.fromJson` method
- Creates a `updateNote` method that takes in the `title`, `note`, and `id` parameter and uses the `updateDocument` function to update a note; this creates an instance of the note using the `title` and `note` parameter, passes in the `id` as the `documentId`, and passes in the updated note by converting the Dart object to JSON using the `newNote.toJson()` method
- Creates a `deleteNote` method that takes in an `id` as a parameter and uses the `deleteDocument` function to delete the matching note

**PS**: *The question mark* `*?*` *and bang* `*!*` *operator used tells the compiler to relax the* [*non-null constraint error*](https://dart.dev/null-safety) *(Meaning the parameter can be null)*


## Consuming the Service

With that done, we can start using the service to perform the required operation.
**Get All Notes**
To get the list of notes, we need to navigate to the `screens` folder, open `home.dart` and modify as shown below:
{% gist
https://gist.github.com/Mr-Malomz/19323ce81975f587dbae4796d442cda3
%}

The snippet above does the following:

- Imports the required dependencies
- Creates a `notes`, `_isLoading`, and `_isError` properties to manage the application state
- Creates a `_getNoteList` method to get the list of notes using the `getAllNotes` service and sets states accordingly
- Uses the `initState`  method to call the `_getNoteList` method when the object is inserted into the tree
- Conditionally renders the notes based on current states and pass in the current index of the `notes` as an argument to the `NoteCard`

The compiler will complain about a missing constructor property on the `NoteCard`  widget, which we will fix in the next step.

Next, we need to update the `NoteCard` widget by navigating to the `widgets` folder, and in this folder, open `card.dart` file and modify the snippet to the following:
{% gist
https://gist.github.com/Mr-Malomz/8ba22965898733cfb4208b8a22843bad
%}

The snippet above does the following:

- Imports the model class
- Creates a `note` property and adds it as a required parameter to the constructor
- Modifies the UI widget to show the `title` and `note` text dynamically

**Create Note**
To create a note, we need to modify `manage_note.dart` file in the `screens` folder to the following:

First, we need to import required dependencies and create a `_title` and `_note` variable to control inputs.
{% gist
https://gist.github.com/Mr-Malomz/62c08bbd161bd883385c2662708123ce
%}

Next, we need to create a `_createNote` method that uses the `createNote` service to create a note, navigate appropriately, set states, and uses the `snackbar` to show the action performed.

{% gist
https://gist.github.com/Mr-Malomz/5f3a015aa68af10a2b29d531e10a1841
%}

Finally, we need to modify the form widgets by adding controllers to control text on both input fields (**Line 31 & Line 74)** and call the `_createNote` function when the **save** button is pressed.
{% gist
https://gist.github.com/Mr-Malomz/0c291a21312828bf6cf87b49f94a92ca
%}


The snippet above does the following:

- Imports required dependencies
- Creates a `_title` and `_note` variable to control the inputs
- Creates a `_createNote` method that uses the `createNote` service to create a note, navigate appropriately, set states, and uses the `snackbar` to show the action performed
- **Line 88 & Line 131**: add controllers to control text on both input fields
- Calls the `_createNote` function when the save button is pressed

**Get A Note**
To get a note, we need to modify `card.dart` file in the `widgets` folder to the following:
{% gist
https://gist.github.com/Mr-Malomz/748d436a8fdbfa97ed70aa9a2ef71b03
%}

**Line 45** and **Line 60** above adds an `id` argument to the the `ManageNote` screen.

The compiler will complain about a missing constructor property on the `ManageNote` screen, which we will fix in the next step.

Next, modify the `manage_note.dart` file inside the `screens` folder by passing in the unique `id` of each note. We will use the `id` specified to get the details of a note.
{% gist
https://gist.github.com/Mr-Malomz/aa01f38f001a518eccfb587e82519ff4
%}

The snippet above does the following:

- Modifies the constructor to have an `id` property and create an `_isError` variable
- Creates a `_getANote` method to get a specific note using the `getANote` service, update inputs, and set states accordingly
- Uses the `initState`  method to check if it is an edit request or view request and call the `_getANote` method when the object is inserted into the tree
- Conditionally render the form based on current states

**Update Note**
To update a note, we need to modify the same `manage_note.dart` file to the following:
{% gist
https://gist.github.com/Mr-Malomz/7e947e8f02a7031abb1e8fc347dc894f
%}

The snippet above does the following:

- Creates an `_updateNote` method that uses the `updateNote` service to update a matching note, navigate appropriately, set states, and uses the `snackbar` to show the action performed
- **Line 110 - Line 114**: Checks if it is an “**edit operation**” or a “**create operation**” and use the corresponding function

**Delete Note**
To delete a note, we need to modify `home.dart` file in the `screens` folder to the following:
{% gist
https://gist.github.com/Mr-Malomz/87cbdc27f8ed668c27a8b94c4d111eef
%}

The snippet above does the following:

- Creates a `_deleteNote` method that takes in an `id`  parameter and uses the `deleteNote` service to delete a note, refreshes the screen using `pushReplacement` method, and uses the `snackbar` to show the action performed
- Passes in the `deleteNote` as an argument to the `NoteCard`

The compiler will complain about a missing constructor property on the `NoteCard` widget, which we will fix in the next step.

Next, we need to update the `NoteCard` widget by navigating to the `widgets` folder, and in this folder, open `card.dart` file and modify the snippet to the following:
{% gist
https://gist.github.com/Mr-Malomz/7f2fe407451a85b5f27a5631dc7d575b
%}

The snippet above does the following:

- Creates an `onDelete` function property that takes in an `id` parameter and adds it as a required parameter to the constructor
- Modifies the delete button to call the `onDelete` function and pass in the `id`

**Complete home.dart code:**
{% gist
https://gist.github.com/Mr-Malomz/ea7e3ffda2a23578c452bffe38664baa
%}

**Complete manage_note.dart code:**
{% gist
https://gist.github.com/Mr-Malomz/f251717f852a310c5735ce6f6f792bff
%}

**Complete card.dart code:**
{% gist
https://gist.github.com/Mr-Malomz/3d7d37776d8cfdef68cf6f755484b9cf
%}

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


![running app](https://media.giphy.com/media/g5wr9Zq3t6OXtMLDCl/giphy.gif)


We can also validate the entries by navigating to the **Database** section of Appwrite’s management console.

![data on Appwrite](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650411454191_Screenshot+2022-04-19+at+20.08.39.png)

# Conclusion

This post discussed how to create a note-taking app using Flutter and Appwrite. The Appwrite platform ships with services that speed up development processes. Try it out today and focus on what matters while Appwrite takes care of the tricky part.

These resources might 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)
- [Flutter null safety.](https://dart.dev/null-safety)