Create a Notes App with flutter

Published on: Jun 15, 2021

Create a Note-taking App in Flutter

Flutter is a declarative framework that requires programming in Dart. Flutter is suitable for creating simple apps like Note-taking, Event-registration, etc.

Pre-requisites: Knowledge of basic Dart and Flutter. Read about Flutter and Dart.

With basic concepts like Widgets and Material design we can create simple apps very fast and easily in Flutter as in Flutter we can use tons of pre-designed widgets to create almost every popular design using in the modern design world.

In this tutorial, we discuss creating a simple note-taking app. The note-taking app we are going to create provides options like create, save, update and delete notes.

Our note-taking app contains two screens

  • Home screen to display all saved notes
  • Notes edit screen to create new notes or edit saved notes

Create Flutter App

Before creating a Flutter app please make sure you have installed flutter-sdk and dart-sdk. If not follow the instructions to install flutter.

Create a raw flutter app from the terminal. Run the following command and pass any name (to join more than single sting use only underscore)

1flutter create notes_app
2

Go to root directory of notes_app and locate main.dart in lib folder. This is where our app starts execution by calling the main() function. You can find some code here which displays the welcome screen.

Now to see the app in an emulator or on a physical device run the below command.

1cd notes_app/
2flutter run
3

For the initial run, it takes some time to install the app on the device, and later builds will be fast. If you encounter any error run flutter doctor -v for additional information and make sure all necessary items are checked.


What a Note should like and contain?

A simple note must have a title and the content which can be edited as many times as possible. We can also add color to note for look and feel.

To store notes we use Sqflite (a plugin to mimic SQL database in Flutter). Each note can be stored as a single row in the database with fields id, title, content, color.

Create a file note.dart inside lib/models. Add a class Note to store note as an object which can be converted later as a Map object to store in the database.

models/note.dart

1class Note {
2    int id;
3    String title;
4    String content;
5    String noteColor;
6
7    Note({
8        this.id = null,
9        this.title = "Note",
10        this.content = "Text",
11        this.noteColor = 'red'
12    });
13
14    Map<String, dynamic> toMap() {
15        Map<String, dynamic> data = Map<String, dynamic>();
16        if (id != null) {
17            data['id'] = id;
18        }
19        data['title'] = title;
20        data['content'] = content;
21        data['noteColor'] = noteColor;
22        return data;
23    }
24
25     toString() {
26        return {
27            'id': id,
28            'title': title,
29            'content': content,
30            'noteColor': noteColor,
31        }.toString();
32    }
33}
34

This Note class has attributes

  • id (primary key) - an identifier to store unique note objects in the database
  • title - the title of the note
  • content - content of the note
  • noteColor - the color of the note

toMap() returns note as an object to store in the database.

For note colors, add another file called theme/note_colors.dart inside lib/theme.

theme/note_colors.dart

1const NoteColors = {
2    'red': {'l': 0xFFFFCDD2,'b': 0xFFE57373},
3    'pink': {'l': 0xFFF8BBD0, 'b': 0xFFF06292},
4    'purple': {'l': 0xFFE1BEE7, 'b': 0xFFBA68C8},
5    'deepPurple': {'l': 0xFFD1C4E9, 'b': 0xFF9575CD},
6    'indigo': {'l': 0xFFC5CAE9, 'b': 0xFF7986CB},
7    'blue': {'l': 0xFFBBDEFB, 'b': 0xFF64B5F6},
8    'lightBlue': {'l': 0xFFB3E5FC, 'b': 0xFF4FC3F7},
9    'cyan': {'l': 0xFFB2EBF2, 'b': 0xFF4DD0E1},
10    'teal': {'l': 0xFFB2DFDB, 'b': 0xFF4DB6AC},
11    'green': {'l': 0xFFC8E6C9, 'b': 0xFF81C784},
12    'lightGreen': {'l': 0xFFDCEDC8, 'b': 0xFFAED581},
13    'lime': {'l': 0xFFF0F4C3, 'b': 0xFFDCE775},
14    'yellow': {'l': 0xFFFFF9C4, 'b': 0xFFFFF176},
15    'amber': {'l': 0xFFFFECB3, 'b': 0xFFFFD54F},
16    'orange': {'l': 0xFFFFE0B2, 'b': 0xFFFFB74D},
17    'deepOrange': {'l': 0xFFFFCCBC, 'b': 0xFFFF8A65},
18    'brown': {'l': 0xFFD7CCCB, 'b': 0xFFA1887F},
19    'blueGray': {'l': 0xFFCFD8DC, 'b': 0xFF90A4AE},
20};
21

