Flutter Building a Complete Application Manual - Persistence

Store key-value data on disk

If we have a small set of key values ​​we want to save, we can use the shared_preferences plugin.

Often we have to write native platform integrations to store data for both platforms. Fortunately, the shared_preferences plugin is available for this purpose. The Shared Preferences plugin wraps NSUserDefaults on iOS and SharedPreferences on Android to provide persistent storage for simple data.

Establish

Before we start, we need to add the shared_preferences plugin to our pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  shared_preferences: "<newest version>"

save data

To persist key-value data, we can use the SharedPreferences class. To save the data, we call the set method. Note that data is persisted asynchronously. If we want to be notified when data is saved, use the commit() function.

// obtain shared preferences 
SharedPreferences prefs = await SharedPreferences.getInstance();

// set new value
prefs.setInt('counter', counter);

read data

SharedPreferences prefs = await SharedPreferences.getInstance();

int counter = (prefs.getInt('counter') ?? 0) + 1;

In the above example, we load data from the counter key and return 0 if it doesn't exist .

remove data

SharedPreferences prefs = await SharedPreferences.getInstance();

prefs.remove('counter');

Setter and getter methods are available for all primitive classes.

supported types

While using a key-value store is very simple and convenient, it has some limitations:

  • Only primitive types can be used: int , double , bool , string and  string list
  • It is not intended to store large amounts of data, so it is not suitable as an application cache.

For more information on Shared Preferences on Android, visit the Shared Preferences documentation on the Android Developers website .

example

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

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of our application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Shared preferences demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Shared preferences demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  @override
  void initState() {
    super.initState();
    _loadCounter();
  }

  //Loading counter value on start 
  _loadCounter() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    setState(() {
      _counter = (prefs.getInt('counter') ?? 0);
    });
  }
  
  //Incrementing counter after click
  _incrementCounter() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    _counter = (prefs.getInt('counter') ?? 0) + 1;
    setState(() {
      _counter;
    });
    prefs.setInt('counter', _counter);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

Test support

We can populate SharedPreferences with initial values ​​in our tests by running the following code :

const MethodChannel('plugins.flutter.io/shared_preferences')
  .setMockMethodCallHandler((MethodCall methodCall) async {
    if (methodCall.method == 'getAll') {
      return <String, dynamic>{}; // set initial values here if desired
    }
    return null;
  });

The above code should be placed in a test file under the test folder.

read and write files

In some cases, reading and writing files to disk can be very convenient. This can be used to persist data across application launches or download data from the internet and save it for later offline use.

In order to save the file to disk, we need to use the path_provider plugin in combination with the dart:io library.

route

  • Find the correct local path
  • Create a reference to the file location
  • write data to file
  • read data from file

1. Find the correct local path

In this example, we will display a counter. When the counter changes, we need to write the data on disk so that it can be read again when the application loads. So we need to ask: where should we store this data?

The path_provider plugin provides a platform-agnostic way to access common locations on the device filesystem. The plugin currently supports access to two system file locations:

  • Temporary Directory:  A temporary directory (cache) that the system can clear at any time. On iOS, this corresponds to the value returned by NSTemporaryDirectory() . On Android, this is the value returned by getCacheDir() .
  • Documents directory : The application's directory for storing files that only it can access. The system clears the directory only when the application is removed. On iOS, this corresponds to NSDocumentDirectory . On Android, this is the AppData directory.

In our case, we want to store information in the documents directory! We can find the path to the documents directory like this:

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

2. Create a reference to the file location

Once we know where to store the file, we need to create a reference to the full location of the file. We can do this using the File class from the dart:io library .

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

3. Write data to file

Now that we have a File to work with, we can use it to read and write data! First, we write some data to a file. Since we're using a counter, we'll just store integers as strings.

Future<File> writeCounter(int counter) async {
  final file = await _localFile;
  
  // Write the file
  return file.writeAsString('$counter');
}

4. Read data from file

Now we have some data on disk and we can read it! Again, we'll use the File class to do this.

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

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

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

test

In order to test the code that interacts with the file, we need to mock the call to the MethodChannel . MethodChannel is the class that Flutter uses to communicate with the host platform.

In our tests, we were unable to interact with the filesystem on the device. We need to interact with our test environment's filesystem!

To simulate method calls, we can provide a setupAll function in our test file. This function will run before the test execution.

setUpAll(() async {
  // Create a temporary directory to work with
  final directory = await Directory.systemTemp.createTemp();
  
  // Mock out the MethodChannel for the path_provider plugin
  const MethodChannel('plugins.flutter.io/path_provider')
      .setMockMethodCallHandler((MethodCall methodCall) async {
    // If we're getting the apps documents directory, return the path to the
    // temp directory on our test environment instead.
    if (methodCall.method == 'getApplicationDocumentsDirectory') {
      return directory.path;
    }
    return null;
  });
});

complete example

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

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

void main() {
  runApp(
    new MaterialApp(
      title: 'Reading and Writing Files',
      home: new FlutterDemo(storage: new 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 new File('$path/counter.txt');
  }

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

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

      return int.parse(contents);
    } catch (e) {
      // If we encounter 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 {
  final CounterStorage storage;

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

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

class _FlutterDemoState extends State<FlutterDemo> {
  int _counter;

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

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

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

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

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325459348&siteId=291194637