Backup, Restore & Share JSON file Flutter-img

Backup, Restore & Share JSON file Flutter

This guide uses popular packages to write, restore and share a JSON file in flutter
Mobile apps sometimes require a way to backup and restore data. In this guide we will go through creating a file and writing to it. We will also see how we can share it with some external source and import it again to read the data.

Getting Started

This app will be simple list of people and will have 5 buttons that will:
  1. Write
  2. Read (without file picker)
  3. Share
  4. Clear what's displayed
  5. Import via file picker
We will use a couple of packages to get through this guide.
path_provider: ^2.0.1 // to get the correct path to store our file
permission_handler: ^8.0.0+1 // to handle the permissions to read and write files
share: ^2.0.1 // to share the file that we create
file_picker: ^3.0.1 // to import the file that we saved
We also need to update the permissions that the app uses.

For android add this to your AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
For iOS add this to your info.plist
<key>NSPhotoLibraryUsageDescription</key>
<string>This app requires to save a backup file on device</string>

Creating Person class

Let's create our simple person class that will have a name and a gender
class Person {
  String name;
  String gender;

  Person({this.gender, this.name});

  factory Person.fromJson(Map<String, dynamic> json) { // 1
    return Person(
      name: json['name'] as String,
      gender: json['gender'] as String,
    );
  }

  Map<String, dynamic> toJson() { // 2
    return {
      'name': this.name,
      'gender': this.gender,
    };
  }
}
  1. A factory that takes in a map (JSON) and returns the correct data object
  2. A function that converts the current object to a map that can be stored as JSON
We will use these, as we build the rest of the functionality.

Creating / Writing a file

Let's create a storage class that will host all the data access logic.
import 'dart:convert';
import 'dart:io';

import 'package:backup_restore_json/person.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';

class Storage {
  Future<String> get _localPath async { // 1
    final directory = await getApplicationDocumentsDirectory();
    var path = directory.path;
    return path;
  }

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

  Future<File> writePeople(List<Person> people) async { // 3
    if (!await Permission.storage.request().isGranted) { // 4
      return Future.value(null);
    }

    final file = await _localFile;
    if (!await file.exists()) { // 5
      await file.create(recursive: true);
    }
    String encodedPeople = jsonEncode(people); // 6
    return file.writeAsString(encodedPeople); // 7
  }
}
Here we use the path_provider package to get the correct path to store a file for the current OS.

  1. localPath: returns the path that we will use using getApplicationDocumentsDirectory method from the path_provider package
  2. localFile: returns the file that we will store in the path
  3. Function to write to our JSON file
  4. Uses permissions package to ask for permission if it's been granted it returns true without requesting a second time
  5. checks if the file exists if it does not we create a new file
  6. uses the dart provided jsonEncode function to encode our list of people into a string. This uses our toJson function that we created in our Person class
  7. writes the string to the file
These files cannot be accessed from the file app on android and iOS requires the permission to show them.

Reading From Stored File

Let's read from the file that we wrote to directly without using a file picker. Let's create a new function in our storage class
Future<List<Person>> readPeople(
    [bool local = true, File selectedFile]) async { // 1
  try {
		// start of 2
    File file;
    if (local) {
      file = await _localFile;
    } else {
      file = selectedFile;
    }
		// end of 2

    final jsonContents = await file.readAsString(); // 3
    List<dynamic> jsonResponse = json.decode(jsonContents); // 4
    return jsonResponse.map((i) => Person.fromJson(i)).toList(); // 5
  } catch (e) {
    // If encountering an empty array
    return [];
  }
}
  1. Our function accepts 2 optional arguments in order to reuse this code in our file picker function.
  2. If no argument is provided then we use the local file otherwise we use the selectedFile argument
  3. We read the contents of the file as a JSON string
  4. We use json.decode that is provided by dart in order to decode the string into a json list
  5. We use the factory we created in our Person class as we map the values in jsonResponse

Sharing the File

In a lot of applications once you have your backup stored on the device. You usually have the option to send that backup file somewhere. We will use the share package to do this by adding a simple function to our Storage class.
void share() async {
  File file = await _localFile; // 1
  Share.shareFiles([file.path], text: 'Back up'); // 2
}
  1. We get our local file using our getter
  2. We use the shareFiles function provided by the share package with a list of files we want to share. In our case it is just one file. The package provides much more options for customization but for the sake of our guide, this functionality is enough

Reading from a file using the file picker

Now let's read a file using the file picker by also extending our storage class. We will use the same function for reading a file but pass arguments to it.
Future<List<Person>> readFromFilePicker() async {
  FilePickerResult result = await FilePicker.platform.pickFiles(); // 1

  if (result == null) { // 2
    return Future.value(readPeople());
  }

  File file = File(result.files.single.path); // 3
  var people = readPeople(false, file); // 4
  writePeople(await people); // 5
  return people;
}
  1. Uses the file picker package to open the OS-specific file picker
  2. If no file was chosen then return the normal list that's currently stored
  3. Read the file from the file picker result
  4. get the list of people by using our readPeople function and providing the file
  5. write the new list to our file that is stored in our app

UI

This guide's focus is on the logic and data side, not the UI. Here is the UI implementation for demo and reference purposes.
import 'dart:async';
import 'dart:io';

import 'package:backup_restore_json/person.dart';
import 'package:backup_restore_json/storage.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

class FlutterDemo extends StatefulWidget {
  final Storage storage;

  FlutterDemo({Key key, this.storage}) : super(key: key);

  @override
  _FlutterDemoState createState() => _FlutterDemoState();
}

class _FlutterDemoState extends State<FlutterDemo> {
  List<Person> people = [];

  Future<File> addPeople() {
    setState(() {
      people.addAll([
        new Person(name: 'Mark', gender: 'Male'),
        new Person(name: 'Kate', gender: 'Female'),
        new Person(name: 'Tyler', gender: 'Male'),
      ]);
    });

    return widget.storage.writePeople(people);
  }

  void readPeople() async {
    var importedPeople = await widget.storage.readPeople();
    setState(() {
      people = importedPeople;
    });
  }

  void readPeopleFromFilePicker() async {
    var importedPeople = await widget.storage.readFromFilePicker();
    setState(() {
      people = importedPeople;
    });
  }

  void delete() {
    setState(() {
      people = [];
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Reading and Writing Files')),
      body: Container(
        child: Column(
          children: [
            Row(
              children: [
                TextButton(
                  onPressed: () => addPeople(),
                  child: Text('Write'),
                ),
                TextButton(
                  onPressed: () => readPeople(),
                  child: Text('Read'),
                ),
                TextButton(
                  onPressed: () => widget.storage.share(),
                  child: Text('Share'),
                ),
                TextButton(
                  onPressed: () => delete(),
                  child: Text('Clear'),
                ),
                TextButton(
                  onPressed: () => readPeopleFromFilePicker(),
                  child: Text('File Picker'),
                ),
              ],
            ),
            Expanded(
              child: ListView.builder(
                  padding: const EdgeInsets.all(8),
                  itemCount: people.length,
                  itemBuilder: (context, index) {
                    var person = people[index];
                    return ListTile(
                      title: Text(person.name),
                    );
                  }),
            )
          ],
        ),
      ),
    );
  }
}

Conclusion

In this guide, we used a couple of packages to create a backup and restore functionality that saves data to a JSON file. This should be a good starting point for you to add this kind of functionality to your app.

Like the post?

feel free to share it anywhere

Related Guides

Hive Flutter Database-img

Hive Flutter Database

Efficient, powerful offline storage