Each color name ('k') is a key and each key ('k') has two colors 'l' and 'b', where 'l' is a light color and 'b' is the bright color of this 'k' color. The light and bright colors are used to display a note in the UI which we discuss later. 'k' is the color name we store in the database.


Store notes in the database

Now to store notes on the database we use sqflite plugin. Install sqflite by adding dependency in pubspec.yaml.

1dependencies:
2  flutter:
3    sdk: flutter
4  sqflite: ^1.3.0
5

Now in terminal run flutter pub get to install or update dependencies in pubspec.yaml.

To handle database operations we write different functions for different operations like read, write, update and delete. Create notes_database.dart inside models and add a class to handle different operations

models/notes_database.dart

1import 'package:sqflite/sqflite.dart';
2
3import 'note.dart';
4
5class NotesDatabase {
6    static final _name = "NotesDatabase.db";
7    static final _version = 1;
8
9    Database database;
10    static final tableName = 'notes';
11
12    initDatabase() async {
13        database = await openDatabase(
14            _name,
15            version: _version,
16            onCreate: (Database db, int version) async {
17                await db.execute(
18                    '''CREATE TABLE $tableName (
19                    id INTEGER PRIMARY KEY AUTOINCREMENT,
20                    title TEXT,
21                    content TEXT,
22                    noteColor TEXT
23                    )'''
24                );
25            }
26        );
27    }
28
29    Future<int> insertNote(Note note) async {
30        return await database.insert(tableName,
31            note.toMap(),
32            conflictAlgorithm: ConflictAlgorithm.replace
33        );
34    }
35
36    Future<int> updateNote(Note note) async {
37        return await database.update(tableName, note.toMap(),
38            where: 'id = ?',
39            whereArgs: [note.id],
40            conflictAlgorithm: ConflictAlgorithm.replace
41        );
42    }
43
44    Future<List<Map<String, dynamic>>> getAllNotes() async {
45        return await database.query(tableName);
46    }
47
48    Future<Map<String, dynamic>> getNotes(int id) async {
49        var result = await database.query(tableName,
50            where: 'id = ?',
51            whereArgs: [id]
52        );
53
54        if (result.length > 0) {
55            return result.first;
56        }
57
58        return null;
59    }
60
61    Future<int> deleteNote(int id) async {
62        return await database.delete(tableName,
63            where: 'id = ?',
64            whereArgs: [id]
65        );
66    }
67
68    closeDatabase() async {
69        await database.close();
70    }
71}
72

First, we need to create a table in the database with some schema. Inside initDatabase(), we are calling openDatabase() to create database and table or open existing database and table by passing parameters _name (name of the database) and _version where _name = NotesDatabse.db is the name of the database and we can maintain different versions of the database through _version.

If there is no database with a specified name, onCreate callback is called to create a database with table and schema. Above we create a table with tableName = notes and initial schema with required fields like id, title, content, and noteColor to store a note object.

openDatabase() is an async operation and returns Database object reference which points to the created/existed database. We store this reference as database of type class Database.

Other functions insertNote, updateNote, getNotes and deleteNotes handles different database operations. Read more about how to perform different operations in sqflite.

As we cannot store Note as a class object we convert Note object members to a Map object by calling Note.toMap() which returns a Map object which sqflite map fields and values to store in the database. And Sqflite returns data as Map objects the way we pass it to insert rows in the database.

We have added logic to maintain notes in the database. But we have not done anything in UI to interact for maintaining notes.

Add Home Screen

Now create a file called home.dart in lib/screens. This home.dart serves as the Home screen of our app. Add following code to home.dart

screens/home.dart

