Flutter入門進階之旅(十二)Flutter 資料儲存

謝棟發表於2019-04-15

前言

之前的章節我們基本上把Flutter中基礎部分的東西都做了簡單的講解,通過前面章節的循序學習讀者也基本能完成一些簡單的UI繪製並能利用Flutter處理一些簡單的使用者互動,讀者可能也留意到,我們之前的章節中所學習到的內容並沒有涉及到資料儲存方面的操作,或者說,我們到現在為止並不知道在Flutter中資料應該怎麼存,存在哪。本篇博文中筆者將會為大家解決這一疑惑。

關於Flutter中的資料儲存

相信做過原生Android開發的讀者對資料儲存並不陌生,在原生Android中我們會把一些輕量級的資料(如使用者資訊、APP配置資訊等)寫入SharedPreferences做儲存,把需要長期儲存的資料寫入本地檔案或者Sqlite3,當然Flutter中也同樣用一套完整的本地資料儲存體系,下面我們就一直來了解下上述提到的這3中本地儲存方式在Flutter中使用。

1.SharedPreferences

在Flutter中本身並沒有內建SharedPreferences儲存,但是官方給我們提供了第三方的元件來實現這一儲存方式。我們可以通過pubspec.yaml檔案引入,關於pubspec.yaml的使用我們在Flutter入門進階之旅(五)Image Widget,這一章節提到過,只不過在Image使用中我們引入的是assets檔案依賴。

如下我們在dependencies節點下引入SharedPreferences的依賴,讀者在pubspec.yaml引入依賴時一定要注意程式碼縮排格式,否則在在執行flutter packages get時很可能會報錯


dependencies:
  flutter:
    sdk: flutter
    
  # 新增sharedPreference依賴
  shared_preferences: ^0.5.0

dev_dependencies:
  flutter_test:
    sdk: flutter
    
  # 引入本地資源圖片
  assets:
     - images/a.png
     - images/aaa.png
複製程式碼

然後命令列執行flutter packages get把遠端依賴同步到本地,在此筆者寫文章的時候sharedPreference的最新版本是0.5.0,讀者可自行去pub.dartlang.org/flutter上獲取最新版本,同時也可以在上面找到其他需要引入的資源依賴包。

筆者的話

囉裡囉嗦的準備工作總算是講完了,主要是今天的課程涉及到了包依賴管理,可能對於有些初學者有點懵,所以我就藉助sharedPreference把依賴引入廢話扯了一大通,如果讀者已經掌握了上述操作,可跳過準備工作直接到下面的部分。

繼續上面的內容,我們先來體驗一下sharedPreference,貼個圖大家放鬆一下。

sharedPreference

從上圖中我們看到我們使用sharedPreference做了簡單儲存跟獲取的操作,其實sharedPreference好像也就這麼點左右,不是存就是取。讀者在自行操作時一定不要忘記匯入sharedPreference的包

import 'package:shared_preferences/shared_preferences.dart';
複製程式碼

存資料

跟原生Android一樣,Flutter中操作sp也是通過key-value的方式存取資料

/**
   * 利用SharedPreferences儲存資料
   */
  Future saveString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    sharedPreferences.setString(
        STORAGE_KEY, _textFieldController.value.text.toString());
  }
複製程式碼

SharedPreferences中為我們提供了String、bool、Double、Int、StringList資料型別的存取。

SharedPreferences

取資料

/**
   * 獲取存在SharedPreferences中的資料
   */
  Future getString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    setState(() {
      _storageString = sharedPreferences.get(STORAGE_KEY);
    });
  }
複製程式碼

上述操作邏輯中我們通過_textFieldController獲取在TextField中的值,在按下儲存按鈕的同時我們把資料寫入sp中,當按下獲取值的時候我們通過setState把從sp中獲取的值同步更新到下面的Text上顯示。

完整程式碼:

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

class StoragePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => StorageState();
}

class StorageState extends State {
  var _textFieldController = new TextEditingController();
  var _storageString = '';
  final STORAGE_KEY = 'storage_key';

