Flutter Data Persistence

6 mins

6 mins

Ashutosh

Published on Oct 20, 2024

Flutter Data Persistence: Reading & Writing Files for Data Storage

In today's digital landscape, data persistence is essential for any successful mobile or web app. Users expect their data to remain available even after closing the app, which is where data persistence comes into play. Whether you're developing a cross-platform mobile app with Flutter, building for the web, or working with desktop applications, understanding data persistence is vital to providing a seamless user experience.

In our previous blogs, we discussed topics such as JSON parsing and efficient JSON handling to avoid jank. This time, we’ll explore how to read and write files in Flutter using different methods like key-value storage, file handling, and SQLite, ensuring that your data persists across app launches. This guide will help you navigate the options available for storing data on disk in Flutter, which is especially useful for applications requiring data persistence.

Data Persistence

Data persistence allows apps to retain and manage information across multiple sessions, providing a seamless user experience. Without proper data persistence, your app could lose critical data when closed or minimized, leading to poor user experience and unreliable functionality. Whether you are building a business app, a social platform, or an e-commerce solution, implementing effective data persistence techniques is crucial.

Overview of Flutter’s Data Persistence Options

Flutter offers several methods for persisting data on disk, each tailored to specific use cases. These methods include:

  1. Key-value storage (via SharedPreferences) – Ideal for storing small, simple data such as user preferences or app settings.

  2. File handling – Suitable for storing larger amounts of data, such as user-generated content or text files.

  3. SQLite – Perfect for handling complex, relational data and large datasets requiring advanced queries.

Understanding these methods and their use cases can help you choose the most appropriate technique for your app's needs. Now, let’s dive into each method in more detail.

Storing Key-Value Data

SharedPreferences is the best tool for small amounts of data, such as user preferences or lightweight settings. This method allows you to store simple key-value pairs persistently across app launches.

How to Use SharedPreferences in Flutter

  1. Add the Dependency To use SharedPreferences, include the following in your

    dependencies:
      shared_preferences: ^2.0.9
  2. Save Data SharedPreferences allows you to save simple data types such as integers, strings, and booleans:

    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setInt('counter', 10);
  3. Retrieve Data You can easily retrieve the stored data using methods like getInt, getString, etc.:

    SharedPreferences prefs = await SharedPreferences.getInstance();
    int counter = prefs.getInt('counter') ?? 0;
  4. Remove Data To delete data, use the remove() method:

    prefs.remove('counter');

Read and write files

Reading and Writing Files in Flutter

If you need to store larger amounts of data or files (such as user-generated content or downloaded data), you should read and write files using Flutter’s file handling methods.

Steps to Read and Write Files in Flutter

  1. Add the Path Provider Dependency Use the path_provider package to access the file system:

    dependencies:
      path_provider: ^2.0.3
  2. Get the Local Path To find where to store your files, use the following:

    final directory = await getApplicationDocumentsDirectory();
  3. Write Data to a File Create a function to write data to a file:

    Future<File> writeCounter(int counter) async {
      final file = File('${directory.path}/counter.txt');
      return file.writeAsString('$counter');
    }
  4. Read Data from a File You can read the data using readAsString():

    Future<int> readCounter() async {
      try {
        final file = File('${directory.path}/counter.txt');
        String contents = await file.readAsString();
        return int.parse(contents);
      } catch (e) {
        return 0;
      }
    }

File storage in Flutter is versatile and can handle more complex use cases such as saving images, large text files, or binary data. This method is often used in cross-platform app development, especially in applications where data needs to be available offline.

Persisting Data with SQLite in Flutter

SQLite is a powerful choice for structured data. SQLite allows for more complex data persistence and querying, making it perfect for apps requiring relational data storage or frequent read/write operations.

Implementing SQLite in Flutter

  1. Add SQLite Dependency To get started with SQLite, add these dependencies:

    dependencies:
      sqflite: ^2.0.0
      path_provider: ^2.0.3
  2. Initialize the Database Use SQLite to create a local database:

    Future<Database> initDB() async {
      final directory = await getApplicationDocumentsDirectory();
      final path = '${directory.path}/app.db';
      return openDatabase(path, version: 1, onCreate: (db, version) {
        return db.execute('CREATE TABLE items(id INTEGER PRIMARY KEY, name TEXT)');
      });
    }
  3. Perform CRUD Operations SQLite supports all basic CRUD operations.

    Insert Data:

    Future<void> insertItem(String name) async {
      final db = await initDB();
      await db.insert('items', {'name': name});
    }

    Read Data:

    Future<List<Map<String, dynamic>>> getItems() async {
      final db = await initDB();
      return db.query('items');
    }

SQLite is particularly useful in Flutter development when handling large datasets or complex queries to manage relational data is required. If you're developing apps for cross-platform frameworks like Flutter, SQLite provides the efficiency and reliability you need for data persistence.

Choosing the Right Data Persistence Method

Different apps have different needs when it comes to data storage. Here's a quick guide to choosing the right method:

  • Key-Value Storage: Best for small, simple datasets like preferences or settings.

  • File Storage: Ideal for larger, unstructured data like user-generated content, documents, or media.

  • SQLite: The go-to option for complex, structured data where you need to perform advanced queries or manage relational data.

Complete example

import 'dart:async';
import 'dart:io';

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

void main() {
  runApp(
    MaterialApp(
      title: 'Reading and Writing Files',
      home: FlutterDemo(storage: CounterStorage()),
    ),
  );
}

class CounterStorage {
  Future<String> get _localPath async {
    final directory = await getApplicationDocumentsDirectory();

    return directory.path;
  }

  Future<File> get _localFile async {
    final path = await _localPath;
    return File('$path/counter.txt');
  }

  Future<int> readCounter() async {
    try {
      final file = await _localFile;

      // Read the file
      final contents = await file.readAsString();

      return int.parse(contents);
    } catch (e) {
      // If encountering an error, return 0
      return 0;
    }
  }

  Future<File> writeCounter(int counter) async {
    final file = await _localFile;

    // Write the file
    return file.writeAsString('$counter');
  }
}

class FlutterDemo extends StatefulWidget {
  const FlutterDemo({super.key, required this.storage});

  final CounterStorage storage;

  @override
  State<FlutterDemo> createState() => _FlutterDemoState();
}

class _FlutterDemoState extends State<FlutterDemo> {
  int _counter = 0;

  @override
  void initState() {
    super.initState();
    widget.storage.readCounter().then((value) {
      setState(() {
        _counter = value;
      });
    });
  }

  Future<File> _incrementCounter() {
    setState(() {
      _counter++;
    });

    // Write the variable as a string to the file.
    return widget.storage.writeCounter(_counter);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Reading and Writing Files'),
      ),
      body: Center(
        child: Text(
          'Button tapped $_counter time${_counter == 1 ? '' : 's'}.',
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

Conclusion

Data persistence is a key aspect of building reliable, user-friendly apps in Flutter. By leveraging tools like SharedPreferences, file handling, and SQLite, you can ensure that your app offers a seamless experience across sessions and platforms. Whether you're working on a mobile app or exploring Flutter for web development, mastering these persistence techniques will help you create better, more resilient applications.

At Blup, we offer powerful low-code solutions that streamline app development with Flutter, providing features that make data persistence easier and more efficient. Learn more about how Blup can accelerate your Flutter app development journey by visiting our platform. Don’t forget to explore our additional resources on cross-platform frameworks, Flutter learning, and app-building software to enhance your skills further!

Join our Blup community for more updates and tutorials, and stay ahead in the evolving world of app development.