DEV Community

Cover image for Flutter: Data synchronization from sqflite to firebase and from firebase to sqflite
TAIO Sylvain
TAIO Sylvain

Posted on

Flutter: Data synchronization from sqflite to firebase and from firebase to sqflite

Step 1: Create a Flutter project
If you haven't installed Flutter yet, follow the instructions on the official Flutter website to do so. After that, create a new Flutter project using the following command:
flutter create data_synchronization
cd data_synchronization

Step 2: Set Up dependencies
Add the plugins to your pubspec.yaml file and run flutter pub get to install the dependencies as shown below.

dependencies:
path_provider: ^2.0.9
sqflite: ^2.0.2
firebase_core: ^2.8.0
cloud_firestore:
firebase_storage: ^11.0.0

Step 3: Create a Model

Image description

Step 4: Create your sqflite database management file

import 'dart:io';
import 'package:data_synchronization/person.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DatabaseHelper {
  static const _databaseName = 'MyDatabase.db';
  // ignore: constant_identifier_names
  static const int DB_VERSION = 1;

  //singleton class
  DatabaseHelper._();

  static final DatabaseHelper instance = DatabaseHelper._();

  Database? _database;

  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }
  //init database
  _initDatabase() async {
    Directory dataDirectory = await getApplicationDocumentsDirectory();
    String dbPath = join(dataDirectory.path, _databaseName);
    return await openDatabase(dbPath,
        version: DB_VERSION, onCreate: _onCreateDB);
  }

  Future _onCreateDB(Database db, int version) async {
    //create tables
    await db.execute('''
          CREATE TABLE ${Person.tblTable}(
            ${Person.colId} INTEGER PRIMARY KEY AUTOINCREMENT,
            ${Person.colName} TEXT,
            ${Person.colAge} INTEGER
          )
          ''');
  }
  //Data - insert
  Future<int> insertData(Person blog) async {
    Database db = await database;
    return await db.insert(Person.tblTable, blog.toMap());
  }
  //Data - update
  Future<int> updateDatag(Person blog) async {
    final db = await database;
    var result = await db.update(Person.tblTable, blog.toMap(),
        where: "${Person.colId} = ?", whereArgs: [blog.id]);
    return result;
  }

  //Data - retrieve all
  Future<List<Person>> fetchDatas() async {
    Database db = await database;
    List blogs = await db.query(Person.tblTable);
    return blogs.isEmpty
        ? []
        : blogs.map((x) => Person.fromMap(x)).toList();
  }

  // ignore: body_might_complete_normally_nullable
  Future<int?> closeDb() async {
    var dbClient = await database;
    await dbClient.close();
  }

  deleteAll() async {
    final dbClient = await database;
    dbClient.rawDelete("Delete from ${Person.tblTable}");
  }

}
Enter fullscreen mode Exit fullscreen mode

Step 5: Create a AddUser Widget

Nous configurons ici nos bases de données sqflite et mettons en place des requêtes pour ajouter, afficher, modifier et supprimer des données sqflite.

import 'package:data_synchronization/db_helper.dart';
import 'package:data_synchronization/person.dart';
import 'package:flutter/material.dart';

// Widget de formulaire pour saisir les données
class AddPersonForm extends StatefulWidget {
  @override
  _AddPersonFormState createState() => _AddPersonFormState();
}

class _AddPersonFormState extends State<AddPersonForm> {
  bool isLoad = false;
  final DatabaseHelper _dbHelper = DatabaseHelper.instance;
  final _formKey = GlobalKey<FormState>();
  late TextEditingController _nameController;
  late TextEditingController _ageController;

  @override
  void initState() {
    super.initState();
    _nameController = TextEditingController();
    _ageController = TextEditingController();
  }

  @override
  void dispose() {
    _nameController.dispose();
    _ageController.dispose();
    super.dispose();
  }

  // Méthode pour enregistrer les données dans SQLite
  Future<void> _saveToSqflite() async {
    if (_formKey.currentState!.validate()) {
      await  _dbHelper.insertData(
          Person(
              name:_nameController.text.trim(),
              age: int.parse(_ageController.text.trim())
          )
      );
      _nameController.clear();
      _ageController.clear();
      // ignore: use_build_context_synchronously
      Navigator.pop(context);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Add User"),
        centerTitle: true,
      ),
      body: Form(
        key: _formKey,
        child: Column(
          children: [
            TextFormField(
              controller: _nameController,
              decoration: const InputDecoration(labelText: 'Name'),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter a name';
                }
                return null;
              },
            ),
            TextFormField(
              controller: _ageController,
              decoration: const InputDecoration(labelText: 'Age'),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter an age';
                }
                if (int.tryParse(value) == null) {
                  return 'Please enter a valid age';
                }
                return null;
              },
            ),
            ElevatedButton(
              onPressed: _saveToSqflite,
              child: const Text('Save'),
            ),
          ],
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Create a HomeScreen Widget
In this view, we'll then display the data and create two functions to synchronize data from saqflite to Firebase and from Firebase to sqflite.


import 'package:data_synchronization/db_helper.dart';
import 'package:data_synchronization/person.dart';
import 'package:data_synchronization/add_user.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';


