檔案路徑與讀寫


如果只是想寫讀取檔案,在〈Assets 管理〉中有提及,可以透過 AssetBundle 來的 loadString 等方法,從 assets 資料夾中讀取,那麼寫入呢?

AssetBundle 只提供了一些讀取的方法,若要寫入,基本上可以透過 Dart 提供的標準 io 程式庫,只不過,必須解決路徑的問題,Android、iOS 等彼此間會有路徑不同的問題,雖然試著指定特定路徑也可以,然而 Flutter 本身的目標是跨裝置,最好是有一致的方式來指定路徑。

Flutter 官方推薦使用 path_provider,撰寫文件的這個時間點,其版本是 1.6.11,要使用這個程式庫,可以在 pubspec.yaml 中的 dependencies 區段加入:

dependencies:
  path_provider: ^1.6.11

接著在終端機中執行以下指令取得程式庫:

flutter packages get

為了使用 Dart 標準 iopath_provider,先如下 import

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

path_provider 提供了一些方法,可以取得路徑,來看看其中的幾個方法:

  • getApplicationSupportDirectory:可存放應用程式資料之路徑,這類資料由應用程式自行產生,不曝露給使用者,例如應用程式自行產生的專屬格式檔案,這個資料夾會在應用程式被移除後自動刪除。

  • getApplicationDocumentsDirectory:通常可用來存放應用程式專屬格式,然而是使用者操作而產生的檔案,例如特定格式的應用程式專案封裝檔案,要存放通用格式的使用者資料也可以,不過這個這個資料夾會在應用程式被移除後自動刪除。

  • getExternalStorageDirectory:取得外部儲存裝置的路徑,例如 SD 卡,應用程式被移除後不會影響這個路徑,可用來儲存使用者操作而產生的檔案,然而非應用程式專屬格式的檔案,例如 JPG 格式的照片,若不支援或沒有外部儲存裝置,呼叫此方法會拋出錯誤。

  • getTemporaryDirectory:取得暫存資料夾,這個資料夾隨時可能被系統或使用者操作清除。

來寫個函式取得 getApplicationDocumentsDirectory 的路徑:

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

有了路徑之後,可以進一步建立 File,例如,建立一個代表 message.txt 的 File

Future<File> get _messageFile async {
  final path = await _appDocPath;
  return File('$path/message.txt');
}

有了 File 後,做點基本的讀寫:

Future<String> readMessage() async {
    final file = await _messageFile;
    String contents = await file.readAsString();
    return contents;
}

Future<File> writeMessage(String message) async {
  final file = await _messageFile;
  return file.writeAsString('$message');
}

底下是個應用範例,應用程式會有個輸入欄位,載入 message.txt 的內容,如果一開始沒有這個檔案就建立一個,你在輸入欄位鍵入內容後按下 Enter,會將欄位內容寫入 message.txt:

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

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

Future<File> get _messageFile async {
  final path = await _appDocPath;
  return File('$path/message.txt');
}

Future<String> readMessage() async {
    final file = await _messageFile;
    String contents = await file.readAsString();
    return contents;
}

Future<File> writeMessage(String message) async {
  final file = await _messageFile;
  return file.writeAsString('$message');
}

void main() => runApp(
  MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: Text('Openhome.cc'),
      ),
      body: Message()
    )
  )
);

class Message extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _Message();
}

class _Message extends State<Message> {
  var messageController = TextEditingController(); 
  File messageFile;

  @override
  void initState() {
    super.initState();
    _messageFile
      .then((file) => file.exists())
      .then((exists) async {
        if(exists) {
          messageController.text = await readMessage();
          print(messageController.text);
        }
        else {
          //messageController.text = '';
          messageFile.create();
        }
      });
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: messageController,
      decoration: InputDecoration(
        labelText: '訊息',
        hintText: '無訊息',
        prefixIcon: Icon(Icons.message),
      ),
      textInputAction: TextInputAction.done,
      onSubmitted: (value) => writeMessage(value),
    );
  }
}