1import 'package:flutter/material.dart';
2
3const c1 = 0xFFFDFFFC, c2 = 0xFFFF595E, c3 = 0xFF374B4A, c4 = 0xFF00B1CC, c5 = 0xFFFFD65C, c6 = 0xFFB9CACA,
4    c7 = 0x80374B4A, c8 = 0x3300B1CC, c9 = 0xCCFF595E;
5
6// Home Screen
7class Home extends StatefulWidget{
8    
9    _Home createState() => _Home();
10}
11
12class _Home extends State<Home> {
13    
14    Widget build(BuildContext context) {
15        return MaterialApp(
16            title: 'Super Note',
17            home: Scaffold(
18                backgroundColor: Color(c6),
19                appBar: AppBar(
20                    automaticallyImplyLeading: false,
21                    backgroundColor: const Color(c2),
22                    brightness: Brightness.dark,
23
24                    title: Text(
25                        'Super Note',
26                        style: TextStyle(
27                            color: const Color(c5),
28                        ),
29                    ),
30                ),
31
32                //Floating Button
33                floatingActionButton: FloatingActionButton(
34                    child: const Icon(
35                        Icons.add,
36                        color: const Color(c5),
37                    ),
38                    tooltip: 'New Notes',
39                    backgroundColor: const Color(c4),
40                    onPressed: () => {},
41                ),
42            ),
43        );
44    }
45}
46

There are some color constants defined at the top which will be used across the app. The color format in Flutter is different from normal Hex. In normal Hex format, we provide opacity at last but in Flutter we have to provide opacity at first.

Here we are creating a Home widget as StatefulWidget keeping in mind that we need to maintain the state. Every custom widget must override build method and return a widget. MaterialApp widget gives child widgets material look and we must declare required attributes. Scaffold widget is a common material design concept that provides appbar, floating button, drawer, body, etc.

The Home screen displays all notes stored in the database. We discuss later displaying notes in the Home screen after creating notes in the Edit screen.

To display our Home screen as default screen in our app call Home() widget inside MyApp in main.dart

main.dart

1import 'package:flutter/material.dart';
2
3import './screens/home.dart';
4
5void main() => runApp(MyApp());
6
7class MyApp extends StatelessWidget {
8  
9  Widget build(BuildContext context) {
10    return MaterialApp(
11      home: Home(),
12    );
13  }
14}
15

To see the changes in the app, in the flutter running environment press r to hot reload or R restart of the app.

Home Screen

The Floating action button at the bottom-right will take us to the Edit screen to create a new note. To add navigation from Home to Edit, first create a Edit Screen Widget in notes_edit.dart inside lib/screens. For now, add a simple UI for the Edit screen like below because we just need a widget to route from Home to Edit screen.

screens/notes_edit.dart

1import 'package:flutter/material.dart';
2
3const c1 = 0xFFFDFFFC, c2 = 0xFFFF595E, c3 = 0xFF374B4A, c4 = 0xFF00B1CC, c5 = 0xFFFFD65C, c6 = 0xFFB9CACA,
4            c7 = 0x80374B4A;
5
6class NotesEdit extends StatefulWidget {
7    _NotesEdit createState() => _NotesEdit();
8}
9
10class _NotesEdit extends State<NotesEdit> {
11    
12    Widget build(BuildContext context) {
13        return MaterialApp(
14            title: 'Edit Screen',
15            home: Text(
16                'Edit'
17            ),
18        );
19    }
20}
21

NotesEdit widget is the main widget for the Edit screen. We call this widget in navigation.

Navigation from Home to Edit

Add navigation from Home to Edit when pressed floating-action-buttton. Call Navigation.push() for the EditNotes widget. In home.dart add navigation in onPressed() event of floating-action-button.

1import './notes_edit.dart';
2
1//Floating Button
2floatingActionButton: FloatingActionButton(
3    child: const Icon(
4        Icons.add,
5        color: const Color(c5),
6    ),
7    tooltip: 'New Notes',
8    backgroundColor: const Color(c4),
9    // Go to Edit screen
10    onPressed: () {
11      Navigator.push(
12        context,
13        MaterialPageRoute(builder: (context) => NotesEdit()),
14      );
15    }
16),
17

Change Edit Screen

Change Edit screen UI for creating a new note.

screens/notes_edit.dart

