Skip to content

Simple example project using GraphQL-Flutter and Artemis for static type generation

Notifications You must be signed in to change notification settings

baconcheese113/graphql-flutter-artemis-example

Repository files navigation

Flutter Graphql Artemis Example

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/

Goal

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.


Reasons not to use Artemis

  • 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 to child_user to pass it to a child Widget looking for child_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

Schema Generation (introspection)

  1. 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 for GraphQL in the Marketplace. It uses the .graphqlconfig configuration file.

  2. Next, expand the plugin from the bottom of the window and double click on Endpoints->Default GraphQL Endpoint and select Get GraphQL Schema From Endpoint

  3. You should be able to see your schema in graphql/schema.dart


Building the Types

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__


Using Queries

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

pokemon_list.query.graphql

query PokemonList {
    pokemons(limit: 50) {
        count
        results {
            # id is required to use this field with cache
            id
            ...pokemonListCard_item
        }
    }
}

pokemon_list.dart#L12

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();

        ...
    });

Using Fragments

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
}

pokemon_list_card.dart

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>"),
      ),
    );
  }
}

Using Artemis Helper Classes

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)))
      ],
    );
  }),
);

Other Tips

Editing build.yaml

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.

About

Simple example project using GraphQL-Flutter and Artemis for static type generation

Topics

Resources

Stars

Watchers

Forks

Languages