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
にせずとも問題なくなる。
以上。