讀取與寫入檔案
在某些情況下,你可能需要在磁碟上讀取與寫入檔案。 例如,你可能需要讓資料在應用程式(app)重啟後仍然保留, 或是從網路下載資料並儲存起來以便離線使用。
若要在行動裝置或桌面應用程式中將檔案儲存到磁碟, 可以結合 path_provider 套件與 dart:io 函式庫來實現。
本教學範例包含以下步驟:
- 找到正確的本機路徑。
- 建立檔案位置的參考。
- 將資料寫入檔案。
- 從檔案讀取資料。
想了解更多,請觀看這支介紹 path_provider 套件的「本週套件」影片:
Watch on YouTube in a new tab: "path_provider | Flutter package of the week"
1. 找到正確的本機路徑
#這個範例會顯示一個計數器。當計數器變動時, 會將資料寫入磁碟,這樣當應用程式再次載入時就能讀取到這些資料。 那麼,應該將這些資料儲存在哪裡呢?
path_provider 套件 提供了一種與平台無關的方式,讓你可以存取裝置檔案系統中常用的位置。該套件目前支援存取 兩個檔案系統位置:
- 暫存目錄(Temporary directory)
- 一個暫存目錄(快取),系統可能隨時清除。於 iOS 上,對應到
NSCachesDirectory。在 Android 上,則是getCacheDir()所回傳的值。 - 文件目錄(Documents directory)
- 應用程式專用的檔案儲存目錄,只有該 app 可以存取。系統只會在 app 被刪除時才清除該目錄。 在 iOS 上,對應到
NSDocumentDirectory。 在 Android 上,則是AppData目錄。
本範例會將資料儲存在文件目錄中。 你可以透過以下方式取得文件目錄的路徑:
import 'package:path_provider/path_provider.dart';
// ···
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}2. 建立檔案位置的參考
#當你已經知道要將檔案儲存在哪裡時,請建立一個指向該檔案完整位置的參考。你可以使用 File 類別,這個類別來自 dart:io 函式庫,來達成這個目的。
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),
),
);
}
}