1import 'package:flutter/material.dart';
2
3import '../models/note.dart';
4import '../models/notes_database.dart';
5import '../theme/note_colors.dart';
6
7const c1 = 0xFFFDFFFC, c2 = 0xFFFF595E, c3 = 0xFF374B4A, c4 = 0xFF00B1CC, c5 = 0xFFFFD65C, c6 = 0xFFB9CACA,
8    c7 = 0x80374B4A;
9
10class NotesEdit extends StatefulWidget {
11    _NotesEdit createState() => _NotesEdit();
12}
13
14class _NotesEdit extends State<NotesEdit> {
15    String noteTitle = '';
16    String noteContent = '';
17    String noteColor = 'red';
18
19    TextEditingController _titleTextController = TextEditingController();
20    TextEditingController _contentTextController = TextEditingController();
21
22    void handleTitleTextChange() {
23        setState(() {
24            noteTitle = _titleTextController.text.trim();
25        });
26    }
27
28    void handleNoteTextChange() {
29        setState(() {
30            noteContent = _contentTextController.text.trim();
31        });
32    }
33
34    
35    void initState() {
36        super.initState();
37        _titleTextController.addListener(handleTitleTextChange);
38        _contentTextController.addListener(handleNoteTextChange);
39    }
40
41    
42    void dispose() {
43        _titleTextController.dispose();
44        _contentTextController.dispose();
45        super.dispose();
46    }
47
48    
49    Widget build(BuildContext context) {
50        return Scaffold(
51            backgroundColor: Color(NoteColors[this.noteColor]['l']),
52            appBar: AppBar(
53                backgroundColor: Color(NoteColors[this.noteColor]['b']),
54
55                leading: IconButton(
56                    icon: const Icon(
57                        Icons.arrow_back,
58                        color: const Color(c1),
59                    ),
60                    tooltip: 'Back',
61                    onPressed: () => {},
62                ),
63
64                title: NoteTitleEntry(_titleTextController),
65            ),
66
67            body: NoteEntry(_contentTextController),
68        );
69    }
70}
71

In the above NotesEdit widget, the state variables noteTitle, noteContent and noteColor are initialized to default values for now. noteTitel is to store title of the note, noteContent is to store note content andnoteColor is color of the color, light and bright colors of the noteColor are used as backgroundColor for appBar and Scaffold respectively.

Also there are two TextEditingController defined which are used to controll TextField values for noteTitle and noteContent. These two text controller are attached with listeners in iniitState(). These listeneres listen to changes and updates text values in state. _titleTextController handles and updates text value for noteTitle and _contentTextController handles noteContent.

The title of the appBar is set to a widget NoteTitleEntry which handles displaying and editing of the title.

1class NoteTitleEntry extends StatelessWidget {
2    final _textFieldController;
3
4    NoteTitleEntry(this._textFieldController);
5
6    
7    Widget build(BuildContext context) {
8        return TextField(
9            controller: _textFieldController,
10            decoration: InputDecoration(
11                border: InputBorder.none,
12                focusedBorder: InputBorder.none,
13                enabledBorder: InputBorder.none,
14                errorBorder: InputBorder.none,
15                disabledBorder: InputBorder.none,
16                contentPadding: EdgeInsets.all(0),
17                counter: null,
18                counterText: "",
19                hintText: 'Title',
20                hintStyle: TextStyle(
21                    fontSize: 21,
22                    fontWeight: FontWeight.bold,
23                    height: 1.5,
24                ),
25            ),
26            maxLength: 31,
27            maxLines: 1,
28            style: TextStyle(
29                fontSize: 21,
30                fontWeight: FontWeight.bold,
31                height: 1.5,
32                color: Color(c1),
33            ),
34            textCapitalization: TextCapitalization.words,
35        );
36    }
37}
38

In the TextField, the controller is set to _textFieldController which is passed from parent widget as _titleTextController**.

Similarly, content of the notes is handled by another widget NoteEntry.

1class NoteEntry extends StatelessWidget {
2    final _textFieldController;
3
4    NoteEntry(this._textFieldController);
5
6    
7    Widget build(BuildContext context) {
8        return Container(
9            height: MediaQuery.of(context).size.height,
10            padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
11            child: TextField(
12                controller: _textFieldController,
13                maxLines: null,
14                textCapitalization: TextCapitalization.sentences,
15                decoration: null,
16                style: TextStyle(
17                    fontSize: 19,
18                    height: 1.5,
19                ),
20            ),
21        );
22    }
23}
24

Here also controller of TextField is set to _textFieldController which is passed from parent wdiget as _contentTextController.

After adding all these widgets, the Edit screen would look like

Edit screen

Add a Color palette to select Note color

We will add a color palette to select note color and store the value in noteColor. For color palette, add an icon in appBar actions which on press shows a Dialog box with different colors.

In NotesEdit add color palette button

