Flutter 構建完整應用手冊-持久化

將鍵值數據存儲在磁盤上

若是咱們有一小部分咱們想要保存的鍵值,咱們可使用shared_preferences插件。html

一般咱們不得不編寫原平生臺集成來存儲這兩個平臺的數據。 幸運的是,shared_preferences插件可用於此目的。 共享偏好設置插件包裝iOS上的NSUserDefaults和Android上的SharedPreferences,爲簡單數據提供持久存儲。java

創建

在咱們開始以前,咱們須要將shared_preferences插件添加到咱們的pubspec.yaml文件中:android

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

保存數據

要持久化鍵值數據,咱們可使用SharedPreferences類。 爲了保存數據,咱們調用set方法。 請注意,數據是異步持久的。 若是咱們想要在保存數據時獲得通知,請使用commit()函數。緩存

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

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

讀取數據

SharedPreferences prefs = await SharedPreferences.getInstance();

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

在上面的例子中,咱們從counter鍵加載數據,若是它不存在,則返回0app

移除數據

SharedPreferences prefs = await SharedPreferences.getInstance();

prefs.remove('counter');

Settergetter方法適用於全部原始類。less

支持的類型

雖然使用鍵值存儲很是簡單方便,但它有一些限制:異步

  • 只能使用原始類型:int, double, bool, string 和 string list
  • 它不是用來存儲大量數據,所以不適合做爲應用程序緩存。

有關Android上共享首選項的更多信息,請訪問Android開發人員網站上的共享首選項文檔async

例子

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.
    );
  }
}

測試支持

經過運行如下代碼,咱們能夠在咱們的測試中使用初始值填充SharedPreferenceside

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

上面的代碼應放在測試文件夾下的測試文件中。函數

讀寫文件

在某些狀況下,將文件讀取和寫入磁盤可能很是方便。 這可用於跨應用程序啓動持續保存數據或從互聯網上下載數據並保存以供之後脫機使用。

爲了將文件保存到磁盤,咱們須要將path_provider插件與dart:io庫結合使用。

路線

  • 找到正確的本地路徑
  • 建立對文件位置的引用
  • 將數據寫入文件
  • 從文件中讀取數據

1.找到正確的本地路徑

在這個例子中,咱們將顯示一個計數器。 當計數器發生變化時,咱們須要在磁盤上寫入數據,以便在應用程序加載時再次讀取它。 所以,咱們須要問:咱們應該在哪裏存儲這些數據?

path_provider插件提供了一種平臺不可知的方式來訪問設備文件系統上的經常使用位置。 該插件當前支持訪問兩個系統文件位置:

  • 臨時目錄: 一個臨時目錄(緩存),系統能夠隨時清除。 在iOS上,這對應於NSTemporaryDirectory()返回的值。 在Android上,這是getCacheDir()返回的值。
  • 文檔目錄:應用程序的目錄,用於存儲只有它能夠訪問的文件。 只有當應用程序被刪除時,系統纔會清除目錄。 在iOS上,這對應於NSDocumentDirectory。 在Android上,這是AppData目錄。

在咱們的例子中,咱們但願將信息存儲在文檔目錄中! 咱們能夠像這樣找到文檔目錄的路徑:

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

2.建立對文件位置的引用

一旦咱們知道在哪裏存儲文件,咱們須要建立一個文件的完整位置的引用。 咱們可使用dart:io庫中的File類來實現此目的。

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

3.將數據寫入文件

如今咱們有一個File可使用,咱們可使用它來讀取和寫入數據! 首先,咱們將一些數據寫入文件。 因爲咱們正在使用計數器,所以咱們只會將整數存儲爲字符串。

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

4.從文件中讀取數據

如今咱們在磁盤上有一些數據,咱們能夠閱讀它! 再次,咱們將使用File類來完成此操做。

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;
  }
}

測試

爲了測試與文件交互的代碼,咱們須要模擬對MethodChannel的調用。 MethodChannel是Flutter用來與主機平臺進行通訊的類。

在咱們的測試中,咱們沒法與設備上的文件系統進行交互。 咱們須要與咱們的測試環境的文件系統進行交互!

爲了模擬方法調用,咱們能夠在咱們的測試文件中提供一個setupAll函數。 該功能將在測試執行以前運行。

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

完整例子

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),
      ),
    );
  }
}
相關文章
相關標籤/搜索