Stream-based strongly typed GraphQL client for Dart

Overview

A simple, powerful GraphQL Client for Flutter and Dart

MIT License PRs Welcome Watch on GitHub Star on GitHub Watch on GitHub Discord

Documentation 📖

Features

  1. Fully Typed: work faster and safer with compile time checks and IDE autocomplete, including fully typed Cache reads and writes.
  2. 🔄 Built-In Code Generators: automatically generated immutable data classes for all your GraphQL Operations and Fragments, based on your schema.
  3. 🌐 Customizable Network Interface: highly customizable network interface using gql_link, allowing you to compose and extend Links.
  4. Normalized Optimistic Cache: keep data in sync with cache normalization and update your UI instantly with optimistic data.
  5. 💾 Multiple Data Stores: extensible Store interface with built-in MemoryStore and HiveStore (which uses hive for offline persistence).
  6. 📄 Refetch & Pagination: easily update responses with new data or combine multiple responses, allowing for seamless pagination.
  7. 📱 Flutter Widgets: Widgets for Queries, Mutations, and Subscriptions, available out of the box.

Packages

This repo is a monorepo for ferry and related packages.

Pub Package Description
version package:ferry Stream-based GraphQL Client
version package:ferry_cache Normalized, strongly typed, optimistic cache
version package:ferry_exec Strongly typed GraphQL execution interface
version package:ferry_flutter Flutter Widgets for GraphQL Operations
version package:ferry_generator Dart Type Generator for GraphQL Operations
version package:ferry_store Data Persistence for ferry's cache
version package:ferry_hive_store Hive implementation of ferry_store
version package:normalize Normalization / denormalization of GraphQL data
Comments
  • How to send Auth Token Header (JWT)

    How to send Auth Token Header (JWT)

    Looking through Ferrys documentation, I haven't been able to find how to send JWT header token to the client for GraphQL requests that need authentication.

    https://ferrygraphql.com/docs/queries

    I have a data layer for my GraphQL queries which is then used in my view models to read/write data.

    Any help or would be appreciated.

    opened by arpitjacob 23
  • Feedback

    Feedback

    @klavs, @micimize, @comigor, I just published this package and thought you guys might be interested in checking it out.

    I'd love any thoughts / feedback you may have.

    Check out the readme and example.

    opened by smkhalsa 23
  • NoSuchMethodError: invalid member on null: 'loading'

    NoSuchMethodError: invalid member on null: 'loading'

    Hi,

    Been using the library few a weeks now but hit a problem and not sure how to debug it.

    Using the Operation widget and using a similar setup to the example I have started getting the response NoSuchMethodError: invalid member on null: 'loading' but can't find a way to debug it.

    My code looks something similar to this:

    final GMyReq myRequest = GMyReq((b) => b
          ..fetchPolicy = FetchPolicy.NetworkOnly
          ..vars.uuid = widget.uuid);
    
    return Operation(
      operationRequest: festivalRequest,
      client: _client,
      builder: (BuildContext context, OperationResponse<GMyData, GMyVars> response) {
        print('RESPONSE');
        print(response);
        print('RESPONSE END');
    
        if (response.loading)
          return Center(child: CircularProgressIndicator());
    
        // ...
      }
    );
    );
    

    But the print command outputs the following:

    RESPONSE
    Instance of 'OperationResponse<GMyData, GMyVars>'
    RESPONSE END
    RESPONSE
    null
    RESPONSE END
    

    Is there a way for me to find out why null is being returned? I've added gql_error_link and a ErrorLink to my client but it doesn't seem to be getting triggered.

    Thanks, Gary

    opened by garyrutland 22
  • Offline mutation link usage

    Offline mutation link usage

    I've recently been trialing the offline mutation link included with ferry (I plan on extending it within my project as it has a few limitations that will be an issue for my project, I can potentially make PRs to include any changes that might be useful here). Anyway I've hit on an issue actually getting the requests to be made when the app comes back online.

    Currently when it goes offline I can see that the request is serialized and stored in the box, however when it comes back online, it seems to add the request to the requestController but it doesn't seem to be made.

    It's very possible that I've misunderstood something about how this should work. My understanding was that when a request is added to the request controller it would then forward it on to the remaining links in the chain, except after calling requestController.add in the offlineLink it doesn't seem to get forwarded back the offlineLink.

    i.e. flow in my head is

    requestLink -> offlineLink -> ... FetchPolicyETC
    // comes back online
    offlineLinkConnectHandler -> requestLink -> offlineLink.request
    

    This doesn't seem to be happening though as I've logged within every other part of the offline link and it doesn't seem to get triggered to execute the restored request

    My setup looks like

    
        inbox = await Hive.openBox('mutation_queue');
    
        final requestController = StreamController<OperationRequest>.broadcast();
    
        _offlineLink = CopyOfOfflineTypedLink(
          cache: _cache,
          serializers: serializers,
          requestController: requestController,
          mutationQueueBox: inbox,
        );
    
        _client = TypedLink.from([
          // Request link. Must be at the start
          RequestControllerTypedLink(requestController),
          _offlineLink,
          const AddTypenameTypedLink(),
          const OptimisticTypedLink(),
          // Terminating link. Must be at the end
          FetchPolicyTypedLink(link: link, cache: _cache)
        ]);
    
    // In request function
        final req = GcheckInReq(
          (b) => b
            ..vars.id = id
            ..vars.staffId = staffId
            ..vars.geolocation.latitude = currentLocation.latitude
            ..vars.geolocation.longitude = currentLocation.longitude
            ..vars.actionDateTime = DateTime.now(),
        );
    
        _client.request(req).listen((rsp) {
          print('rsp: $rsp');
        });
    
    

    One thing I noticed which could be a red herring is that the RequestControllerTypedLink filters based on the types in the operation request so maybe since the restored request doesn't have matching types it doesn't find it?

        return requestController.stream
            .whereType<OperationRequest<TData, TVars>>()
            .where(
              (req) => req.requestId == null
                  ? req == request
                  : req.requestId == request.requestId,
            )
    

    Any help figuring this out would be much appreciated 🙏🏿

    opened by akinsho 19
  • Ferry Incompatible with Flutter 2.8.1

    Ferry Incompatible with Flutter 2.8.1

    Due to the minimum requirement of analyzer 3.0.0 from flutter v2.8.1, ferry_generator uses analyzer v1.7.2 and needs to be upgraded to v3.0.0.

    Although the build runs with a warning currently. The build fails with errors related to gql_code_builder.

    opened by dopecoder 18
  • Cache including typename in the dataId always

    Cache including typename in the dataId always

    I have run into some issues when attempting to use readFragment on a fragment defined on an interface where it is unable to locate the data in the cache because the cache creates ids that are $typename:$id and in readFragment (https://github.com/gql-dart/ferry/blob/master/normalize/lib/src/denormalize_fragment.dart#L68) the typename comes from the fragment type, which works great for object spreads but not for interface spreads.

    My initial thought would be to allow/encourage a cache mode where the ids are globally unique, as that is the pattern I have seen elsewhere.

    What do y'all think? Would a PR that creates such a mode be accepted?

    opened by tberman 18
  • Offline mutation support

    Offline mutation support

    Description

    With optimistic cache make the mutation without the internet connection. After that turn on the connection.

    Error code

    [VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: 
    #0      IOClient.send (package:http/src/io_client.dart:65:7)
    <asynchronous suspension>
    #1      BaseClient._sendUnstreamed (package:http/src/base_client.dart:176:38)
    #2      BaseClient.post (package:http/src/base_client.dart:58:7)
    #3      HttpLink.request (package:gql_http_link/src/link.dart:99:44)
    <asynchronous suspension>
    #4      Client._networkResponseStream (package:ferry/src/client/client.dart:146:12)
    #5      Client._optimisticNetworkResponseStream (package:ferry/src/client/client.dart:128:13)
    #6      Client._responseStream (package:ferry/src/client/client.dart:88:16)
    #7      Client.responseStream.<anonymous closure> (package:ferry/src/client/client.dart:49:29)
    #8      SwitchMapStreamTransformer._buildTransformer.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:rxdart/src/transformers/switch_map.dart:55:47)
    #9      _rootRunUnary (dart:async/zone.dart:1134:38)
    #10     _CustomZone.runUnary (dart:async/zone.dart:1031:19)
    #11<…>
    

    Screenshots

    It took 60 seconds to get the issue, sorry for the long animated gif.

    ezgif com-video-to-gif (1)

    opened by awaik 18
  • Build failing: misconfigured builder definition

    Build failing: misconfigured builder definition

    I am running flutter pub run build_runner build --delete-conflicting-outputs

    [INFO] Generating build script... [INFO] Generating build script completed, took 400ms

    [INFO] Precompiling build script...... [WARNING] /C:/src/flutter/.pub-cache/hosted/pub.dartlang.org/gql_code_builder-0.2.0/lib/src/ast.dart:618:12: Erro r: A non-null value must be returned since the return type 'Expression' doesn't allow null.

    • 'Expression' is from 'package:code_builder/src/specs/expression.dart' ('/C:/src/flutter/.pub-cache/hosted/pub. dartlang.org/code_builder-4.1.0/lib/src/specs/expression.dart'). Expression _directiveLocation(DirectiveLocation location) { ^ [INFO] Precompiling build script... completed, took 6.2s

    [SEVERE] Failed to precompile build script .dart_tool/build/entrypoint/build.dart. This is likely caused by a misconfigured builder definition.

    pub finished with exit code 78

    opened by MubeenNaeem 15
  • Getting null data for specific query

    Getting null data for specific query

    Hi, I am getting null response below query with ferry. I can get correct data within playground but ferry. I have tried different approaches but without an error it is hard to catch what is going on. If anyone has an idea please leave a comment.

    Query

    
    query StudentNotification($id: Id!) {
        student(studentId: $id) {
            notifications {
                id
                userId
                data
                status
                createdAt
            }
        }
    }
    
    

    Dart code that returns null data

    Future<List<GStudentNotificationData_student_notifications>>
          getStudentNotifications({required String userId}) async {
        late final OperationResponse response;
        try {
          final studentNotificationsReq =
              GStudentNotificationReq((q) => q..vars.id.value = userId);
    
          response = await graphQlClient.request(studentNotificationsReq).first;
          print('THIS IS NOTIFICATION RESPONSE: $response');
    
          return [];
          // return response.data.student.notifications.toList()!;
        } catch (e) {
          print(response.graphqlErrors);
          throw Exception(e.toString());
        }
      }
    
    

    Debug console

    image

    opened by dokumanx 15
  • fix(cache): Fix memory leak.

    fix(cache): Fix memory leak.

    The issue was caused by this line: https://github.com/gql-dart/ferry/blob/201980f313a98aa94ef45146ff229f24a95c3125/packages/ferry_cache/lib/src/cache.dart#L105

    As per the docs, isEmpty waits for the first element of this stream. The problem is that operationDataChangeStream and fragmentDataChangeStream end with .skip(1) so the stream never emits and the Future is pending forever.

    My fix seems extremely simple, so maybe I missed some complexity but all the tests are passing... Also, I don't know how to write an additional test to make sure that we don't leak.

    If you want to manual test, I've created a branch where I added the original memory-leak repo: https://github.com/GP4cK/ferry/tree/memory-leak-example (it overrides the ferry dependencies to make sure to use my version of the code).

    Closes #343

    opened by GP4cK 14
  • On windows, build_runner fails

    On windows, build_runner fails

    When I run

    pub run build_runner build

    it fails with

    [SEVERE] built_value_generator:built_value on lib/data/graphql/__generated__/all_games.req.gql.dart: Error in BuiltValueGenerator for abstract class GQueryReq implements Built<GQueryReq, dynamic>, OperationRequest<dynamic, dynamic>. Please make the following changes to use BuiltValue:

    The failure is caused by using '' instead of '/' in the import statement, like this:

    import 'package:portal\data\graphql\__generated__\all_games.ast.gql.dart'

    and it works well on Mac without any problem.

    opened by simpai 14
  • Support for @include and @skip directives

    Support for @include and @skip directives

    Streaming there an issue found inside graphql-flutter https://github.com/zino-hofmann/graphql-flutter/issues/1246

    I do not know if it is duplicate or already supported

    opened by vincenzopalazzo 2
  • Need explanation on Optimistic Updates

    Need explanation on Optimistic Updates

    If I use optimistic updates with CacheAndNetwork policy, how will it behave? It firstly shows optimistic value, then tries to grab the data from cache (if exists), then tries to fetch the data from network? So the optimistic value is kind of "default data", in case when we don't have cache?

    opened by subzero911 9
  • Whole response becomes NULL if a field in response have null value ?

    Whole response becomes NULL if a field in response have null value ?

    For some specific queries, I was getting NULL response

    So I did some digging, finally found that, in the response if a field is null, then the entire response becomes null

    For example ,

    In this response,

    {
        "data": {
            "cable_opr_by_pk": {
                "address": "124",
                "cable_name": "erty",
                "email": "[email protected]",
                "password": "qwer123",
                "phone_no": "9876543210",
                "user_id": 11,
                "isActive": false,
                "paid_month": null
            }
        }
    

    The paid_month is null. So what happens is, the entire response becomes null, that is

    final resp = await client.request(reqCust).firstWhere((response) => response.dataSource != DataSource.Optimistic);

    In the above execution, resp.data?.cable_opr_by_pk in NULL .

    But if I added some value for paid_month in the DB and retried, then I able to get result since paid_month is not NULL.

    Why this is happening ? Please help me

    opened by RageshAntony 1
  • Upgrade all dependencies to the latest.

    Upgrade all dependencies to the latest.

    Hi @knaeckeKami,

    Do you have plan to upgrade all the library used in the ferry to the latest. Currently I used ferry and some of the libraries are not compatible to the latest one. For example auto_route

    Thanks you, Minh

    opened by minhnguyenandpad 1
  • Client.requestController.add(request) is not sending any network request

    Client.requestController.add(request) is not sending any network request

    I am trying to update some cached data by re-querying via client.requestController.add(request). However, this isn't sending any request when I check the network logs.

    Below is the code I used to re-create the issue in the pokemon example:

    import 'package:flutter/material.dart';
    import 'package:ferry/ferry.dart';
    import 'package:gql_http_link/gql_http_link.dart';
    
    import 'src/graphql/__generated__/all_pokemon.req.gql.dart';
    
    void main() async {
      final client = Client(
        link: HttpLink("https://pokeapi.dev"),
        cache: Cache(),
      );
    
      final request = GAllPokemonReq(
        (b) => b
          ..vars.limit = 50
          ..vars.offset = 0,
      );
    
      runApp(
        MaterialApp(
          title: 'Demo',
          onGenerateRoute: (_) => MaterialPageRoute(
            builder: (_) => OutlinedButton(
              onPressed: () {
                client.requestController.add(request); // This doesn't send any request.
                client.request(request); // This doesn't send any request too.
              },
              child: const Text('Send Request'),
            ),
          ),
        ),
      );
    }
    

    or just run the pokemon example code here: https://github.com/aaronbau/ferry-request-controller-issue

    Let me know if you need more info. Thanks!

    opened by aaronbau 4
  • Incorrect parser response

    Incorrect parser response

    I was have error on request for data structure.

    [log] DEBUG 2022-12-04 00:59:46.539828:: GraphQL API Request QUERY AddCartItem [log] DEBUG 2022-12-04 00:59:46.541269:: GraphQL API Request HEADERS {user-id: 914c20e2-1554-4458-869a-52761fd7f81f} [log] DEBUG 2022-12-04 00:59:46.542483:: GraphQL API Request VARIABLES GAddCartItemVars { cartId=07faeffb-833e-4cf9-be9b-cdc7e01578c5, data=GCartItemData { cartItemConfigurationDataList=[GCartItemConfigurationData { comment=, optionalModificatorIdList=[], qty=1, requiredModificatorList=[], }], productId=c5a59d1b-0341-4b75-9cee-00a57eee981b, }, } [log] ERROR 2022-12-04 00:59:46.566488:: LinkException(type '_$GCartItemData' is not a subtype of type 'GCartItemData' of 'object', #0 _$GCartItemDataSerializer.serialize (package:holla_client/core/data/remote/fragment/generated/cart_item.data.gql.g.dart) #1 BuiltJsonSerializers._serialize (package:built_value/src/built_json_serializers.dart:103:18) #2 BuiltJsonSerializers.serialize (package:built_value/src/built_json_serializers.dart:69:18) #3 _$GAddCartItemVarsSerializer.serialize (package:holla_client/core/data/remote/mutation/generated/cart.var.gql.g.dart:33:19) #4 BuiltJsonSerializers._serialize (package:built_value/src/built_json_serializers.dart:103:18) #5 BuiltJsonSerializers.serialize (package:built_value/src/built_json_serializers.dart:69:18) #6 BuiltJsonSerializers.serializeWith (package:built_value/src/built_json_serializers.dart:53:12) #7 GAddCartItemVars.toJson (package:holla_client/core/data/remote/mutation/generated/cart.var.gql.dart:25:53) #8 GAddCartItemReq.execRequest (package:holla_client/core/data/remote/mutation/generated/cart.req.gql.dart:42:25)

    opened by vadimlukutin 1
Owner
GQL Dart
GraphQL tools for Dart
GQL Dart
:fire:GeoFlutterFire:fire: is an open-source library that allows you to store and query firestore documents based on their geographic location.

GeoFlutterFire ?? GeoFlutterFire is an open-source library that allows you to store and query a set of keys based on their geographic location. At its

Darshan N 282 Dec 11, 2022
AllSQL is an open-source compact browser based SQL Compiler, built using Flutter.

AllSQL - An open-source compact browser based SQL Compiler AllSQL is an open-source compact browser based SQL Compiler, built using Flutter. Please cr

Adit Luhadia 22 Nov 20, 2022
Moor is an easy to use, reactive, typesafe persistence library for Dart & Flutter

Moor is an easy to use, reactive, typesafe persistence library for Dart & Flutter

Simon Binder 1.8k Dec 30, 2022
Dart package for accessing databases.

Introduction This is database.dart, a vendor-agnostic database access API for Flutter and other Dart projects. This version is just an early preview.

Dint 63 Jan 2, 2023
Generate dart type definitions from PostgreSQL database schema

schema-dart Generate dart type definitions from PostgreSQL database schema (WIP) Installation: Until the package is published to pub.dev, it can be in

null 8 Oct 31, 2022
Parser tool is a real-time compiler and runtime engine for strongly typed PEG parsers

parser_tool Version 0.1.1 (BETA) Parser tool is a real-time compiler and runtime engine for strongly typed PEG parsers. Parser tool contains libraries

null 6 Jun 28, 2021
Lightning fast, strongly typed network protocol

What is Bolt Bolt is a network protocol written in Dart to send and receive strongly typed data objects. It is designed to be easy to use and to be as

Jochum van der Ploeg 42 Dec 3, 2022
A GraphQL client for Flutter, bringing all the features from a modern GraphQL client to one easy to use package.

GraphQL Flutter ?? Bulletin See the v3 -> v4 Migration Guide if you're still on v3. Maintenance status: Low. Follow #762 for updates on the planned ar

Zino App B.V. 3.1k Jan 5, 2023
A GraphQL client for Flutter, bringing all the features from a modern GraphQL client to one easy to use package. Built after react apollo

Flutter GraphQL Table of Contents Flutter GraphQL Table of Contents About this project Installation Usage GraphQL Provider [Graphql Link and Headers]

Snowball Digital 45 Nov 9, 2022
A GraphQL client for Flutter, bringing all the features from a modern GraphQL client to one easy to use package.

GraphQL Flutter ?? Bulletin See the v3 -> v4 Migration Guide if you're still on v3. Maintenance status: Low. Follow #762 for updates on the planned ar

Zino & Co. 3.1k Jan 5, 2023
An rx stream builder widget that is able to pre-populate a flutter StreamBuilder with data from an rx stream if the stream is either a value or a replay observable.

An rx stream builder widget that is able to pre-populate a flutter StreamBuilder with data from an rx stream if the stream is either a value or a replay observable. For example the RX stream is a BehaviorSubject or a ReplaySubject.

Jon Samwell 8 Jan 22, 2022
Lite-graphql - A light way implementation of GraphQL client in dart language

lite GraphQL client A light way implementation of GraphQL client in dart languag

Vincenzo Palazzo 3 Mar 17, 2022
A demo Project Showcasing HowTo use GraphQL to conect as a client to a GraphQL service.

graphql_demoapp A Flutter App to demonstrate how to work with GraphQL Server, connecting through our app as a client. Working with basic queries and m

Igor L Sambo 2 Nov 7, 2021
SurrealDB client written in pure dart. auto reconnect, typed functions

SurrealDB Client For Dart & Flutter SurrealDB client for Dart and Flutter. Quick Start import 'package:surrealdb/surrealdb.dart'; void main(List<Stri

Duhan BALCI 10 Dec 18, 2022
Stream sticker animation - Stream Sticker Animation using Rive

Stream Sticker Animation using Rive Sample Flutter project to demonstrate how to

Souvik Biswas 4 Feb 8, 2022
Flutter-based mobile app displaying a list of daily curated content from top engineering blogs and articles. Backed by a GraphQL-based API written in Kotlin..

Flutter-based mobile app displaying a list of daily curated content from top engineering blogs and articles. Backed by a GraphQL-based API written in Kotlin..

Armel Soro 20 Dec 14, 2022
Widget to let the user search through a keyword string typed on a customizable keyboard in a single or multiple choices list presented as a dropdown in a dialog box or a menu.

searchable_dropdown Widget to let the user search through a keyword string typed on a customizable keyboard in a single or multiple choices list prese

Bobby Stenly Irawan 108 Sep 11, 2022
Shared preferences typed - A type-safe wrapper around shared preferences, inspired by ts-localstorage

Typed Shared Preferences A type-safe wrapper around shared_preferences, inspired

Philipp Bauer 0 Jan 31, 2022
Sol - an imperative, statically-typed language designed as a tool for learning how to program

SOL: Simple Obvious Language Sol is an imperative, statically-typed language designed as a tool for learning how to program. It is simple, in that it

Christopher Fujino 3 Oct 6, 2022
A dynamic, Stream-based API for job scheduling in Dart, capable of processing on different triggers.

Pendulum A library for task-scheduling in Dart. Exports a Task class that returns Streams, with a number of versatile options to customize how Tasks a

Aditya Kishore 1 Aug 22, 2020