Flutter: Data synchronization from sqflite to firebase and from firebase to sqflite
Tags: flutter, sqflite, firebase, synchronization
Published: 2023-12-14 08:33:01

**Step 1: Create a Flutter project**
If you haven't installed Flutter yet, follow the instructions on the [official Flutter website](https://docs.flutter.dev/get-started/install) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tmqu88zf3t37wifsuxc3.png)

**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}");
  }

}
```
**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'),
            ),
          ],
        ),
      ),
    );
  }
}
```
**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;
    });
  }
}

```
**Don't forget to leave a comment if you enjoyed**