  /**
   * 利用SharedPreferences儲存資料
   */
  Future saveString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    sharedPreferences.setString(
        STORAGE_KEY, _textFieldController.value.text.toString());
  }

  /**
   * 獲取存在SharedPreferences中的資料
   */
  Future getString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    setState(() {
      _storageString = sharedPreferences.get(STORAGE_KEY);
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('資料儲存'),
      ),
      body: new Column(
        children: <Widget>[
          Text("shared_preferences儲存", textAlign: TextAlign.center),
          TextField(
            controller: _textFieldController,
          ),
          MaterialButton(
            onPressed: saveString,
            child: new Text("儲存"),
            color: Colors.pink,
          ),
          MaterialButton(
            onPressed: getString,
            child: new Text("獲取"),
            color: Colors.lightGreen,
          ),
          Text('shared_preferences儲存的值為  $_storageString'),


        ],
      ),
    );
  }
}
複製程式碼

2.檔案儲存

雖然我們今天內容是Flutter的資料儲存,尷尬的是Flutter本身都沒有內建提到的這三種儲存方式,不過好在官方給我們提供了三方的支援庫,不知道後續的Flutter版本中會不會對此做改進。操作檔案同樣我們也需要像SharedPreferences一樣,需要在pubspec.yaml引入。在 Flutter 裡實現檔案讀寫,需要使用 path_provider 和 dart 的 io 模組。path_provider 負責查詢 iOS/Android 的目錄檔案,IO 模組負責對檔案進行讀寫

  # 新增檔案依賴
  path_provider: ^0.5.0
複製程式碼

筆者在此引入的最新版本是0.5.0,讀者可自行去pub.dartlang.org/flutter上獲取最新版本。

由於整個操作演示邏輯跟SharedPreferences一樣,我就不詳細講解檔案儲存中關於存取資料的具體操作了,稍微我貼上原始碼,讀者自行查閱程式碼對比即可,關於檔案儲存的三個獲取檔案路徑的方法我這裡說明一下,做過原生Android開發的讀者可能對此不陌生,但是ios或者初學者可能並不瞭解這個概念,所以我想提出來說明一下。

在path_provider中有三個獲取檔案路徑的方法:

  • getTemporaryDirectory()//獲取應用快取目錄,等同IOS的NSTemporaryDirectory()和Android的getCacheDir() 方法
  • getApplicationDocumentsDirectory()獲取應用檔案目錄類似於Ios的NSDocumentDirectory和Android上的 AppData目錄
  • getExternalStorageDirectory()//這個是儲存卡,僅僅在Android平臺可以使用

來看下操作檔案的效果圖

檔案儲存

借用了SharedPreferences儲存的邏輯,只是把儲存的程式碼放在了file.text中,程式碼裡有詳盡的註釋,我就不多做解釋說明了,讀者可自行嘗試對比跟SharedPreferences的差別

樣例程式碼

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

class StoragePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => StorageState();
}

class StorageState extends State {
  var _textFieldController = new TextEditingController();
  var _storageString = '';

  /**
   * 利用檔案儲存資料
   */
  saveString() async {
    final file = await getFile('file.text');
    //寫入字串
    file.writeAsString(_textFieldController.value.text.toString());
  }

  /**
   * 獲取存在檔案中的資料
   */
  Future getString() async {
    final file = await getFile('file.text');
    var filePath  = file.path;
    setState(() {
      file.readAsString().then((String value) {
        _storageString = value +'\n檔案儲存路徑:'+filePath;
      });
    });
  }

  /**
   * 初始化檔案路徑
   */
  Future<File> getFile(String fileName) async {
    //獲取應用檔案目錄類似於Ios的NSDocumentDirectory和Android上的 AppData目錄
    final fileDirectory = await getApplicationDocumentsDirectory();

    //獲取儲存路徑
    final filePath = fileDirectory.path;

    //或者file物件(操作檔案記得匯入import 'dart:io')
    return new File(filePath + "/"+fileName);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('資料儲存'),
      ),
      body: new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("檔案儲存", textAlign: TextAlign.center),
          TextField(
            controller: _textFieldController,
          ),
          MaterialButton(
            onPressed: saveString,
            child: new Text("儲存"),
            color: Colors.cyan,
          ),
          MaterialButton(
            onPressed: getString,
            child: new Text("獲取"),
            color: Colors.deepOrange,
          ),
          Text('從檔案儲存中獲取的值為  $_storageString'),
        ],
      ),
    );
  }
}
複製程式碼

