1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ひとりアドカレ2024 byもんすんAdvent Calendar 2024

Day 10

Roadmap.shで学ぶFlutter - Advanced Dart - [Core Libraries⑤:io]

Posted at

導入

roadmap.shのFlutterの`Advanced Dartについて学習を進めていきます。

今回はdart:ioライブラリです。
このライブラリは、ファイル、ディレクトリ、プロセス、ソケット、WebSocket、HTTPクライアント、サーバーを扱うためのAPIを提供してくれます。

dart:io

dart:ioライブラリは、ウェブアプリではないのFlutterアプリ、コマンドラインスクリプト、サーバーでのみインポートして使用できます。
ウェブアプリでは使用できません。

dart:ioライブラリは一般的に非同期APIを実装することが推奨されています。同期処理の場合、その処理がアプリケーションをブロックしてしまい、スケールさせることが難しくなる可能性があるためです。
そのため、ほとんどの操作はFutureStreamオブジェクトを介して結果を返します。

同期メソッドもdart:ioライブラリに一応存在はしており、メソッド名に「Sync」サフィックスが付いています。

dart:ioライブラリもdart:mathなどと同様に、使用する際には、以下のようにインポートが必要になります。

import 'dart:io';

ファイルとディレクトリの操作

今回のライブラリを使用すると、コマンドラインアプリでファイルの読み書きやディレクトリのブラウズが可能です。
ファイルの内容を読み込む方法は2通りあります。

  • すべてを一度に読み込む
    • ファイルの内容をすべて格納できるだけのメモリが必要
  • ストリームを使用して読み込む
    • ファイルが非常に大きい場合や、読み込みながら処理したい場合は、Streamを使用するのが良い

テキストとしてファイルを読み込む

UTF-8でエンコードされたテキストファイルを読み込む場合、readAsString()を使用してファイル全体を文字列として読む込むことができます。逆に行ごとに取得したい場合は、readAsLines()を使用することで実現可能です。
いずれの場合も、Futureオブジェクトが返却されます。

void main() async {
  var config = File('config.txt');

  // ファイル全体を1つの文字列に格納
  var stringContents = await config.readAsString();
  print('ファイルは${stringContents.length}文字の長さです。');

  // ファイルの各行をそれぞれの文字列に格納
  var lines = await config.readAsLines();
  print('ファイルには${lines.length}行あります。');
}

バイナリとしてファイルを読み込む

ファイル全体をバイトのリストとして読み込むことも可能です。
readAsBytes()の呼び出しはFutureを返却します。
結果が利用可能になった時点でその結果を提供してくれます。

void main() async {
  var config = File('config.txt');

  var contents = await config.readAsBytes();
  print('ファイルは${contents.length}バイトの長さです。');
}

エラーハンドリング

エラーが発生した際、未処理の例外にならないようにするには、FuturecatchErrorハンドラを登録するか、非同期関数内でtry-catchを使用します。

void main() async {
  var config = File('config.txt');
  try {
    var contents = await config.readAsString();
    print(contents);
  } catch (e) {
    print(e);
  }
}

ストリームでファイル内容を読み込む

Streamを使用してファイルを少しずつ読み込むことができます。
Stream APIや、Dartの非同期サポートの一部であるawait forを使用できます。

import 'dart:io';
import 'dart:convert';

void main() async {
  var config = File('config.txt');
  Stream<List<int>> inputStream = config.openRead();

  var lines = utf8.decoder.bind(inputStream).transform(const LineSplitter());
  try {
    await for (final line in lines) {
      print('ストリームから${line.length}文字を取得');
    }
    print('ファイルが閉じられました');
  } catch (e) {
    print(e);
  }
}

ファイル内容の書き込み

データをファイルに書き込むには、IOSinkを使用します。
FileopenWrite()メソッドを使用して、書き込み可能なIOSinkを取得します。
FileMode.writeは、デフォルトのモードであり、ファイル内の既存のデータを完全に上書きします。

完全上書きでなく、ファイルの末尾にデータを追加したい場合は、オプションのmodeパラメータでFileMode.appendを指定します。

var logFile = File('log.txt');

// ファイルに完全上書きしたい場合
var sink = logFile.openWrite();
sink.write('ファイルがアクセスされました ${DateTime.now()}\n');
await sink.flush();
await sink.close();

// 末尾に追加したい場合
var sink = logFile.openWrite(mode: FileMode.append);

バイナリデータを書き込むには、add(List<int> data)を使用します。

ディレクトリ内のファイル一覧を取得

次は特定のファイルに対するI/Oではなく、ディレクトリに対する操作についてです。
ディレクトリ内のすべてのファイルとサブディレクトリを検索する操作は非同期になります。list()メソッドは、ファイルやディレクトリが見つかるたびにオブジェクトを出力してくれるStream`を返却します。

void main() async {
  var dir = Directory('tmp');

  try {
    var dirList = dir.list();
    await for (final FileSystemEntity f in dirList) {
      if (f is File) {
        print('ファイルを発見: ${f.path}');
      } else if (f is Directory) {
        print('ディレクトリを発見: ${f.path}');
      }
    }
  } catch (e) {
    print(e.toString());
  }
}

HTTPクライアントとサーバー

dart:ioライブラリは、コマンドラインアプリがHTTPリソースにアクセスしたり、HTTPサーバーを実行するためのクラスを提供します。
これを利用することで、HTTPサーバーアプリケーションをFlutterで作成することが可能になります。

HTTPサーバー

HttpServerクラスは、ウェブサーバーを構築するための低レベルの機能を提供します。リクエストハンドラのマッチング、ヘッダーの設定、データのストリーミングなどが可能です。

次のサンプルウェブサーバーは、シンプルなテキスト情報を返します。このサーバーはポート8888で127.0.0.1(localhost)を監視し、/dartパスへのリクエストに応答します。他のパスへのリクエストには、ステータスコード404(ページが見つかりません)で応答します。

void main() async {
  final requests = await HttpServer.bind('localhost', 8888);
  await for (final request in requests) {
    processRequest(request);
  }
}

void processRequest(HttpRequest request) {
  print('リクエストを受信: ${request.uri.path}');
  final response = request.response;
  if (request.uri.path == '/dart') {
    response
      ..headers.contentType = ContentType(
        'text',
        'plain',
      )
      ..write('サーバーからの挨拶');
  } else {
    response.statusCode = HttpStatus.notFound;
  }
  response.close();
}

HTTPクライアント

dart:ioを使用して直接HTTPリクエストを行うのは避けた方が良いとされています。dart:ioHttpClientクラスはプラットフォーム依存であり、単一の実装に結びついています。代わりに、package:httpのような高レベルのライブラリを使用することが推奨されています。

終わりに

今回はdart:ioライブラリについて学習しました。File操作によく利用されるライブラリですが、HTTP通信にも利用できることは言葉上でしか理解できていなかったので、為になりました。ただ、モバイルアプリとして開発する場合は、Flutterはクライアントとなるので、終盤に書いた通り、package:httpのようなライブラリを使用した方が良さそうです。
その点も含めて、今回学習できたことはプラスになったと思うので、今後もこういったCoreライブラリを活用していきたいです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?