A Comprehensive Guide to SQLite CRUD Operations in Flutter with Bloc
Hello everyone,
Today, I'll guide you through performing CRUD (Create, Read, Update, Delete) operations in Flutter using SQLite, with Bloc for state management. SQLite is a local database that stores data on the device, making it essential to understand SQL queries to utilize its full potential.
First of all, it’s important to know that SQLite is a local database. This means it stores our data locally. To leverage this powerful local database, we should have some knowledge of SQL queries.
Dependencies
The first step is to add the necessary dependencies that will help us along the process.
2. flutter_bloc
3. equatble
Add these dependencies to your pubspec.yaml
file under the dependencies section.
dependencies:
flutter:
sdk: flutter
sqflite: ^2.3.3+1
flutter_bloc: ^8.1.5
equatable: ^2.0.5
path_provider: ^2.1.3
Now we’ll setup our sqlite file in the lib folder. For this we will first setup a Database Helper class in which we will create a table to perform our operations of CRUD.
my_sqlite.dart
//Dependencies
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
final databaseName = "items.db";
String items = 'CREATE TABLE items(id INTEGER PRIMARY KEY, name TEXT)';
// Create and initialize the database
Future<Database> initDB() async {
final databasePath = await getDatabasesPath();
final path = join(databasePath, databaseName);
return openDatabase(path, version: 1, onCreate: (db, version) async {
await db.execute(items);
});
}
}
Ok, so now that we have created and initialized our database named ‘items’. Now we will implement write the dart functions.
CRUD operations
Create
Future<void> insertItem(String name) async { final db = await initDB(); await db.insert( 'items', {'name': name}, conflictAlgorithm: ConflictAlgorithm.replace, // Handle conflicts by replacing existing data ); }
2. Read
Future<List<Map<String, dynamic>>> fetchItems() async { final db = await initDB(); return await db.query('items'); }
3. Update
Future<void> updateItem(int id, String name) async { final db = await initDB(); await db.update( 'items', {'name': name}, where: 'id = ?', whereArgs: [id], ); }
4. Delete
Future<void> deleteItem(int id) async { final db = await initDB(); await db.delete( 'items', where: 'id = ?', whereArgs: [id], ); }
Now, that we have written all the sqlite crud queries in our dart functions, now we will set up our blocs, event and states to manage our state in our application.
Events
We’ll define the events for our bloc.
items_event.dart
part of 'items_bloc.dart'; sealed class ItemsEvent extends Equatable { const ItemsEvent(); @override List<Object> get props => []; } //For fetching the items from the database. class LoadItems extends ItemsEvent {} //For adding an item in the database. class AddItem extends ItemsEvent { final String name; const AddItem(this.name); } //For updating an existing item in the database. class UpdateItem extends ItemsEvent { final int id; final String name; const UpdateItem(this.id, this.name); } //For deleting a particular item in the database. class DeleteItem extends ItemsEvent { final int id; const DeleteItem(this.id); }
State
We’ll define the states for our bloc.
items_state.dart
part of 'items_bloc.dart'; sealed class ItemsState extends Equatable { const ItemsState(); @override List<Object> get props => []; } final class ItemsInitial extends ItemsState {} class ItemLoadInProgress extends ItemsState {} class ItemLoadSuccess extends ItemsState { final List<Map<String, dynamic>> items; const ItemLoadSuccess(this.items); } class ItemOperationFailure extends ItemsState { final String message; const ItemOperationFailure(this.message); }
Bloc
We’ll define our bloc.
items_bloc.dart
//Dependencies: import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; //Paths import '../my_sqlite.dart'; part 'items_event.dart'; part 'items_state.dart'; class ItemsBloc extends Bloc<ItemsEvent, ItemsState> { final DatabaseHelper databaseHelper; ItemsBloc(this.databaseHelper) : super(ItemsInitial()) { on<LoadItems>(_onLoadItems); on<AddItem>(_onAddItem); on<UpdateItem>(_onUpdateItem); on<DeleteItem>(_onDeleteItem); } // This function is for fetching the items. void _onLoadItems(LoadItems event, Emitter<ItemsState> emit) async { emit(ItemLoadInProgress()); try { final items = await databaseHelper.fetchItems(); emit(ItemLoadSuccess(items)); } catch (_) { emit(const ItemOperationFailure( "Failed to fetch the items in the database")); } } // This function is for adding items. void _onAddItem(AddItem event, Emitter<ItemsState> emit) async { await databaseHelper.insertItem(event.name); add(LoadItems()); } // This function is for updating items. void _onUpdateItem(UpdateItem event, Emitter<ItemsState> emit) async { await databaseHelper.updateItem(event.id, event.name); add(LoadItems()); } //This function is for deleting items with specific id. void _onDeleteItem(DeleteItem event, Emitter<ItemsState> emit) async { await databaseHelper.deleteItem(event.id); add(LoadItems()); } }
Now, that we are done with the bloc also, is the time of UI where we see the payoff of all the buildup that we have been doing.
main.dart
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'bloc/items_bloc.dart'; import 'home_page.dart'; import 'my_sqlite.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => ItemsBloc(DatabaseHelper()), child: const MaterialApp( title: 'Items', home: HomePage(), ), ); } }
home_page.dart
```dart
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:items/bloc/items_bloc.dart';
class HomePage extends StatelessWidget { const HomePage({super.key});
void _showItemDialog(BuildContext context, int? id, String existingName) { final TextEditingController controller = TextEditingController(text: existingName);
showDialog( context: context, builder: (context) { return AlertDialog( title: Text(id == null ? 'Add Item' : 'Update Item'), content: TextField( controller: controller, decoration: const InputDecoration(hintText: 'Enter item name'), ), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: const Text('Cancel'), ), TextButton( onPressed: () { final itemName = controller.text; if (id == null) { context.read().add(AddItem(itemName)); } else { context.read().add(UpdateItem(id, itemName)); } Navigator.of(context).pop(); }, child: Text(id == null ? 'Add' : 'Update'), ), ], ); }, ); }
@override Widget build(BuildContext context) { context.read().add(LoadItems()); return Scaffold( appBar: AppBar( title: const Text('Flutter CRUD with Bloc'), ), body: BlocBuilder( builder: (context, state) { if (state is ItemLoadInProgress) { return const Center(child: CircularProgressIndicator()); } else if (state is ItemLoadSuccess) { return ListView.builder( itemCount: state.items.length, itemBuilder: (context, index) { final item = state.items[index]; return Card( color: Colors.white, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(item['name']), Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.edit), onPressed: () { _showItemDialog( context, item['id'], item['name']); }, ), IconButton( icon: const Icon(Icons.delete), onPressed: () { context .read() .add(DeleteItem(item['id'])); }, ), ], ), ], ), ), ); }, ); } else if (state is ItemOperationFailure) { return const Center(child: Text('Failed to load items')); } return Container(); }, ), floatingActionButton: FloatingActionButton( onPressed: () { _showItemDialog(context, null, ''); }, child: const Icon(Icons.add), ), ); } } ```
In this blog, we covered how to set up SQLite in a Flutter project, create a Database Helper class, and implement CRUD operations. We also set up Bloc to manage our application’s state and integrated it with our UI. This setup ensures a clear separation of concerns, making your app more maintainable and scalable.
You can find all the code in this github link.
Thank you for your time.
Subscribe to my newsletter
Read articles from Jitesh Yadav directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by