Structuring Queries
Since Flutter is built around the principle of Widget composition, it's common to have data spread across many nested widgets. For example, let's say we want to have a PokemonList
Widget that displays a list of PokemonCard
Widgets.
Our Widget tree might look like this:
Our first impulse might be to write a Query like this one:
And use it in the following Widgets:
While this works, it tightly couples our PokemonList
and PokemonCard
Widgets which causes several disadvantages:
- Our
PokemonCard
Widget can't be reused with data from other GraphQL Operations since it has an explicit dependency on theGAllPokemonData_pokemons
type. - Our
AllPokemon
Query must keep track of the data requirements not only for ourPokemonList
itself (in which the query is executed), but also for all child Widgets (i.e.PokemonCard
).
Colocation of Widgets and Data Requirements
A common pattern to overcome these issues is to colocate Widgets and their data requirements. In other words, each Widget should have a corresponding GraphQL definition that specifies only the data needed for that Widget.
A naive implementation of this (don't do this) might be to:
- Request only the
id
field in ourAllPokemon
Query - Pass the
id
to thePokemonCard
- Execute a
GetPokemon
Query in ourPokemonCard
that fetches the data only for that Pokemon
However, this would result in a seperate network request (and subsequent database query) for each Pokemon in the list. Not very efficient.
Instead, we can extract our PokemonCard
's data requirements into a Fragment:
Now our PokemonCard
can depend on the GPokemonCardFragment
type.
This means the PokemonCard
Widget can be reused anywhere the PokemonCardFragment
is used. It also means that if our data requirements for PokemonCard
change (say, if we need to add a height
property), we only need to update our PokemonCardFragment
. Our AllPokemon
Query and any other operations that use PokemonCardFragment
don't need to be updated.
This pattern leads to code that is easier to maintain, test, and reason about.
Fragments on Root Query
The above pattern works even if your data requirements for a single screen include multiple GraphQL queries since you can include Fragments on any GraphQL type, including the root Query
type.
For example, let's say you want to add a user avatar Widget to the header of your PokemonListScreen
that shows the currently logged-in user.
You might structure your queries like so:
Even though you are fetching data from two different root queries (pokemons
and user
), you can use a single Operation
Widget which will make a single network request for the PokemonListScreen
.