Offline Mode Implementation with SQLite and Hive

In modern mobile and web applications, providing a seamless user experience, even when the network connection is unreliable or unavailable, is paramount. Implementing offline mode allows users to continue using core app functionalities, such as reading data, creating content, and interacting with features, irrespective of their internet connectivity. This blog post explores implementing offline mode using two robust local data storage solutions: SQLite and Hive.

Understanding Offline Mode

Offline mode is a feature that enables an application to function without a constant internet connection. It involves caching data locally so that users can access and interact with it when offline. Key aspects of offline mode include:

  • Data Caching: Storing data locally to be accessible without an internet connection.
  • Synchronization: Synchronizing local data with remote data when the network becomes available.
  • Conflict Resolution: Handling conflicts that arise when local changes conflict with remote data updates.

Choosing a Local Database: SQLite vs. Hive

SQLite

SQLite is a self-contained, high-reliability, embedded, full-featured, SQL database engine. It is widely used in mobile applications due to its lightweight nature and zero-configuration setup.

  • Pros:
    • Widely adopted and supported across different platforms.
    • Mature and well-tested.
    • SQL-based querying.
  • Cons:
    • Can be slower for complex data models compared to NoSQL databases.
    • Requires SQL knowledge for querying.

Hive

Hive is a lightweight NoSQL database written in Dart, perfect for Flutter applications. It’s designed for speed and ease of use, providing a simple key-value store.

  • Pros:
    • Fast and efficient for read and write operations.
    • Simple API and easy to learn.
    • Built for Flutter, providing seamless integration.
  • Cons:
    • Limited querying capabilities compared to SQL databases.
    • Relatively newer compared to SQLite.

Implementing Offline Mode with SQLite

Step 1: Project Setup

First, set up a new Android project in Kotlin or an iOS project in Swift. Add the SQLite dependency to your project. In Kotlin (Android), you can use Room Persistence Library for easy SQLite database management.


// build.gradle.kts (Module: app)
plugins {
    id("kotlin-kapt")
}

dependencies {
    implementation("androidx.room:room-runtime:2.6.1")
    kapt("androidx.room:room-compiler:2.6.1")
    implementation("androidx.room:room-ktx:2.6.1") // Kotlin extensions for Room
}

Step 2: Define Data Model

Define your data model and create a Room entity for each data class that needs to be stored offline.


import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "articles")
data class Article(
    @PrimaryKey val id: Int,
    val title: String,
    val content: String
)

Step 3: Create a DAO (Data Access Object)

Create a DAO interface to interact with the SQLite database.


import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query

@Dao
interface ArticleDao {
    @Query("SELECT * FROM articles")
    suspend fun getAllArticles(): List
@Insert suspend fun insertArticle(article: Article) }

Step 4: Create a Room Database

Create an abstract class that extends RoomDatabase.


import androidx.room.Database
import androidx.room.RoomDatabase

@Database(entities = [Article::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun articleDao(): ArticleDao
}

Step 5: Initialize the Database

Initialize the database in your Application class or a Singleton class.


import android.app.Application
import androidx.room.Room

class MyApplication : Application() {
    companion object {
        lateinit var database: AppDatabase
    }

    override fun onCreate() {
        super.onCreate()
        database = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java,
            "article_database"
        ).build()
    }
}

Step 6: Implement Data Retrieval and Caching

Retrieve data from the network. If the network call fails, retrieve the data from the local SQLite database.


import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

suspend fun getArticles(): List
{ return try { // Try to fetch articles from the network val articles = fetchArticlesFromNetwork() // Save articles to local database withContext(Dispatchers.IO) { articles.forEach { article -> MyApplication.database.articleDao().insertArticle(article) } } articles } catch (e: Exception) { // If network call fails, fetch articles from local database withContext(Dispatchers.IO) { MyApplication.database.articleDao().getAllArticles() } } } // Dummy function to simulate fetching articles from network suspend fun fetchArticlesFromNetwork(): List
{ // Replace this with your actual network call return listOf( Article(1, "Article 1", "Content for Article 1"), Article(2, "Article 2", "Content for Article 2") ) }

Implementing Offline Mode with Hive

Step 1: Project Setup

To get started with Hive in a Flutter project, add the required dependencies in the pubspec.yaml file.


dependencies:
  hive: ^2.2.3
  hive_flutter: ^1.1.0
  path_provider: ^2.1.2
dev_dependencies:
  hive_generator: ^2.0.1
  build_runner: ^2.4.8

Step 2: Initialize Hive

Initialize Hive in the main() function before running the app.


import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart' as path_provider;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final appDocumentDir = await path_provider.getApplicationDocumentsDirectory();
  Hive.init(appDocumentDir.path);
  await Hive.openBox('articles');
  runApp(MyApp());
}

Step 3: Define Data Model

Create a data model and register an adapter to store objects in Hive.


import 'package:hive/hive.dart';

part 'article.g.dart';

@HiveType(typeId: 0)
class Article {
  @HiveField(0)
  final int id;
  @HiveField(1)
  final String title;
  @HiveField(2)
  final String content;

  Article({required this.id, required this.title, required this.content});
}

Generate the adapter file by running the following command in the terminal:


flutter packages pub run build_runner build

Step 4: Store and Retrieve Data

Store and retrieve data from the Hive box.


import 'package:hive/hive.dart';

Future> getArticles() async {
  try {
    // Try to fetch articles from network
    final articles = await fetchArticlesFromNetwork();

    // Save articles to Hive box
    var box = Hive.box('articles');
    box.addAll(articles);

    return articles;
  } catch (e) {
    // If network call fails, fetch articles from Hive box
    var box = Hive.box('articles');
    return box.values.toList().cast
(); } } // Dummy function to simulate fetching articles from network Future> fetchArticlesFromNetwork() async { // Replace this with your actual network call return [ Article(id: 1, title: "Article 1", content: "Content for Article 1"), Article(id: 2, title: "Article 2", content: "Content for Article 2"), ]; }

Data Synchronization

Synchronization is a critical aspect of offline mode implementation. Here’s a basic approach:

  1. Check Network Connectivity: Before making network calls, check if the device is online.
  2. Background Sync: Implement background synchronization using WorkManager (Android) or similar mechanisms to sync data when the network becomes available.
  3. Conflict Resolution: Handle conflicts that arise when local changes conflict with remote data updates. This might involve strategies like last-write-wins, merge conflicts, or user-defined conflict resolution policies.

Considerations

  • Data Size: Limit the amount of data cached locally to avoid excessive storage usage.
  • Security: Ensure that sensitive data is encrypted when stored locally.
  • User Experience: Provide clear indications to the user about the app’s online/offline status.
  • Testing: Thoroughly test the offline functionality under various network conditions.

Conclusion

Implementing offline mode using SQLite or Hive can greatly enhance the user experience of mobile and web applications by allowing continued functionality even without an internet connection. SQLite is suitable for apps requiring SQL-based querying and mature database solutions, while Hive offers a fast, lightweight, and easy-to-use NoSQL solution, particularly for Flutter applications. By following the steps outlined in this blog post, you can effectively integrate offline capabilities into your applications, ensuring a seamless and robust user experience.