1actions: [
2    IconButton(
3        icon: const Icon(
4            Icons.color_lens,
5            color: const Color(c1),
6        ),
7        tooltip: 'Color Palette',
8        onPressed: () => handleColor(context),
9    ),
10],
11

For this button, onPressed event calls handleColor() function which shows a color palette and store selected value in noteColor variable. Define handleColor() inside _NotesEdit

1void handleColor(currentContext) {
2    showDialog(
3        context: currentContext,
4        builder: (context) => ColorPalette(
5            parentContext: currentContext,
6        ),
7    ).then((colorName) {
8        if (colorName != null) {
9            setState(() {
10                noteColor = colorName;
11            });
12        }
13    });
14}
15

This handleColor() calls widget ColorPalette which is a Dialog box and returns selected color value. Add ColorPalette widget to show different colors and return selected color

1class ColorPalette extends StatelessWidget {
2    final parentContext;
3
4    const ColorPalette({
5         this.parentContext,
6    });
7
8    
9    Widget build(BuildContext context) {
10        return Dialog(
11            backgroundColor: Color(c1),
12            clipBehavior: Clip.hardEdge,
13            insetPadding: EdgeInsets.all(MediaQuery.of(context).size.width * 0.03),
14            shape: RoundedRectangleBorder(
15                borderRadius: BorderRadius.circular(2),
16            ),
17            child: Container(
18                padding: EdgeInsets.all(8),
19                child: Wrap(
20                    alignment: WrapAlignment.start,
21                    spacing: MediaQuery.of(context).size.width * 0.02,
22                    runSpacing: MediaQuery.of(context).size.width * 0.02,
23                    children: NoteColors.entries.map((entry) {
24                        return GestureDetector(
25                            onTap: () => Navigator.of(context).pop(entry.key),
26                            child: Container(
27                                width: MediaQuery.of(context).size.width * 0.12,
28                                height: MediaQuery.of(context).size.width * 0.12,
29                                decoration: BoxDecoration(
30                                    borderRadius: BorderRadius.circular(MediaQuery.of(context).size.width * 0.06),
31                                    color: Color(entry.value['b']),
32                                ),
33                            ),
34                        );
35                    }).toList(),
36                ),
37            ),
38        );
39    }
40}
41

As we already stored different colors in NoteColors Map object, we iterate this object and fill the color palette with bright colors.

Color Palette

Save notes

We have everything to save notes in the database. We save a note in the database if backButton is pressed at the top. Now add a function to handle backButton onPressed.

1void handleBackButton() async {
2    if (noteTitle.length == 0) {
3        // Go Back without saving
4        if (noteContent.length == 0) {
5            Navigator.pop(context);
6            return;
7        }
8        else {
9            String title = noteContent.split('\n')[0];
10            if (title.length > 31) {
11                title = title.substring(0, 31);
12            }
13            setState(() {
14                noteTitle = title;
15            });
16        }
17    }
18
19    // Save New note
20    Note noteObj = Note(
21        title: noteTitle,
22        content: noteContent,
23        noteColor: noteColor
24    );
25    try {
26        await _insertNote(noteObj);
27    } catch (e) {
28        print('Error inserting row');
29    } finally {
30        Navigator.pop(context);
31        return;
32    }
33}
34

This function calls _insertNote() which saves the note object in the database.

1Future<void> _insertNote(Note note) async {
2  NotesDatabase notesDb = NotesDatabase();
3  await notesDb.initDatabase();
4  int result = await notesDb.insertNote(note);
5  await notesDb.closeDatabase();
6}
7

We have saved notes in the database, now in the Home screen, display the saved notes in list view.


Show saved notes on the Home screen

We can retrieve saved notes from the database and we use that retrieved data to show a note as a list on the Home screen. As retrieving data from the database is an async task and we need to have data before building the Home widget, we use FutureBuilder.

1Future<List<Map<String, dynamic>>> readDatabase() async {
2    try {
3      NotesDatabase notesDb = NotesDatabase();
4      await notesDb.initDatabase();
5      List<Map> notesList = await notesDb.getAllNotes();
6      await notesDb.closeDatabase();
7      List<Map<String, dynamic>> notesData = List<Map<String, dynamic>>.from(notesList);
8        notesData.sort((a, b) => (a['title']).compareTo(b['title']));
9      return notesData;
10    } catch(e) {
11        print('Error retrieving notes');
12        return [{}];
13    }
14}
15

