檔案讀寫

磁碟檔案的讀寫操作可能會相對方便地實現某些業務場景。它常見於應用啟動期間產生的持久化資料,或者從網路下載資料供離線使用。

為了將檔案儲存到磁碟,你需要結合使用 dart:iopath_provider 這個 package。

參考以下步驟:

  1. 找到正確的本地路徑

  2. 建立一個指向檔案位置的引用

  3. 將資料寫入檔案

  4. 從檔案讀取資料

你可以觀看每週 package 影片來了解更多關於 path_provider 的內容:

1. 找到正確的本地路徑

這個例子裡,我們將會顯示一個計數器,當計數器發生變化時,你將在磁碟中寫入資料,以便在應用載入時重新讀取這些資料。因此,你一定想知道:我應該將這些資料儲存在哪裡?

path_provider package 提供一種平台無關的方式以一致的方式訪問裝置的檔案位置系統。該 plugin 當前支援訪問兩種檔案位置系統:

臨時資料夾:
這是一個系統可以隨時清空的臨時(快取)資料夾。在 iOS 上對應 NSCachesDirectory 的回傳值;在 Android 上對應 getCacheDir() 的回傳值。

Documents 目錄:
供應用使用,用於儲存只能由該應用訪問的檔案。只有在刪除應用時,系統才會清除這個目錄。在 iOS 上,這個目錄對應於 NSDocumentDirectory。在 Android 上,則是 AppData 目錄。

在本範例中,你需要將訊息儲存在 Documents 目錄中。可以按如下所示,找到 Documents 目錄路徑:

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 File('$path/counter.txt');
}

3. 將資料寫入檔案

現在你已經有了可以使用的 File,接下來使用這個檔案來讀寫資料。首先,將一些資料寫入該檔案。由於使用了計數器,因此只需將整數儲存為字串格式,使用 '$counter' 即可呼叫。

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
    final contents = await file.readAsString();

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

完整範例

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