Thank you for reading my blog posts, I am no longer publishing new content on this platform. You can find my latest content on either mainawycliffe.dev or All Things Typescript Newsletter (✉️)
In this post, I am going to show you how to upload files using GraphQL and flutter. To work with GraphQL in flutter, we are going to use graphql_flutter package, which has over 1000 stars on GitHub and I frequently contribute to.
So, without further ado, let’s get started.
To upload files, we are going to use a very simple schema. Our mutation will accept a file and then return a download URL for the file. We won’t look at the implementation at the server end.
scalar Upload
type Mutation {
upload(file: Upload!): String!
}
NB: If you are interested, you can find the GraphQL Server for this articles’ demo here.
Here is how our app will work:
You can find the accompanying repo for this article on here on GitHub.
For this articles’ demo, we are going to need the following dependencies for our flutter project – graphql_flutter and image_picker.
Let’s add them to our projects pubspec
file.
graphql_flutter: ^2.0.0
image_picker: ^0.6.0
And we are going to import the following packages:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:http/http.dart';
import 'package:http_parser/http_parser.dart';
import 'package:image_picker/image_picker.dart';
Feel free to add any extra dependencies and imports to make your UI look better.
For this article’ demo, we are going to use the image_picker package to pick an image. Then, the picked image can then be uploaded to our server. We are going to build a very simplistic UI for our demo application, where we will have a simple button to pick an image. And when an image is picked, it shall be displayed inside an Image widget, and an upload button revealed. The user can then click the upload button to upload the image.
First, let’s define our GraphQL Mutation – uploadImage
– which will accept variable called $file
of type Upload
. The $file
variable will be the image selected using the image_picker and will be passed to the upload
mutation.
const uploadImage = r"""
mutation($file: Upload!) {
upload(file: $file)
}
""";
Then, inside our StatefulWidget
, let’s add a selectImage
method and a private File property called _image
. When the select image
method is called, it is going to call the ImagePicker.pickImage
method, showing a file picker dialog, where a user picks an image. After a user selects an image, we are going to set the _image
property to the resulting file (image).
Future selectImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
_image = image;
});
}
Next, let’s create our UI using:
@override
Widget build(BuildContext context) {
return Container(
child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
if (_image != null)
Flexible(
flex: 9,
child: Image.file(_image),
)
else
Flexible(
flex: 9,
child: Center(
child: Text("No Image Selected"),
),
),
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
FlatButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.photo_library),
SizedBox(
width: 5,
),
Text("Select File"),
],
),
onPressed: () => selectImage(),
),
// Mutation Widget Here
],
),
)
],
),
);
}
Now, we are just missing is the GraphQL Upload Implementation. There are two approaches for this, and we will look at each below:
Mutation
Widgetgraphql_flutter
package provides some easy to use widgets that you can drop inside your flutter app. One of these widgets is the Mutation widget which you can use to run mutation. But before using the Mutation
widget, we are going to need a GraphQLClient. The GraphQLClient needs to be provided somewhere up our widget tree, preferably at the very beginning.
You can achieve this by creating a GraphQLClient and then pass it down the widget tree using the GraphQLProvider
widget. First, lets create a GraphQLClient wrapped with ValueNotifier:
final httpLink = HttpLink(
uri: 'http://$host:8080/query',
);
var client = ValueNotifier(
GraphQLClient(
cache: InMemoryCache(),
link: httpLink,
),
);
NB: The host points to localhost, which is different between IOS and Android emulator:
String get host => Platform.isAndroid ? '10.0.2.2' : 'localhost';
Next, we need to pass down the above client down the widget tree using the GraphQLProvider
widget. In our case, we are going to wrap the Scaffold widget with it, you can also wrap the MaterialApp with if you so wish.
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: GraphQLProvider(
client: client,
child: Scaffold(
appBar: AppBar(
title: Text("Graphql Upload Image Demo"),
),
body: MyHomePage(),
),
),
);
Now we are ready to use the Mutation widget. To use the Mutation widget, you just need to pass MutationOptions
, a Widget Builder method and an onCompleted
and/or update callbacks. The last two callbacks are called when a mutation is completed. With onCompleted
callback passing the resulting data from the mutation results. While the update
passing the QueryResults object which you can use to check for errors.
To use the Mutation widget, we are going to add it next to the select image button. We are going to also check whether the _image
property is set before displaying the widget.
if (_image != null)
Mutation(
options: MutationOptions(
document: uploadImage,
),
builder: (RunMutation runMutation, QueryResult result) {
return FlatButton(
child: _isLoadingInProgress(),
onPressed: () {},
);
},
onCompleted: (d) {},
update: (cache, results) {},
),
The Mutation widget is going to return an upload button, which a user can press to upload the image. Let’s implement the onPressed
method of the button so that it will upload the image when pressed. First, we are going to prepare the image for uploading by converting it into a MultipartFile
. Then, we can pass the resulting MultipartFile variable as the variable for our Mutation, when calling the runMutation
method passed from the builder.
var byteData = _image.readAsBytesSync();
var multipartFile = MultipartFile.fromBytes(
'photo',
byteData,
filename: '${DateTime.now().second}.jpg',
contentType: MediaType("image", "jpg"),
);
runMutation(<String, dynamic>{
"file": multipartFile,
});
Then we can use the update call-back to check if the upload has been completed successfully and show a response.
update: (cache, results) {
var message = results.hasErrors
? '${results.errors.join(", ")}'
: "Image was uploaded successfully!";
final snackBar = SnackBar(content: Text(message));
Scaffold.of(context).showSnackBar(snackBar);
},
And that’s it when a GraphQL operation completes successfully, a success message is going to be displayed, else the resulting error message will be displayed using a snackbar.
There a lot of people who don’t like using the Mutation widget for various reasons. You can also do that with the graphql_flutter. We are going to replace the Mutation widget above, with a simple button, which will call a method – _uploadImage
– when pressed. We are going to place the _uploadImage
method inside our StatefulWidget.
if (_image != null)
FlatButton(
child: _isLoadingInProgress(),
onPressed: () => _uploadImage(context),
),
Then inside our _uploadImage
method, we are going to first convert the image to a MultipartFile
just like we did before.
var byteData = _image.readAsBytesSync();
var multipartFile = MultipartFile.fromBytes(
'photo',
byteData,
filename: '${DateTime.now().second}.jpg',
contentType: MediaType("image", "jpg"),
);
And then create MutationOptions variable for our mutation, passing the document and the multipart file above.
var opts = MutationOptions(
document: uploadImage,
variables: {
"file": multipartFile,
},
);
Then, create our GraphQLClient:
final httpLink = HttpLink(
uri: 'http://$host:8080/query',
);
var client = GraphQLClient(
cache: InMemoryCache(),
link: httpLink,
);
And finally, run the mutation by calling client.Mutation
function which returns a Future
of QueryResults, which you can use to show the final response:
var results = await client.mutate(opts);
var message = results.hasErrors
? '${results.errors.join(", ")}'
: "Image was uploaded successfully!";
final snackBar = SnackBar(content: Text(message));
Scaffold.of(context).showSnackBar(snackBar);
With this you can use it with scope models, BLoCs or any state management solution you comfortable with.
If you are not interested in using the flutter_graphql widgets, you can instead directly use the graphql package, which is the core of the flutter_graphql and are both maintained by the same team.
In this post, I want to look closely at how dart pub handles dependency versions. The end goal is to help you understand how to manage your …
Read MoreI was in the middle implementing a BottomNavigationBar, when a thought crossed mind. Can I use bloc pattern to manage its state? Admittedly, …
Read MoreOver the last 3 months, I have been involved on a project where I ended up building a GraphQL Server using Go. Before this, I had not worked …
Read More