This function reads all saved notes in the database and returns them as Future objects. We call this function in FutureBuilder and it builds the note list which displays each notes as a list.

Before that add necessary imports in home.dart to handle the database, to store note object and colors.

1import '../models/note.dart';
2import '../models/notes_database.dart';
3import '../theme/note_colors.dart';
4

Store read notes from database in state and define other state variables

1List<Map<String, dynamic>> notesData;
2List<int> selectedNoteIds = [];
3

notesData stores all notes data read from database and selectedNoteIds will have a list of selected notes when a note is selected in Home.

1body: FutureBuilder(
2    future: readDatabase(),
3    builder: (context, snapshot) {
4        if (snapshot.hasData) {
5            notesData = snapshot.data;
6            return Stack(
7                children: <Widget>[
8                    // Display Notes
9                    AllNoteLists(
10                        snapshot.data,
11                        this.selectedNoteIds,
12                        afterNavigatorPop,
13                        handleNoteListLongPress,
14                        handleNoteListTapAfterSelect,
15                    ),
16                ],
17            );
18        } else if (snapshot.hasError) {
19            print('Error reading database');
20        } else {
21            return Center(
22                child: CircularProgressIndicator(
23                    backgroundColor: Color(c3),
24                ),
25            );
26        }
27    }
28),
29

Here before building the widget we read the data from the database and builds a list of note widgets to display on the Home screen by calling AllNoteLists widget. We also pass different callback functions to AllNoteLists to handles cases like the long selection of note, deselect a note, etc.

Define all these functions inside _Home

1// Render the screen and update changes
2void afterNavigatorPop() {
3    setState(() {});
4}
5
6// Long Press handler to display bottom bar
7void handleNoteListLongPress(int id) {
8    setState(() {
9        if (selectedNoteIds.contains(id) == false) {
10            selectedNoteIds.add(id);
11        }
12    });
13}
14
15// Remove selection after long press
16void handleNoteListTapAfterSelect(int id) {
17    setState(() {
18        if (selectedNoteIds.contains(id) == true) {
19            selectedNoteIds.remove(id);
20        }
21    });
22}
23
24// Delete Note/Notes
25void handleDelete() async {
26    try {
27        NotesDatabase notesDb = NotesDatabase();
28        await notesDb.initDatabase();
29        for (int id in selectedNoteIds) {
30            int result = await notesDb.deleteNote(id);
31        }
32        await notesDb.closeDatabase();
33    } catch (e) {
34
35    } finally {
36        setState(() {
37            selectedNoteIds = [];
38        });
39    }
40}
41

Define AllNoteLists widget which gets arguments from parent widget including note data and callback functions to handle

