ymmooot

Ferry Graphql の File Upload で NoCache にしない方法

Flutter の GraphQL クライアントライブラリである Ferry で File Upload を行う方法は以下の記事が詳しい。
How to Upload files with Ferry GraphQL and Flutter | by Alberto Diaz | Medium
とても助かる。

問題点

MultipartFile に対するシリアライザーを用意して、Ferry が Upload 型を扱えるようにしている。
しかし、それだけだと Ferry がランタイムエラーを起こす。

At this point your are saving your file to the backend but dart raise a runtime error:
This is because Ferry by default try to cache the response and it doesn’t know how to cache the file, so the easiest way to ignore it is to change the fetch policy to a NoCache policy, so we have to change:

記事内では FetchPolicy.NoCache を指定することで、エラーを回避する方法が紹介されている。
これだと、キャッシュ前提で動いてるアプリケーションではキャッシュが更新されないため、古いキャッシュが残り続けることになる。
例えば以下のような mutation があったとする。

fragment UserFragment on User {
  id
  name
  iconUrl
}

mutation UpdateUser($name: String, $icon: File) {
  updateUser(input: {
    name: $name
    icon: $icon
  }) {
    ...UserFragment
  }
}

この mutation は UserFragment のキャッシュを更新するため、その後 UserFragment を取得する query を発行してもキャッシュを利用することができるはずである。しかし mutation を FetchPolicy.NoCache にしてしまうと、同時に更新した name とアップロードして得た iconUrl をキャッシュに反映することができない。
その状態で UserFragment を取得する query を発行すると、古いものが取れてしまう。これを解消するためには、mutation の後に必ずリフェッチするなど、キャッシュの管理が必要となる。それは避けたい。

対策

Ferry が MultipartFile に対して toJson を呼び出すのが原因なため、toJson を呼ばれても問題がないようにする。
Upload は mutation の時だけ使われ、query で取得することはなく実際にキャッシュする必要はないため、toJson はただの空の json を返すだけで良い。

class XMultipartFile extends MultipartFile {
  XMultipartFile(
    super.field,
    super.stream,
    super.length, {
    super.filename,
    super.contentType,
  });

  static Future<XMultipartFile> fromPath(String field, String filePath,
      {String? filename, MediaType? contentType}) async {
    final f = await MultipartFile.fromPath(field, filePath,
        filename: filename, contentType: contentType);
    return XMultipartFile.fromFile(f);
  }

  static XMultipartFile fromFile(MultipartFile file) {
    return XMultipartFile(
      file.field,
      file.finalize(),
      file.length,
      filename: file.filename,
      contentType: file.contentType,
    );
  }

  Map<String, dynamic> toJson() {
    return {};
  }
}

これで Upload 引数に渡すものを XMultipartFile にしてあげれば、FetchPolicy.NoCache にせずとも問題なくなる。
以上。