3.Sqflite

在Flutter中的資料庫叫Sqflite跟原生安卓的Sqlite叫法不一樣。我們來看下Sqflite官方對它的解釋說明:

SQLite plugin for Flutter. Supports both iOS and Android.

 Support transactions and batches
 Automatic version managment during open
 Helpers for insert/query/update/delete queries
 DB operation executed in a background thread on iOS and Android
複製程式碼

通過上面的描述,我們瞭解到Sqflite是一個同時支援Android跟Ios平臺的資料庫,並且支援標準的CURD操作,下面我們還是用上面操作檔案跟sp的程式碼邏輯是一塊體驗一下Sqflite

同樣需要引入依賴:

  #新增Sqflite依賴
  sqflite: ^1.0.0
複製程式碼

模擬場景:

利用Sqflite建立一張user表,其中user表中id設定為主鍵id,且為自增長,name欄位為text型別,使用者按下儲存按鈕後,把TextFile輸入框裡的內容插入到user表中,當按下獲取按鈕時,取出資料庫中最後一條資料顯示在下方Text上,並且顯示出當前資料庫中一共有多少條資料,以及資料庫的儲存路徑。

效果圖

Sqflite
上述描述樣式程式碼

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

class StoragePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => StorageState();
}

class StorageState extends State {
  var _textFieldController = new TextEditingController();
  var _storageString = '';

  /**
   * 利用Sqflite資料庫儲存資料
   */
  saveString() async {
    final db = await getDataBase('my_db.db');
    //寫入字串
    db.transaction((trx) {
      trx.rawInsert(
          'INSERT INTO user(name) VALUES("${_textFieldController.value.text.toString()}")');
    });
  }

  /**
   * 獲取存在Sqflite資料庫中的資料
   */
  Future getString() async {
    final db = await getDataBase('my_db.db');
    var dbPath = db.path;
    setState(() {
      db.rawQuery('SELECT * FROM user').then((List<Map> lists) {
        print('----------------$lists');
        var listSize = lists.length;
        //獲取資料庫中的最後一條資料
        _storageString = lists[listSize - 1]['name'] +
            "\n現在資料庫中一共有${listSize}條資料" +
            "\n資料庫的儲存路徑為${dbPath}";
      });
    });
  }

  /**
   * 初始化資料庫儲存路徑
   */
  Future<Database> getDataBase(String dbName) async {
    //獲取應用檔案目錄類似於Ios的NSDocumentDirectory和Android上的 AppData目錄
    final fileDirectory = await getApplicationDocumentsDirectory();

    //獲取儲存路徑
    final dbPath = fileDirectory.path;

    //構建資料庫物件
    Database database = await openDatabase(dbPath + "/" + dbName, version: 1,
        onCreate: (Database db, int version) async {
      await db.execute("CREATE TABLE user (id INTEGER PRIMARY KEY, name TEXT)");
    });

    return database;
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('資料儲存'),
      ),
      body: new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("Sqflite資料庫儲存", textAlign: TextAlign.center),
          TextField(
            controller: _textFieldController,
          ),
          MaterialButton(
            onPressed: saveString,
            child: new Text("儲存"),
            color: Colors.cyan,
          ),
          MaterialButton(
            onPressed: getString,
            child: new Text("獲取"),
            color: Colors.deepOrange,
          ),
          Text('從Sqflite資料庫中獲取的值為  $_storageString'),
        ],
      ),
    );
  }
}

複製程式碼

至此,關於Flutter的本地儲存相關的內容就全部講解完了,在本文章中,我為了清晰程式碼結構跟業務邏輯,複用的都是同一個儲存業務邏輯跟UI便於大家結合程式碼做對比,讀者可結合程式碼自行對比三種儲存方式的細節差別。

相關文章