1// Display all notes
2class AllNoteLists extends StatelessWidget {
3    final data;
4    final selectedNoteIds;
5    final afterNavigatorPop;
6    final handleNoteListLongPress;
7    final handleNoteListTapAfterSelect;
8
9    AllNoteLists(
10        this.data,
11        this.selectedNoteIds,
12        this.afterNavigatorPop,
13        this.handleNoteListLongPress,
14        this.handleNoteListTapAfterSelect,
15    );
16
17    
18    Widget build(BuildContext context) {
19        return ListView.builder(
20            itemCount: data.length,
21            itemBuilder: (context, index) {
22                dynamic item = data[index];
23                return DisplayNotes(
24                    item,
25                    selectedNoteIds,
26                    (selectedNoteIds.contains(item['id']) == false? false: true),
27                    afterNavigatorPop,
28                    handleNoteListLongPress,
29                    handleNoteListTapAfterSelect,
30                );
31            }
32        );
33    }
34}
35
36
37// A Note view showing title, first line of note and color
38class DisplayNotes extends StatelessWidget {
39    final notesData;
40    final selectedNoteIds;
41    final selectedNote;
42    final callAfterNavigatorPop;
43    final handleNoteListLongPress;
44    final handleNoteListTapAfterSelect;
45
46    DisplayNotes(
47        this.notesData,
48        this.selectedNoteIds,
49        this.selectedNote,
50        this.callAfterNavigatorPop,
51        this.handleNoteListLongPress,
52        this.handleNoteListTapAfterSelect,
53    );
54
55    
56    Widget build(BuildContext context) {
57        return Padding(
58            padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 2.0),
59            child: Material(
60                elevation: 1,
61                color: (selectedNote == false? Color(c1): Color(c8)),
62                clipBehavior: Clip.hardEdge,
63                borderRadius: BorderRadius.circular(5.0),
64                child: InkWell(
65                    onTap: () {
66                        if (selectedNote == false) {
67                            if (selectedNoteIds.length == 0) {
68                                // Go to edit screen to update notes
69                            }
70                            else {
71                                handleNoteListLongPress(notesData['id']);
72                            }
73                        }
74                        else {
75                            handleNoteListTapAfterSelect(notesData['id']);
76                        }
77                    },
78
79                    onLongPress: () {
80                        handleNoteListLongPress(notesData['id']);
81                    },
82                    child: Container(
83                        width: MediaQuery.of(context).size.width,
84                        padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
85                        child: Row(
86                            children: <Widget>[
87                                Expanded(
88                                    flex: 1,
89                                    child: Column(
90                                        mainAxisAlignment: MainAxisAlignment.center,
91                                        crossAxisAlignment: CrossAxisAlignment.center,
92                                        mainAxisSize: MainAxisSize.min,
93                                        children: <Widget>[
94                                            Container(
95                                                alignment: Alignment.center,
96                                                decoration: BoxDecoration(
97                                                    color: (selectedNote == false?
98                                                        Color(NoteColors[notesData['noteColor']]['b']):
99                                                        Color(c9)
100                                                    ),
101                                                    shape: BoxShape.circle,
102                                                ),
103                                                child: Padding(
104                                                    padding: EdgeInsets.all(10),
105                                                    child: (
106                                                        selectedNote == false?
107                                                        Text(
108                                                            notesData['title'][0],
109                                                            style: TextStyle(
110                                                                color: Color(c1),
111                                                                fontSize: 21,
112                                                            ),
113                                                        ):
114                                                        Icon(
115                                                            Icons.check,
116                                                            color: Color(c1),
117                                                            size: 21,
118                                                        )
119                                                    ),
120                                                ),
121                                            ),
122                                        ],
123                                    ),
124                                ),
125
126                                Expanded(
127                                    flex: 5,
128                                    child: Column(
129                                        mainAxisAlignment: MainAxisAlignment.spaceAround,
130                                        crossAxisAlignment: CrossAxisAlignment.start,
131                                        mainAxisSize: MainAxisSize.min,
132                                        children:<Widget>[
133                                            Text(
134                                                notesData['title'] != null? notesData['title']: "",
135                                                style: TextStyle(
136                                                    color: Color(c3),
137                                                    fontSize: 18,
138                                                    fontWeight: FontWeight.bold,
139                                                ),
140                                            ),
141
142                                            Container(
143                                                height: 3,
144                                            ),
145
146                                            Text(
147                                                notesData['content'] != null? notesData['content'].split('\n')[0]: "",
148                                                style: TextStyle(
149                                                    color: Color(c7),
150                                                    fontSize: 16,
151                                                    fontWeight: FontWeight.w300,
152                                                ),
153                                            ),
154                                        ],
155                                    ),
156                                ),
157                            ],
158                        ),
159                    ),
160                ),
161            ),
162        );
163    }
164}
165

AllNoteLists builds a list of notes from the Map of a list of notes. In ListView builder it passes each note extracted data to another widget DisplayNotes which represents each note.

Now Home screen displays all notes stored as

Home screen

Long press on the note to select the note. And if a note is selected we can add a delete action to delete the selected notes. Add Delete button at appBar actions which shows delete icon only if any note is selected.

1actions: [
2    (selectedNoteIds.length > 0?
3        IconButton(
4            icon: const Icon(
5                Icons.delete,
6                color: const Color(c1),
7            ),
8            tooltip: 'Delete',
9            onPressed: () => handleDelete(),
10        ):
11        Container()
12    ),
13],
14

Define hanldeDelete() which deletes all selected notes from database.

1// Delete Notes
2void handleDelete() async {
3    try {
4        NotesDatabase notesDb = NotesDatabase();
5        await notesDb.initDatabase();
6        for (int id in selectedNoteIds) {
7            int result = await notesDb.deleteNote(id);
8        }
9        await notesDb.closeDatabase();
10    } catch (e) {
11        print('Cannot delete notes');
12    } finally {
13        setState(() {
14            selectedNoteIds = [];
15        });
16    }
17}
18

For notes, we have added create, read and delete functions. Now we will add an update function to edit already stored notes.

Update notes

