Simple Flutter example project using graphql-flutter and artemis with a focus on folder/file structure.
Uses the PokeApi schema available here https://graphql-pokeapi.graphcdn.app/
This project aims to replicate a typical client structure using npm packages Apollo or Relay.js. and act as a point of reference. In general, queries and fragments should live as close as possible to the widgets that use them and should not be reused across multiple widgets.
- As of now, Artemis doesn't support parsing out graphql queries from
.dart
files, so each query needs to be separated into it's own.graphql
file. This is suboptimal because it adds a level of separation between the code and the query definition, and can make refactoring more difficult. - Unlike the gql function commonly used for creating the Document obj, Artemis doesn't automatically add
__typename
to allow the cache to function properly. So it will need to be manually added to each object. - Fragments on the same object as a parent can't be passed. So if you have have the following you'll have to cast
parent_user
tochild_user
to pass it to a child Widget looking forchild_user
. See this issue
fragment parent_user on User {
__typename
id
firstName
...child_user
}
fragment child_user on User {
__typename
id
lastName
}
ChildWidget(userFrag: widget.userFrag), // <-- this gives an error
ChildWidget(userFrag: widget.userFrag as ChildUserMixin), // <-- this works
-
The schema is included in this project, but you can regenerate it with the GraphQL Android Studio plugin. You can add it in Android Studio from
File->Preferences->Plugins
and then searching forGraphQL
in the Marketplace. It uses the .graphqlconfig configuration file. -
Next, expand the plugin from the bottom of the window and double click on
Endpoints->Default GraphQL Endpoint
and selectGet GraphQL Schema From Endpoint
-
You should be able to see your schema in graphql/schema.dart
Once you have the schema, you're ready to generate the type files using Build Runner. In the terminal use the command:
flutter pub run build_runner build --delete-conflicting-outputs
This also deletes the old generated types if they exist. You should now be able to see the Artemis types in lib/__generated__
Here's an example using a Query Widget with the .graphql queries and Artemis types.
Make sure to use the
.query.graphql
and.fragment.graphql
filename postfixes
query PokemonList {
pokemons(limit: 50) {
count
results {
# id is required to use this field with cache
id
...pokemonListCard_item
}
}
}
return Query(
options: QueryOptions(
document: POKEMON_LIST_QUERY_DOCUMENT,
// operationName is optional
operationName: POKEMON_LIST_QUERY_DOCUMENT_OPERATION_NAME,
),
builder: (QueryResult result, {fetchMore, refetch}) {
if (result.isLoading) return const CircularProgressIndicator();
if (result.hasException) return Center(child: Text(result.exception!.toString()));
final data = PokemonList$Query.fromJson(result.data!);
final cardList = data.pokemons!.results!.map((pokemon) {
print("Pokemon name: ${pokemon!.name}");
return PokemonListCard(itemFrag: pokemon);
}).toList();
...
});
This fragment is used with the query from the above section
pokemon_list_card.fragment.graphql
fragment pokemonListCard_item on PokemonItem {
# __typename and id are required to use fragment with cache
__typename
id
url
name
image
artwork
}
class PokemonListCard extends StatelessWidget {
final PokemonListCardItemMixin itemFrag;
const PokemonListCard({Key? key, required this.itemFrag}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
leading: itemFrag.image != null ? Image.network(itemFrag.image!) : null,
title: Text(itemFrag.name ?? "<No Name>"),
),
);
}
}
If you pass true
to generate_helpers in build.yaml Artemis will generate a class for each query/mutation similar to the following
// ***** This code is generated in api.graphql.dart ******
class PokemonDetailScreenQuery extends GraphQLQuery<PokemonDetailScreen$Query,
PokemonDetailScreenArguments> {
PokemonDetailScreenQuery({required this.variables});
@override
final DocumentNode document = POKEMON_DETAIL_SCREEN_QUERY_DOCUMENT;
@override
final String operationName =
POKEMON_DETAIL_SCREEN_QUERY_DOCUMENT_OPERATION_NAME;
@override
final PokemonDetailScreenArguments variables;
@override
List<Object?> get props => [document, operationName, variables];
@override
PokemonDetailScreen$Query parse(Map<String, dynamic> json) =>
PokemonDetailScreen$Query.fromJson(json);
}
You can then use the helper class to configure the query and parse the result
// We can set query as a variable here just to decrease bloat
final query = PokemonDetailScreenQuery(
variables: PokemonDetailScreenArguments(name: args.name),
);
return Query(
options: QueryOptions(
document: query.document,
variables: query.variables.toJson(),
),
builder: (result, {fetchMore, refetch}) {
if (result.isLoading) return const CircularProgressIndicator();
if (result.hasException) return Text(result.exception.toString());
final data = query.parse(result.data!);
final pokemon = data.pokemon!;
return Column(
children: [
Image.network(data.pokemon.sprites!.backDefault!),
Center(child: Text(args.name, style: const TextStyle(fontSize: 24)))
],
);
}),
);
Artemis uses Build_Runner to generate Dart files. Build_Runner uses a build.yaml
configuration file and Artemis (being a builder) is listed under builders
and has it's own list of configuration options. You'll receive this error
Schema validation: Property 'targets' is not allowed
until you configure a JSONSchema in Android Studio for the Dart Build System to understand build.yaml
. Just follow the directions here to resolve it.