I’ve been struggling with how to structure my Flutter app for a while. It uses GraphQL to query and mutate data, and it always ends up with me over-fetching data and either dumping GraphQL files all over the place or moving them into a catch-all folder hard to keep track of.
One of the core principles of GraphQL is only to fetch what you need. To effectively do this, we need to maintain a tight coupling between widgets and Queries. Another principle is only to fetch things once! This sounds like opposing ideas, really. How can we split up the, e.g., our queries and gather them together simultaneously!?
In a recent major app refactor, I got the opportunity to look at this problem with fresh eyes. I had recently had a chance to work more with Relay at my new job, which made me take another look at a language feature of GraphQL that I knew well, but never thought of using like this, namely fragments!
Before I get into how I ultimately structured my code, let’s have a look at my setup:
- I am using GraphQL Client, developed by the Dutch ticketing sales platform, Zino App.
- For Apollo-like flutter widgets, I am using the corresponding GraphQL-flutter package developed by the same team.
Now, GraphQL is typed, Dart is typed, so the stars really align and whisper, nay scream, CODEGENERATION!
I can not begin to stress how important typing and code-generation are for developing and maintaining any app of a certain size! Long gone are the days where YOLO typing prevailed into large GLHF libraries, relying on manic resource-heavy static analyses to violently beat your CPU into giving you a resemblance of a decent developing experience.
So, for code-generation, I used a very nice project called Artemis. This also comes with a client which can be conveniently disabled for our purpose, leaving us with an automatic generation of serializers. Serializers are not a new phenomenon. Utility classes parsing and imposing structure on seemingly arbitrary JSON data have been around since approximately the 1800s (please don’t Google this). They allow us to make assumptions about our data and share it between different parts of our program without assuming anything of their origins.
Making Artemis work with GraphQL flutter is fairly straightforward. Artemis generates documents and serializers from
.graphql files. We then call the client with the generated document, optionally with some variables for which Artemis also generates serializers, and lastly parse the result using the generated serializers.
This looks something like this:
An obvious next step is to add helper widgets handling serializing to and from JSON. While not perfect, this is a major leap in the right direction.
This approach relies on the developer to remember to use the right serializers with the right queries and the right documents and the right variables (but only when variables are required). Every. Single. Time!
I don’t suppose that I know developers in general, but I do know myself, and if I have to write some resembling code twice, you bet I’ll be hitting that sweet CTRL-C + CTRL-V combo, like a junior developer seeing stack-overflow for the first time! Even though I might just as well have rewritten the entire thing again. This is how you get bugs in your code while ensuring it’ll be an absolute nightmare to maintain.
Full disclosure, the Artemis client will most likely alleviate this by bunding serializers and documents into a convenient class. Still, we want to use another client, so that really doesn’t apply to us.
Furthermore, this is not getting us closer to our goal of splitting up our queries and preventing over-fetching.
Let’s try and take a step back and see how we can use GraphQL to solve our problem with unstructured code and then see if we can find tools to solve our problem.
To prevent over-fetching, each widget should ideally be able to define the data it needs. This suggests a close coupling between the widgets and the queries, something we could express by creating files for a given widget, e.g.
super_duper_widget.dart and accompanying GraphQL files, in this case
super_duper_widget.graphql. This GraphQL file can contain all mutations used and data consumed by the widget. The widget defines the GraphQL file giving absolute control over what fields it’s exposing, driven by what fields it needs.
With this mentality, we can never have a GraphQL file without a Dart file, and other Dart files should never rely on the structure of the accompanying operations of other Dart files but instead define their own companion GraphQL file. If we need to reuse a mutation, we can do so through the Dart file.
Notice, the Dart file does not need to contain a single widget or even a widget at all! It could contain mixins, functions, etc.
The “rules” outlined above encourages a file structure looking something like the following:
This layout gives complete autonomy to the individual widgets to define what data they need, giving us the ability to prevent over-fetching!
Okay, split up the GraphQL files and join the cheering masses flocking to the streets to celebrate our achievement!
… Not quite yet.
The second problem outlined at the beginning of this post was only to fetch things once! This is obviously a gross simplification with many exceptions to the rule. You don’t want to download all the content for your whole app once; talk about over-fetching! But what if we can split up our app a few large parts and fetch these once. E.g., a page?
Normally, a Flutter app consists of a few pages. These contain a
Scaffold defining app bars, bottom bars, dive bars, and bodies, and are pushed and popped off the navigator as the user moves around the app.
By defining a single query fetching all data necessary for the page in the companion GraphQL file, this sounds like a decent step in the right direction!
But how are the widgets supposed to define their data without queries? With fragments! Fragments are reusable pieces of selections that can be reused and applied throughout your documents.
Given the file structure above, this might look something like this:
Notice how we have defined a query for the
HomePage widget and fragments for both the
PersonCard widget and the
This is intentionally a fairly straightforward example. We’re not working on advanced abstract types or multiple widgets using the same data. But for each selection set, we can expand multiple fragments without fetching common data twice. It also works on interfaces/unions as any other type.
In the example above, you might notice how I’ve completely left out any actual data fetching and conveniently assumed that we have some dart-types available to represent the fragments, e.g., the
While we might have an idea of how we want to approach structuring our app, we still need to tackle the problem of convenient maintainable helpers and serializers, preferably generated from our operations. This becomes increasingly important as the number of GraphQL files is bound to grow linearly with the number of dart files.
To solve this problem, I’ve created a new package called GraphQL Codegen. In addition to generating the serializers and variables we need, it can generate helpers for GraphQL Client and GraphQL Flutter that can be used without requiring you to rewrite your whole app. A guide on how to get started with the code generator is available on the package site.
The code generator has great support for fragments by generating an interface for each fragment. You can then annotate your constructors and other arguments with the generated interface, clearly indicating what data you’ll need and what fragment spreads they should add to their operation/fragment.
The fragment interfaces also serve to conveniently let you handle unions and interfaces in your dart code. Take the following document:
Here we define a fragment for each concrete type of the
Person interface. With GraphQL Codegen, we’ll then handle the different types with a simple type check:
Now, the GraphQL Codegen library does not generate helpers for all features of the GraphQL library (yet) and does have a pretty steep requirement on the Dart version wrt. Null safety. This is due to me prioritizing the features I needed for my project, but adding wider support, e.g., subscriptions, should be fairly straightforward. PRs are always welcome!
Regardless, the tool has significantly reduced the time I need to spend on coding and linking up serializer, ensuring that a lot (like a lot a lot) of errors are caught by the dart type checker before ever going into production.
To conclude, I’ve now proposed a maintainable and scalable strategy for structuring your GraphQL flutter app by defining fragments from dart files and creating a single query per page. I’ve also introduced a new code generator, GraphQL Codegen, that works out of the box with GraphQL Client and Flutter, allowing you to parse your data around between widgets in a type-safe way, completely utilizing the types-systems of Dart and GraphQL.
Hopefully, this will prove as useful to you as it has to me! Please don’t hesitate to reach out if you have any questions or feedback.