has been long time didnt write any article, working hardly, code and system design
Our clients hope the system can keep running offline—some areas have no network at all, and others have very unstable connections. This requirement made us rethink our whole data storage approach for the project. We needed something robust enough to handle offline reads and writes, and smart enough to sync changes whenever the network returns.
Intro
After researching various options, we landed on Drift as our local database solution for Flutter. Drift (formerly known as Moor) offers powerful features like type-safe queries, auto-updating streams, and easy schema migrations, making it a great fit for apps that need to perform reliably—online and offline.
To address this, our Flutter system includes:
- A network real-time checker: It constantly monitors connectivity status to smartly switch between online and offline modes.
- Cloud sync (download/upload): Data created or updated locally is synced to the cloud when connectivity is restored, while server-side changes are downloaded for local use.
- Background Task: Sync local orders in midnight.
- smart data retrieval strategy: The app decides whether to fetch data from the cloud or local database based on the current network state, giving users a seamless experience regardless of connection quality.
In this blog, I’ll walk through my implementing offline-first data persistence with Drift, BTW, I use MVVM pattern.
Why Chose Drift for Flutter
For our offline-first Flutter app, we needed a local database solution that was:
- Reliable
- Type-safe
- Reactive
- Easy to maintain
After comparing options like sqflite, Isar, we chose Drift because it offers:
- Queries return Streams that automatically notify the UI of data changes, making real-time updates seamless.
- With clear separation between tables, DAOs, and database logic, it fits perfectly into a clean architecture.
- Manual Migration Control
Getting Started with Drift
Here is the folder structure:
|
|
- Modular: Easy to scale as new tables are added.
- Readable: Each concern (schema vs logic) is isolated.
- Testable: DAOs and migrations can be tested independently.
Setting Up AppDatabase
|
|
- Declare Tables & DAOs
- Implement schemaVersion
- Define MigrationStrategy
- Set Up LazyDatabase
The app_database.g.dart file is initially empty or missing. This is normal. It will be auto-generated once you run the Drift code generator using the following command:
|
|
DAO and Table
To keep our codebase organized and scalable, we separate each table and its corresponding query logic into two dedicated folders: table/ and dao/.
Drift Table Definitions (table/)
|
|
DAO Classes (dao/)
DAOs (Data Access Objects) contain the read/write/query(CRUD) logic for each table.
|
|
Notes:
- watch() enables reactive UI updates (ideal for Flutter)
- Using Companion classes allows partial inserts and updates
Any class like SystemConfigData, PosOrderData, or PosUserData is not something you write manually — it’s automatically generated by Drift’s code generator based on your table definitions.
So if you see a red underline or “undefined class” error in your IDE when you first reference something like PosOrderData, don’t panic — this is normal. Just Run
|
|
Comes Together
In app_database.dart
|
|
And run that CLI, Generate the app_database.g.dart file
Migrations and Schema Changes
As your app grows, so will your database. New features often require adding tables, columns, or modifying constraints. In Flutter Drift, migrations ensure that existing users’ data stays safe and usable when schema changes occur.
We handled this using Drift’s built-in MigrationStrategy in app_database.dart. We override the migration getter to define both onCreate and onUpgrade logic:
|
|
⚠️ Tips
- Always increment schemaVersion when making schema changes:
|
|
- Avoid dropping tables/columns without backups. Drift doesn’t support dropping columns directly, so you need to:
- Create a new table
- Migrate old data manually
- Drop the old table after verification
- Use conditionals (if (from == x && to == y)) for clarity.
- Log everything in onUpgrade
In Dev Env(DONT USE IN PROD ENV), we can easily reset DB
|
|