For this, we can use the Edit screen to update the notes as it has all features to create notes which are similar for update notes also. We have to tell the Edit screen which type of notes operations we doing either create or update notes. To inform the Edit screen we can pass arguments to NotesEdit widget while routing about the type of action and notes data if the action is to update. Change NotesEdit widget to accept arguments telling the type of action and necessary data.

1class NotesEdit extends StatefulWidget {
2    final args;
3
4    const NotesEdit(this.args);
5    _NotesEdit createState() => _NotesEdit();
6}
7

args stores parameters passed from parent widget.

Change navigation arguments for NotesEdit in the floating-action-button in _Home.

1//Floating Button
2floatingActionButton: FloatingActionButton(
3    child: const Icon(
4        Icons.add,
5        color: const Color(c5),
6    ),
7    tooltip: 'New Notes',
8    backgroundColor: const Color(c4),
9    onPressed: () {
10      Navigator.push(
11        context,
12        MaterialPageRoute(builder: (context) => NotesEdit(['new', {}])),
13      );
14    }
15),
16

As floating-button triggers the creation of a new note, we pass argument new to inform NotesEdit that operation in creation of note.

When tapped on a note on the Home screen we navigate to the Edit screen to update the note. For this add navigation from Home to Edit when tapped on the note in DisplayNotes.

1child: InkWell(
2onTap: () {
3    if (selectedNote == false) {
4        if (selectedNoteIds.length == 0) {
5            Navigator.push(
6                context,
7        MaterialPageRoute(
8          builder: (context) => NotesEdit(['update', notesData]),
9        ),
10            ).then((dynamic value) {
11                    callAfterNavigatorPop();
12                }
13            );
14            return;
15        }
16        else {
17            handleNoteListLongPress(notesData['id']);
18        }
19    }
20    else {
21        handleNoteListTapAfterSelect(notesData['id']);
22    }
23},
24

We pass update and notesData to the Edit screen stating the operation is updating notes and note data to fill in the Edit screen.

Change NotesEdit widget in notes_edit.dart for handling update note operation.

1
2void initState() {
3    super.initState();
4    noteTitle = (widget.args[0] == 'new'? '': widget.args[1]['title']);
5    noteContent = (widget.args[0] == 'new'? '': widget.args[1]['content']);
6    noteColor = (widget.args[0] == 'new'? 'red': widget.args[1]['noteColor']);
7
8    _titleTextController.text = (widget.args[0] == 'new'? '': widget.args[1]['title']);
9    _contentTextController.text = (widget.args[0] == 'new'? '': widget.args[1]['content']);
10    _titleTextController.addListener(handleTitleTextChange);
11    _contentTextController.addListener(handleNoteTextChange);
12}
13
14void handleBackButton() async {
15    if (noteTitle.length == 0) {
16        // Go Back without saving
17        if (noteContent.length == 0) {
18            Navigator.pop(context);
19            return;
20        }
21        else {
22            String title = noteContent.split('\n')[0];
23            if (title.length > 31) {
24                title = title.substring(0, 31);
25            }
26            setState(() {
27                noteTitle = title;
28            });
29        }
30    }
31
32    // Save New note
33    if (widget.args[0] == 'new') {
34        Note noteObj = Note(
35            title: noteTitle,
36            content: noteContent,
37            noteColor: noteColor
38        );
39        try {
40            await _insertNote(noteObj);
41        } catch (e) {
42
43        } finally {
44            Navigator.pop(context);
45            return;
46        }
47    }
48
49    // Update Note
50    else if (widget.args[0] == 'update') {
51        Note noteObj = Note(
52            id: widget.args[1]['id'],
53            title: noteTitle,
54            content: noteContent,
55            noteColor: noteColor
56        );
57        try {
58            await _updateNote(noteObj);
59        } catch (e) {
60
61        } finally {
62            Navigator.pop(context);
63            return;
64        }
65    }
66}
67

Tapping on the note in the Home screen will take us to the Edit screen to update notes.


This tutorial addressed how to create a simple note-taking app in Flutter with common operations like create, read, update and delete. We can extend the app to have multiple day-to-day useful features. I hope you will do that to create your own notes app according to your interests and needs.

I have created a full Android working application with additional features like Notes sharing, multi-select notes, deleting notes in the edit screen, sort text in notes, etc. Check out the full code at github.com/santhalakshminarayana/zehero-note.