class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  DatabaseHelper? _dbHelper;
  late List<Person> _blogs;
  bool _isLoading = true;
  bool _isSynch = false;

  final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey = GlobalKey<RefreshIndicatorState>();

  @override
  void initState() {
    super.initState();
    _dbHelper = DatabaseHelper.instance;
    _getBloges();
  }
  Future _getBloges() async {
    List<Person> _blog = await _dbHelper!.fetchDatas();
    setState(() {
      _blogs = _blog;
      _isLoading = false;
    });
  }

  Future<Null> _refresh() async {
    List<Person> _blog = await _dbHelper!.fetchDatas();
    setState(() {
      _blogs = _blog;
    });
    return null;
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Home Page"),
        actions: [
          GestureDetector(
            onTap: () async{
              await _dbHelper!.deleteAll();
              setState(() {
                _refresh();
              });
            },
            child: const Icon(Icons.delete_outline),
          ),
          const SizedBox(width: 10),
          myPopMenu()
        ],
      ),
      body: RefreshIndicator(
        key: _refreshIndicatorKey,
        onRefresh: _refresh,
        child: (_isLoading || _isSynch)
            ?
        const Center(
          child: CircularProgressIndicator(
            valueColor: AlwaysStoppedAnimation<Color>(Colors.pink),
          )
        )
            : _blogs.isEmpty
            ?
        Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                "Aucun résultat trouvé",
              ),
              GestureDetector(
                onTap: (){
                  _refresh();
                },
                child: const Padding(
                  padding: EdgeInsets.only(top: 15),
                  child: Icon(Icons.refresh,size: 40,color: Colors.black45,),
                ),
              )
            ],
          ),
        )
            :
        ListView.builder(
          shrinkWrap: true,
          itemCount: _blogs.length,
          itemBuilder: (context, index){
            return  Padding(
                padding: const EdgeInsets.only(top: 15, left: 2, right: 2),
                child: ListTile(
                  leading: CircleAvatar(
                    radius: 35,
                    backgroundColor: Colors.pink,
                    child: Center(
                      child: Text("${_blogs[index].id}",
                          style: const TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 15
                              ,color: Colors.white
                          )
                      ),
                    ),
                  ),
                  title: Column(
                    mainAxisAlignment: MainAxisAlignment.start,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('${_blogs[index].name}',
                        style: const TextStyle(fontWeight: FontWeight.bold)
                      ),
                      const SizedBox(height: 08),
                      Text('${_blogs[index].age}')
                    ],
                  ),
                )
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _navigateToAddUser(context),
        child: const Icon(Icons.add_outlined),
      )
    );
  }

  Widget myPopMenu() {
    return PopupMenuButton(
        onSelected: (value) {
          if(value==1){
            syncSqfliteToFirebase();
          }else if(value==2){
            syncFirebaseToSqflite();
          }
        },
        itemBuilder: (context) => [
          const PopupMenuItem(
              value: 1,
              child: Text('Synchronisation Sqflite => Firebase',maxLines: 2)),
          const PopupMenuItem(
              value: 2,
              child: Text('Synchronisation Firebase => Sqflite',maxLines: 2)),
        ]);
  }

  void _navigateToAddUser(BuildContext context){
    Navigator.push(context, MaterialPageRoute(
        builder: (contex)=> AddPersonForm())).then((value) {
      setState(() {
        _refresh();
      });
    });
  }

  // Method to synchronize data from SQLite to Firebase
  Future<void> syncSqfliteToFirebase() async {
    setState(() {
      _isSynch = true;
    });
    // SQLite data retrieval
    List<Person> allpersonnes = await _dbHelper!.fetchDatas();
    List<Person> newData = [];

    // Retrieve existing data from Firebase
    QuerySnapshot firebaseData =
    await FirebaseFirestore.instance.collection('firebase_tab').get();

    // Check for duplicate data
    List<String> firebaseIds = firebaseData.docs.map((doc) => doc.id).toList();


    for (var data in allpersonnes) {
      if (!firebaseIds.contains(data.id)) {
        newData.add(
          Person(
            id: data.id,
            name: data.name,
            age: data.age,
          ),
        );
      }
    }

    // Send new data to Firebase
    CollectionReference collection =
    FirebaseFirestore.instance.collection('firebase_tab');
    newData.forEach((person) {
      collection.doc(person.id).set(person.toJson());
    });

    setState(() {
      _isSynch = false;
    });
  }
  // Method to sync data from Firebase to SQLite
  Future<void> syncFirebaseToSqflite() async {
    setState(() {
      _isSynch = true;
    });
    // Retrieving data from Firebase
    QuerySnapshot firebaseData =
    await FirebaseFirestore.instance.collection('firebase_tab').get();
    List<Person> firebasePeople =
    firebaseData.docs.map((doc) => Person.fromSnapshot(doc)).toList();

    // Recovering existing data in SQLite
    List<Person> allpersonnes = await _dbHelper!.fetchDatas();

    // Check for duplicate data
    List sqliteIds = allpersonnes.map((data) => data.id).toList();
    List<Person> newData = [];

    for (var person in firebasePeople) {
      if (!sqliteIds.contains(person.id)) {
        print("oui oui ${person.name}");
        newData.add(person);
      }else{
        print("Nononon ${person.name}");
      }
    }

    //Inserting new data in SQLite
    for (var person in newData) {
      print(person.name);
      _dbHelper!.insertData(person);
    }
    setState(() {
      _refresh();
      _isSynch = false;
    });
  }
}

Enter fullscreen mode Exit fullscreen mode

Don't forget to leave a comment if you enjoyed

Sentry mobile image

Improving mobile performance, from slow screens to app start time

Based on our experience working with thousands of mobile developer teams, we developed a mobile monitoring maturity curve.

Read more

Top comments (1)

Collapse
 
mahmoud-a-m-1993 profile image
Mahmoud

Wow. That's really great

Sentry mobile image

App store rankings love fast apps - mobile vitals can help you get there

Slow startup times, UI hangs, and frozen frames frustrate users—but they’re also fixable. Mobile Vitals help you measure and understand these performance issues so you can optimize your app’s speed and responsiveness. Learn how to use them to reduce friction and improve user experience.

Read full post →

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay