How to Upload files with Ferry GraphQL and Flutter

Alberto Diaz
3 min readJul 23, 2021

--

Revision 19/06/2022

I have created a repository with an example: https://github.com/p02diada/ferry_upload_example

Hi, I’m going to explain how to upload a file with Ferry and Flutter, with a backend based on the graphQL multipart request spec used by a lot of frameworks as Apollo, graphene or graphql-laravel.

What is Ferry?

Ferry is a simple, powerful GraphQL Client for Flutter and Dart. It’s not the first graphQL client, there are other options as graphql-flutter (Maintenance status: Low), but Ferry has some advantages. The most powerful feature from my point of view is the strongly typed system and the built-in code generators.

How to Upload a file with Ferry?

In order to satisfy the graphQL multipart request Ferry has to use a custom Scalar named Upload. Ferry has a documentation about how to use custom scalars but we are going to show you the code that works for Upload.

First of all we need to update our build.yaml to say Ferry that override the Upload scalar with a MultipartFile of the http package, in the schema_builder, data_builder, var_builder, req_builder:

targets:
$default:
builders:
gql_build|schema_builder:
enabled: true
options:
schema: <your-package-name>|lib/schema.graphql
type_overrides:
Upload:
name: MultipartFile
import: 'package:http/http.dart'
gql_build|ast_builder:
enabled: true
gql_build|data_builder:
enabled: true
options:
schema: <your-package-name>|lib/schema.graphql
type_overrides:
Upload:
name: MultipartFile
import: 'package:http/http.dart'
gql_build|var_builder:
enabled: true
options:
schema: <your-package-name>|lib/schema.graphql
type_overrides:
Upload:
name: MultipartFile
import: 'package:http/http.dart'
ferry_generator|req_builder:
enabled: true
options:
schema: <your-package-name>|lib/schema.graphql
type_overrides:
Upload:
name: MultipartFile
import: 'package:http/http.dart'

You need to change <your-package-name> with your own. Then we have to add a custom serializer for MultipartFile that handle the serialization and deserialization of the objects. The deserialization function just returns the file object that is handled by the gql_http_link as explained in the graphql-flutter documentation. Add the following serializer:

import 'package:built_value/serializer.dart';
import 'package:http/http.dart' show MultipartFile;

class UploadSerializer extends PrimitiveSerializer<MultipartFile> {

@override
MultipartFile deserialize(
Serializers serializers,
Object serialized, {
FullType specifiedType = FullType.unspecified,
}) {
assert(serialized is List<int>,
"FileSerializer expected 'Uint8List' but got ${serialized.runtimeType}");
return MultipartFile.fromBytes("field", serialized as List<int>);
}

@override
Object serialize(
Serializers serializers,
MultipartFile file, {
FullType specifiedType = FullType.unspecified,
}) => file;

@override
Iterable<Type> get types => [MultipartFile];

@override
String get wireName => "Upload";
}

Then we need to update our build.yaml to report Ferry that has to use our new serializer:

gql_build|serializer_builder:
enabled: true
options:
schema: <your-package-name>|lib/schema.graphql
custom_serializers:
- import: 'package:<url-to-upload-serializer-file>/upload_serializer.dart'
name: UploadSerializer

You need to change the <url-to-upload-serializer-file> with your own.

Then you have to add your mutation graphQL file where you define the name of the mutation and the inputs that Ferry uses to create the classes to run the request, for example:

mutation UploadImage($input: UploadImageMutationInput!){
UploadImage(input: $input){
ok,
errors,
}
}

where

input UploadImageMutationInput {
file: Upload
clientMutationId: String
}

This is only an example and this depends on your backend, the UploadImageMutationInput should be autogenerated by the Ferry code generator. Then you can generate the request and set the file inside it as in this example:

import 'package:gql_http_link/gql_http_link.dart';
import 'package:ferry/ferry.dart';

final link = HttpLink("[path/to/endpoint]");
final client = Client(link: link);
final file = MultipartFile.fromString(
"",
"just plain text",
filename: "sample_upload.txt",
contentType: MediaType("text", "plain"),
);
final input = GUploadImageMutationInputBuilder()..file = file;
final req = GUploadImageReq(
(b) => b..vars.input = input,
);
OperationResponse<GUploadImageData, GUploadImageVars> res =
await client.request(req).first;

At this point your are saving your file to the backend but dart raise a runtime error:

Converting object to an encodable object failed: ferry

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:

final req = GUploadImageReq(
(b) => b
..vars.input = input
..fetchPolicy = FetchPolicy.NoCache,
);

And that’s all, now we can upload files to a graphQL server using Ferry, Flutter and Dart.

Thanks for all the Contributors of the Ferry community in Discord.

--

--