Fetch and print the GraphQL schema from a GraphQL HTTP endpoint. (Can be used for Relay Modern.)
get-graphql-schema
Fetch and print the GraphQL schema from a GraphQL HTTP endpoint. (Can be used for Relay Modern.)
Note: Consider using graphql-cli instead for improved workflows.
Install
npm install -g get-graphql-schema
Usage
Usage: get-graphql-schema [OPTIONS] ENDPOINT_URL > schema.graphql
Fetch and print the GraphQL schema from a GraphQL HTTP endpoint
(Outputs schema in IDL syntax by default)
Options:
--header, -h Add a custom header (ex. 'X-API-KEY=ABC123'), can be used multiple times
--json, -j Output in JSON format (based on introspection query)
--version, -v Print version of get-graphql-schema
Help & Community
Join our Slack community if you run into issues or have questions. We love talking to you!
Simple, opinionated, codegen library for GraphQL. It allows you to
generate serializers and client helpers to easily call and parse your data.
pub.dev
Additionally, we will be using,
Provider — For state management.
flutter_secure_storage — to store user auth data locally.
get_it — to locate our registered services and view-models files.
build_runner — to generate files. We will configure graphql_codegen with this to make code generation possible.
Alternative package to work with GraphQL:
Ferry
I found this package very complicated but feel free to try this out.
This package will help in making GraphQL API requests. This will also be a code-generation tool to convert schema files (.graphql) to dart types (.dart).
Here, in the options section we have, assetsPath: All the GraphQL-related code will be placed inside lib/graphql/ so we are pointing it to that folder. outputDirectory: is where we want our generated code to reside. So create the following folders.
lib/graphql/generated/
lib/graphql/queries/generated/
Getting the schema file
Install get-graphql-schema globally using npm or yarn and run it from your project root directory.
# Install using yarn
yarn global add get-graphql-schema
# Install using npm
npm install-g get-graphql-schema
In the lib/main.dart we will call setupLocator() in the main() function as shown below.
// main.dartimport'package:flutter/material.dart';import'package:provider/provider.dart';import'package:graphql_flutter/graphql_flutter.dart';import'package:auth_app/locator.dart';import'package:auth_app/ui/views/login.view.dart';import'package:auth_app/core/services/base.service.dart';import'package:auth_app/core/services/auth.service.dart';voidmain()async{// If you want to use HiveStore() for GraphQL caching.// await initHiveForFlutter();setupLocator();runApp(constApp());}classAppextendsStatelessWidget{constApp({Key?key}):super(key:key);@overrideWidgetbuild(BuildContextcontext){returnGraphQLProvider(client:locator<BaseService>().clientNotifier,child:ChangeNotifierProvider.value(value:locator<AuthService>(),child:constMaterialApp(title:'your_app',debugShowCheckedModeBanner:false,home:LoginView(),),),);}
Now we will create the remaining files.
Our data models:
Our base service file contains a configured graphql client which will be used to make the API requests to the server:
// core/services/base.service.dartimport'dart:async';import'package:flutter/foundation.dart';import'package:jwt_decode/jwt_decode.dart';import'package:graphql_flutter/graphql_flutter.dart';import'package:auth_app/locator.dart';import'package:auth_app/core/services/auth.service.dart';import'package:auth_app/core/services/secure_storage.service.dart';import'package:auth_app/graphql/queries/__generated__/auth.graphql.dart';import'package:auth_app/graphql/__generated__/your_app.schema.graphql.dart';classBaseService{lateGraphQLClient_client;lateValueNotifier<GraphQLClient>_clientNotifier;bool_renewingToken=false;GraphQLClientgetclient=>_client;ValueNotifier<GraphQLClient>getclientNotifier=>_clientNotifier;BaseService(){finalauthLink=AuthLink(getToken:_getToken);finalhttpLink=HttpLink("http://localhost:8000/graphql");/// The order of the links in the array matters!finallink=Link.from([authLink,httpLink]);_client=GraphQLClient(link:link,cache:GraphQLCache(),//// You have two other caching options.// But for my example I won't be using caching.//// cache: GraphQLCache(store: HiveStore()),// cache: GraphQLCache(store: InMemoryStore()),//defaultPolicies:DefaultPolicies(query:Policies(fetch:FetchPolicy.networkOnly)),);_clientNotifier=ValueNotifier(_client);}Future<String?>_getToken()async{if(_renewingToken)returnnull;finalstorageService=locator<SecureStorageService>();finalauthData=awaitstorageService.getAuthData();finalaT=authData.accessToken;finalrT=authData.refreshToken;if(aT==null||rT==null)returnnull;if(Jwt.isExpired(aT)){finalrenewedToken=await_renewToken(rT);if(renewedToken==null)returnnull;awaitstorageService.updateAccessToken(renewedToken);return'Bearer $renewedToken';}return'Bearer $aT';}Future<String?>_renewToken(StringrefreshToken)async{try{_renewingToken=true;finalresult=await_client.mutate$RenewAccessToken(Options$Mutation$RenewAccessToken(fetchPolicy:FetchPolicy.networkOnly,variables:Variables$Mutation$RenewAccessToken(input:Input$RenewTokenInput(refreshToken:refreshToken),),));finalresp=result.parsedData?.auth.renewToken;if(respisFragment$RenewTokenSuccess){returnresp.newAccessToken;}else{if(result.exception!=null&&result.exception!.graphqlErrors.isNotEmpty){locator<AuthService>().logout();}}}catch(e){rethrow;}finally{_renewingToken=false;}returnnull;}}
We will use _client in the file above to make the GraphQL API requests. We will also check if our access-token has expired before making an API request and renew it if necessary.
File auth.service.dart contains all Auth APIs service functions:
// core/services/auth.service.dartimport'package:flutter/material.dart';import'package:graphql_flutter/graphql_flutter.dart';import'package:auth_app/locator.dart';import'package:auth_app/core/models/auth.model.dart';import'package:auth_app/core/services/base.service.dart';import'package:auth_app/core/services/secure_storage.service.dart';import'package:auth_app/graphql/queries/__generated__/auth.graphql.dart';import'package:auth_app/graphql/__generated__/your_app.schema.graphql.dart';classAuthServiceextendsChangeNotifier{Auth?_auth;finalclient=locator<BaseService>().client;finalstorageService=locator<SecureStorageService>();Auth?getauth=>_auth;Future<void>initAuthIfPreviouslyLoggedIn()async{finalauth=awaitstorageService.getAuthData();if(auth.accessToken!=null){_auth=Auth.fromAuthData(auth);notifyListeners();}}Future<void>login(Input$LoginInputinput)async{finalresult=awaitclient.query$Login(Options$Query$Login(variables:Variables$Query$Login(input:input),));finalresp=result.parsedData?.auth.login;if(respisFragment$LoginSuccess){_auth=Auth.fromJson(resp.toJson());storageService.storeAuthData(_auth!);notifyListeners();}else{throwgqlErrorHandler(result.exception);}}Future<void>registerUser(Input$UserInputinput)async{finalresult=awaitclient.mutate$RegisterUser(Options$Mutation$RegisterUser(variables:Variables$Mutation$RegisterUser(input:input),));finalresp=result.parsedData?.auth.register;if(respis!Fragment$RegisterSuccess){throwgqlErrorHandler(result.exception);}}Future<void>logout()async{awaitlocator<SecureStorageService>().clearAuthData();_auth=null;notifyListeners();}// You can put this in a common utility functions so// that you can reuse it in other services file too.//StringgqlErrorHandler(OperationException?exception){if(exception!=null&&exception.graphqlErrors.isNotEmpty){returnexception.graphqlErrors.first.message;}return"Something went wrong.";}}
In the above base view, we use the Provider as a state management tool. The base view model extends ChangeNotifier which notifies its view when the notifyListeners() function is called in the View Model.
Now, We will be using the base view and base view model for our login view and login view model:
// ui/views/login.view.dartimport'package:flutter/material.dart';import'package:auth_app/ui/shared/base.view.dart';import'package:auth_app/core/view_models/login.vm.dart';classLoginViewextendsStatelessWidget{constLoginView({super.key});@overrideWidgetbuild(BuildContextcontext){returnBaseView<LoginViewModel>(builder:(context,loginVm,child){returnScaffold(key:loginVm.scaffoldKey,body:SafeArea(child:Padding(padding:constEdgeInsets.all(20.0),child:Column(mainAxisAlignment:MainAxisAlignment.center,children:[Form(// Attach form key for validations. I won't be adding validations.// key: loginVm.formKey,child:Column(children:[Text("Auth App",style:Theme.of(context).textTheme.displayMedium),constSizedBox(height:30),TextFormField(onChanged:loginVm.onChangedEmail,keyboardType:TextInputType.emailAddress,decoration:constInputDecoration(hintText:"Email"),),constDivider(height:2),TextFormField(obscureText:true,onChanged:loginVm.onChangedPassword,decoration:constInputDecoration(hintText:"Password"),),constSizedBox(height:20),TextButton(onPressed:loginVm.onLogin,child:loginVm.isLoading?constCircularProgressIndicator():constText("Login"),),],),),],),),),);},);}}
The final file in our tutorial and we are all done 🎉:
// core/view_models/login.vm.dartimport'package:auth_app/locator.dart';import'package:auth_app/core/view_models/base.vm.dart';import'package:auth_app/core/services/auth.service.dart';import'package:auth_app/graphql/__generated__/your_app.schema.graphql.dart';classLoginViewModelextendsBaseViewModel{String?_email;String?_password;// Used for validation or any other purpose like clearing form and more...// final formKey = GlobalKey<FormState>();final_authService=locator<AuthService>();voidonChangedPassword(Stringvalue)=>_password=value;voidonChangedEmail(Stringvalue)=>_email=value;Future<void>onLogin()async{// Validate login details using [formKey]// if (!formKey.currentState!.validate()) return;try{setIsLoading(true);finalinput=Input$LoginInput(identifier:_email!,password:_password!);await_authService.login(input);displaySnackBar("Successfully logged in!");}catch(error){displaySnackBar(error.toString());}finally{setIsLoading(false);}}}
And always use Provider to access auth from the AuthService, this will make sure that your UI gets updated when you call notifyListeners() in AuthService.
// Always access auth using Provider.ofWidgetbuild(BuildContextcontext){finalauth=Provider.of<AuthService>(context).auth;// Set listen to false if you don't want to re-render the widget.//// final auth = Provider.of<AuthService>(context, listen: false).auth;// DO NOT DO THIS!// If you do this then your UI won't be updated,// when you call notifyListeners() in AuthService.//// final auth = locator<AuthService>().auth;returnScaffold(...)}
I hope this gives you a complete idea about working with GraphQL in Flutter. If you have any questions feel free to comment.