Flutter Database Concepts

Flutter - Introduction to Dart Programming

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 Databases

Local 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

  1. Offline Access:Data is stored on the device, ensuring availability even without internet connectivity.
  2. Fast Performance: Local storage eliminates network latency.
  3. Limited Size:Storage space depends on the device's capacity.
  4. Data Sync:Can be synchronized with a remote server when online.
  5. 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

  1. Lightweight: It's very small, making it ideal for mobile devices.
  2. Self-contained: There is no need for a separate server or network connection, as all the data is stored locally on the device.
  3. ACID-compliant: SQLite supports transactions, ensuring that the database adheres to ACID (Atomicity, Consistency, Isolation, Durability) properties.
  4. Cross-platform: SQLite is used on many different platforms, including Android, iOS, and desktop environments.

How SQLite Works

  1. SQLite stores data in a single file, which can be easily backed up, moved, or copied.
  2. 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:

  • Real-time Sync: Changes made to the data are automatically updated across connected devices in real-time.
  • NoSQL Structure: Data is stored in documents, which are grouped into collections. Each document can contain subcollections and fields.
  • Offline Support: Data can be accessed offline and synced when the connection is restored.
  • Powerful Querying: Firestore allows complex querying with support for filters, ranges, and ordering.
  • SecureData access is controlled via Firebase Security Rules and Firebase Authentication.

  • 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):
      1. For Android, download the google-services.json file.
      2. 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)),
                    ),
                  );
                },
              );
            },
          ),
        );
      }
    }
    
    أحدث أقدم