Flutter Database Concepts
In Flutter, working with a database involves managing data storage and retrieval efficiently. Depending on your application requirements, you can use different types of databases, such as local (SQLite) or cloud-based (Firebase Firestore, REST APIs, or other external databases). Below are the core concepts you should know about databases in Flutter:
Types of Databases in Flutter
a. Local DatabasesLocal databases are used to store data on the user's device. These databases are particularly useful for offline functionality or when you need quick access to data without relying on a network connection. Below are key points and examples related to local databases:
- SQLite:
- Flutter provides support for SQLite using plugins like sqflite or moor.
- Ideal for offline apps or when structured relational data is required.
- Supports CRUD operations (Create, Read, Update, Delete).
- Use SQL queries to interact with the database.
- Shared Preferences:
- A key-value store for small amounts of data like user settings or preferences.
- Lightweight but not suitable for large or relational data.
Characteristics of Local Databases
- Offline Access:Data is stored on the device, ensuring availability even without internet connectivity.
- Fast Performance: Local storage eliminates network latency.
- Limited Size:Storage space depends on the device's capacity.
- Data Sync:Can be synchronized with a remote server when online.
- Security: Data can be encrypted for secure storage.
SQLite Database in Flutter
SQLite is a lightweight, serverless relational database engine that is widely used in mobile applications, embedded systems, and applications where you need a local, self-contained database. It's the most commonly used database in mobile development (both iOS and Android) and provides a powerful way to manage data within apps without the need for a server.
Key Features of SQLite
- Lightweight: It's very small, making it ideal for mobile devices.
- Self-contained: There is no need for a separate server or network connection, as all the data is stored locally on the device.
- ACID-compliant: SQLite supports transactions, ensuring that the database adheres to ACID (Atomicity, Consistency, Isolation, Durability) properties.
- Cross-platform: SQLite is used on many different platforms, including Android, iOS, and desktop environments.
How SQLite Works
- SQLite stores data in a single file, which can be easily backed up, moved, or copied.
- It supports standard SQL queries for CRUD operations (Create, Read, Update, Delete).
SQLite Setup in Flutter
To use SQLite in Flutter, you typically use the sqflite package, which is a wrapper around SQLite for easy integration into Flutter apps.
Step 1: Add Dependencies
To use SQLite in your Flutter project, you need to add the sqflite and path packages to your pubspec.yaml file:
dependencies:
sqflite: ^2.0.0
path: ^1.8.0
Run flutter pub get to install the dependencies.
b. Creating and Using a Database
Initialize the Database: Use the path_provider package to get the database file location:
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
Future<Database> initDatabase() async {
final dbPath = await getDatabasesPath(); // Get database path
return openDatabase(
join(dbPath, 'my_database.db'),
onCreate: (db, version) {
return db.execute(
'CREATE TABLE products(id INTEGER PRIMARY KEY, name TEXT, price REAL)',
);
},
version: 1,
);
}
Insert Data
Future<void> insertProduct(Database db, Map<String, dynamic> product) async {
await db.insert(
'products',
product,
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Fetch Data:
Future<List<Map<String, dynamic>>> fetchProducts(Database db) async {
return await db.query('products'); // Fetch all rows from the 'products' table
}
Update Data
Future<void> updateProduct(Database db, Map<String, dynamic> product) async {
await db.update(
'products',
product,
where: 'id = ?',
whereArgs: [product['id']],
);
}
Delete Data
Future<void> deleteProduct(Database db, int id) async {
await db.delete(
'products',
where: 'id = ?',
whereArgs: [id],
);
}
Create SQLiteDbProvider.dart Inside your lib folder, create a new file named SQLiteDbProvider.dart and add the following code
// Import required packages
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
// Singleton class for SQLite database
class SQLiteDbProvider {
// Singleton instance
static final SQLiteDbProvider _instance = SQLiteDbProvider._internal();
// Private constructor
SQLiteDbProvider._internal();
// Factory constructor to return the same instance
factory SQLiteDbProvider() => _instance;
// Database instance
static Database? _database;
// Getter for the database
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDB();
return _database!;
}
// Initialize database
Future<Database> _initDB() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "mydatabase.db");
return await openDatabase(
path,
version: 1,
onCreate: _onCreate,
);
}
// Create database table
Future<void> _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
price REAL
)
''');
}
}
- async:is used for asynchronous database operations.
- io: is used to access the file system for storing the database.
- path: helps manage file paths.
- path_provider: gets the correct location for storing database files.
- sqflite: is used for SQLite database manipulation.
- Singleton pattern : ensures only one instance of SQLiteDbProvider exists.
- _initDB(): initializes the database in the device's application directory.
- _onCreate(): defines the structure of the products table.
// Import required packages
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
// Singleton class for SQLite database
class SQLiteDbProvider {
// Singleton instance
static final SQLiteDbProvider _instance = SQLiteDbProvider._internal();
// Private constructor
SQLiteDbProvider._internal();
// Factory constructor to return the same instance
factory SQLiteDbProvider() => _instance;
// Database instance
static Database? _database;
// Method to get the database instance
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDB();
return _database!;
}
// Initialize the database
Future<Database> _initDB() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "mydatabase.db");
return await openDatabase(
path,
version: 1,
onCreate: _onCreate,
);
}
// Create table and insert initial data
Future<void> _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
price REAL
)
''');
// Insert initial data into the products table
await db.insert('products', {'name': 'Laptop', 'price': 799.99});
await db.insert('products', {'name': 'Smartphone', 'price': 499.99});
await db.insert('products', {'name': 'Tablet', 'price': 299.99});
}
}
// Import required packages
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'Product.dart'; // Import the Product model
// Singleton class for SQLite database
class SQLiteDbProvider {
// Singleton instance
static final SQLiteDbProvider _instance = SQLiteDbProvider._internal();
// Private constructor
SQLiteDbProvider._internal();
// Factory constructor to return the same instance
factory SQLiteDbProvider() => _instance;
// Database instance
static Database? _database;
// Method to get the database instance
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDB();
return _database!;
}
// Initialize the database
Future<Database> _initDB() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "mydatabase.db");
return await openDatabase(
path,
version: 1,
onCreate: _onCreate,
onOpen: (db) {
print("Database Opened");
},
);
}
// Create table and insert initial data
Future<void> _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
price REAL
)
''');
// Insert initial data into the products table
await db.insert('products', {'name': 'Laptop', 'price': 799.99});
await db.insert('products', {'name': 'Smartphone', 'price': 499.99});
await db.insert('products', {'name': 'Tablet', 'price': 299.99});
}
// Method to get all products from the database
Future<List<Product>> getAllProducts() async {
final db = await database;
final List<Map<String, dynamic>> maps = await db.query('products');
// Convert database rows to Product objects
return List.generate(maps.length, (i) {
return Product.fromMap(maps[i]);
});
}
}
Firebase Firestore Database in Flutter
Firebase Firestore is a NoSQL cloud database provided by Google. It is highly scalable, real-time, and ideal for building modern mobile and web applications. Firestore allows you to store, query, and sync your app's data across multiple devices and platforms seamlessly.
Key Features of Firebase Firestore:
Setup Firebase Firestore in Flutter
Step 1: Add Firebase to Your Flutter Project- Go to the Firebase Console and create a new project.
- Add your app (Android/iOS):
- For Android, download the google-services.json file.
- For iOS, download the GoogleService-Info.plist file.
- Follow the setup instructions provided in the Firebase console to link your app to Firebase.
Step 2: Add Dependencies
Add the firebase_core and cloud_firestore packages to your pubspec.yaml file:
dependencies:
firebase_core: ^2.12.0 # Core Firebase library
cloud_firestore: ^4.9.0 # Firestore database
Run:
flutter pub get
b. Firestore CRUD Operations
1:Add Data
import 'package:cloud_firestore/cloud_firestore.dart';
Future<void> addProduct() async {
FirebaseFirestore.instance.collection('products').add({
'name': 'Wireless Headphones',
'price': 59.99,
});
}
Fetch Data
Stream<QuerySnapshot> fetchProducts() {
return FirebaseFirestore.instance.collection('products').snapshots();
}
Update Data
Future<void> updateProduct(String docId) async {
FirebaseFirestore.instance.collection('products')
.doc(docId)
.update({
'price': 49.99,
});
}
Delete Data
Future<void> deleteProduct(String docId) async {
FirebaseFirestore.instance.collection('products')
.doc(docId)
.delete();
}
4. Using REST APIs
If your data resides in a remote database, you can use REST APIs to interact with it. Use the http package to make GET, POST, PUT, and DELETE requests.
Fetching Data Example:
Future<List<dynamic>> fetchProducts() async {
final response = await http.get(Uri.parse('https://example.com/api/products'));
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception('Failed to load products');
}
}
Comparison of Database Options
Feature | SQLite | Firebase Firestore | REST API |
---|---|---|---|
Type | Relational (SQL) | NoSQL | Depends on the backend |
Offline Support | Yes | Limited | No |
Ease of Setup | Medium | Easy (with Firebase CLI) | Medium |
Data Synchronization | No | Real-time | No |
Best For | Local data storage | Real-time apps | Interfacing with web APIs |
// Import required packages
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
// Main function to initialize Firebase
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
// Main application class
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: ProductList(),
);
}
}
// Product list screen
class ProductList extends StatefulWidget {
@override
_ProductListState createState() => _ProductListState();
}
// State class for ProductList
class _ProductListState extends State<ProductList> {
// Firestore instance
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
// Fetch products from Firestore
Future<List<Map<String, dynamic>>> fetchProducts() async {
QuerySnapshot snapshot = await _firestore.collection('products').get();
return snapshot.docs.map((doc) => doc.data() as Map<String, dynamic>).toList();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Product Store'),
backgroundColor: Colors.blueAccent,
),
body: FutureBuilder<List<Map<String, dynamic>>>(
future: fetchProducts(),
builder: (context, snapshot) {
// Show loading indicator while fetching data
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
// Show error message if fetching fails
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
// Get product list
List<Map<String, dynamic>>? products = snapshot.data;
// Show message if no products found
if (products == null || products.isEmpty) {
return Center(child: Text('No products available'));
}
// Display list of products
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
return Card(
margin: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: ListTile(
leading: Icon(Icons.shopping_cart, color: Colors.blue),
title: Text(products[index]['name'], style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text("\$${products[index]['price']}", style: TextStyle(color: Colors.green)),
),
);
},
);
},
),
);
}
}