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 installed
- Xcode with developer account (for Mac users)
- Either IOS Simulator, Android Studio, or Chrome web browser to run our application
- Docker installation
Appwrite 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.
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/note_app.git && cd note_app
The complete source code is also available on the dev branch of the same repository.
Folder Structure
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:
flutter pub get
Then run the project using the command below:
flutter run
The command above will run the application on the selected device.
Setting up Appwrite
Appwrite 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)
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)
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)
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:
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.
http://localhost:80
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.
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.
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.
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.
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.
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.
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
Next, open the project directory on 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.
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
Next, we need to modify the AndroidManifext.xml as shown below:
<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.
We can learn more about Appwrite’s Flutter platform-specific support here.
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.
appwrite: ^4.0.2
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:
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.
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:
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";
}
Android
We can connect our Android emulator to the system’s IP using 10.0.2.2 IP address.
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:
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
Noteclass with required properties - Adds a constructor with unrequired and required parameters
- Create a
fromJsonandtoJsonmethod 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:
The snippet above does the following:
- Imports the required dependencies
- Creates a
NoteServiceclass withclientanddbproperties to connect to the Appwrite instance and the database, respectively - Creates an
_initmethod that configures theclientand thedbinstances using theAppConstantsdefined earlier and initializes the method in the class constructor - Creates a
getAllNotesmethod that uses the configured database’slistDocumentsfunction to get a list of notes and converts the returned JSON to a list using theNote.fromJsonmethod - Creates a
createNotemethod that takes in thetitleandnoteparameter and uses thecreateDocumentfunction to create a note which creates an instance of the note using the parameters, passes in theunique()flag as thedocumentId; which tells Appwrite to auto-generate a unique ID, and passes in the new note by converting the Dart object to JSON using thenewNote.toJson()method - Creates a
getANotemethod that takes in anidas a parameter and uses thegetDocumentfunction to get the matching note and converts the returned JSON to a Dart object using theNote.fromJsonmethod - Creates a
updateNotemethod that takes in thetitle,note, andidparameter and uses theupdateDocumentfunction to update a note; this creates an instance of the note using thetitleandnoteparameter, passes in theidas thedocumentId, and passes in the updated note by converting the Dart object to JSON using thenewNote.toJson()method - Creates a
deleteNotemethod that takes in anidas a parameter and uses thedeleteDocumentfunction to delete the matching note
PS: The question mark *?* and bang *!* operator used tells the compiler to relax the non-null constraint error (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:
The snippet above does the following:
- Imports the required dependencies
- Creates a
notes,_isLoading, and_isErrorproperties to manage the application state - Creates a
_getNoteListmethod to get the list of notes using thegetAllNotesservice and sets states accordingly - Uses the
initStatemethod to call the_getNoteListmethod when the object is inserted into the tree - Conditionally renders the notes based on current states and pass in the current index of the
notesas an argument to theNoteCard
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:
The snippet above does the following:
- Imports the model class
- Creates a
noteproperty and adds it as a required parameter to the constructor - Modifies the UI widget to show the
titleandnotetext 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.
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.
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.
The snippet above does the following:
- Imports required dependencies
- Creates a
_titleand_notevariable to control the inputs - Creates a
_createNotemethod that uses thecreateNoteservice to create a note, navigate appropriately, set states, and uses thesnackbarto show the action performed - Line 88 & Line 131: add controllers to control text on both input fields
- Calls the
_createNotefunction 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:
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.
The snippet above does the following:
- Modifies the constructor to have an
idproperty and create an_isErrorvariable - Creates a
_getANotemethod to get a specific note using thegetANoteservice, update inputs, and set states accordingly - Uses the
initStatemethod to check if it is an edit request or view request and call the_getANotemethod 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:
The snippet above does the following:
- Creates an
_updateNotemethod that uses theupdateNoteservice to update a matching note, navigate appropriately, set states, and uses thesnackbarto 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:
The snippet above does the following:
- Creates a
_deleteNotemethod that takes in anidparameter and uses thedeleteNoteservice to delete a note, refreshes the screen usingpushReplacementmethod, and uses thesnackbarto show the action performed - Passes in the
deleteNoteas an argument to theNoteCard
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:
The snippet above does the following:
- Creates an
onDeletefunction property that takes in anidparameter and adds it as a required parameter to the constructor - Modifies the delete button to call the
onDeletefunction and pass in theid
Complete home.dart code:
Complete manage_note.dart code:
Complete card.dart code:
With that done, we restart the application using the code editor or run the command below:
flutter run
We can also validate the entries by navigating to the Database section of Appwrite’s management console.
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:



























Top comments (0)