Building a Notes App in Flutter – Part 1: Project Setup & Core UI

Series: This is Part 1 of 2. Read Part 2 → Local Persistence & Polish

Most Flutter tutorials teach you widgets. This one teaches you to think in Flutter.

There’s a big difference between following along with a counter app and actually understanding why Flutter works the way it does. By the end of this article, you won’t just have a notes app — you’ll understand the mental model: widget trees, state ownership, and screen-to-screen data flow. These are the concepts every Flutter developer uses every single day.

Prerequisites: Basic programming knowledge (variables, functions, conditionals). Zero Flutter or Dart experience needed.

What you’ll have by the end of this article: A real, working two-screen notes app with a list view, a note editor, and full navigation between them — all wired up with proper in-memory state.


Table of Contents


1. Setting Up Flutter

Head to the official Flutter installation page and follow the guide for your OS. You’ll install the Flutter SDK and either Android Studio or VS Code as your editor. Both work well — VS Code with the Flutter extension is slightly lighter for beginners.

Once installed, run the health check:

This checks your environment and flags anything missing — missing Xcode, Android SDK, license agreements. Fix every item before moving on, otherwise you’ll hit confusing errors later.

Create and run the project

You should see the default counter app. That’s your green light — everything is wired up correctly. We’ll delete all of it and start fresh.

Project structure at a glance

Everything you write goes inside lib/. The platform folders only matter when you need native integrations.


2. Core Concepts: Widgets, State, and the Tree

Flutter has one rule that governs everything: every piece of UI is a widget. Buttons, text, padding, layout columns — all widgets. They nest inside each other to form a widget tree, and Flutter uses that tree to render the screen.

There are two kinds of widgets. Understanding the difference is the most important thing you’ll learn today:

Widget Type Can Change Over Time? Has setState()? Common Use Cases
StatelessWidget No No Static labels, icons, layout shells, the root MaterialApp
StatefulWidget Yes Yes Lists that grow, forms, screens with user interaction

The rule of thumb: if a user action can change what the widget displays, make it a StatefulWidget. Otherwise, keep it StatelessWidget — it’s simpler and slightly more efficient.


3. The App Entry Point

Open lib/main.dart and replace the entire file with this:

Code What it does
main() Dart’s entry point — execution always starts here
runApp() Inflates the given widget and makes it the root of the UI
MaterialApp Sets up Material Design theming, navigation, and the first screen
home: The widget shown when the app first launches

4. Create the Note Model

Create a new file at lib/models/note.dart. This defines the shape of a single note across the whole app:

id and createdAt are final — they’re set once and never change. title and content are mutable because the user can edit them. In Part 2, we’ll add toJson() and fromJson() methods here so we can persist notes to storage.


5. Build the Notes List Screen

Create lib/screens/notes_list_page.dart. This is the home screen — it owns the notes list and is responsible for keeping it up to date:

💡 Why ListView.builder?
It only creates the widget cells currently visible on screen — not all of them upfront. A Column inside a SingleChildScrollView would build every single note widget at once, even if you have 500 notes. ListView.builder is always the right choice for lists of unknown length.

6. Build the Note Editor Screen

Create lib/screens/note_editor_page.dart. This screen handles both creating a new note and editing an existing one — the same widget, two modes:

Pattern Why it matters
initState() initializes controllers Runs once on widget creation — the right place for setup work, not build()
dispose() cleans up controllers Prevents memory leaks when the screen is removed from the stack
Navigator.pop(context, note) Returns the saved note as a value back to the list screen
maxLines: null + expands: true Makes the content field fill all remaining vertical space naturally
autofocus: true on title Keyboard opens immediately when the screen loads — better UX

7. Wire Up the File Structure

Your complete lib/ structure should now look like this:

This structure separates data (models/) from UI (screens/). In Part 2 we’ll add a services/ folder for storage logic — keeping that out of the UI code too.


8. Run It and Test

Run through this checklist to make sure everything is working:

Action Expected Result
Open the app Empty state icon + “Tap + to create your first note”
Tap the + FAB Editor opens with keyboard focused on the title field
Type a title + content, tap ✓ Note appears as a card at the top of the list
Tap a note in the list Editor opens pre-filled with that note’s content
Edit and save List updates with the new content
Close and reopen the app Notes are gone — expected, we fix this in Part 2

9. Recap & What’s Next

Concept What you built
Project setup Flutter installed, project created, default app running
Widgets & the widget tree Nested Scaffold, AppBar, ListView, Card, TextField
StatefulWidget + setState Notes list that updates live when you add or edit a note
Navigation + data passing Navigator.push / pop with a return value between screens
TextEditingController lifecycle Initialized in initState, disposed in dispose

In Part 2, we wire up shared_preferences so notes survive app restarts. We’ll also add swipe-to-delete, timestamps, and a clean NotesStorage service class — and cover the async patterns that make it all work.

Show 7 Comments

7 Comments

Leave a Reply