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
PokemonCardWidget can't be reused with data from other GraphQL Operations since it has an explicit dependency on theGAllPokemonData_pokemonstype. - Our
AllPokemonQuery must keep track of the data requirements not only for ourPokemonListitself (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
idfield in ourAllPokemonQuery - Pass the
idto thePokemonCard - Execute a
GetPokemonQuery in ourPokemonCardthat 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.