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
  • Request body contains all queries defined in the same file

    Request body contains all queries defined in the same file

    Platform: web ferry version: 0.11.2+1

    Schema:

    type Query {
      foo:Foo
      bar:Bar
    }
    
    type Foo{
      foo:String!
    }
    
    type Bar{
      bar:String!
    }
    

    I created a single file which contains multiple query definition.

    query fooQuery{
        foo{foo}
    }
    
    query barQuery{
        bar{bar}
    }
    

    From this schema, GfooQueryReq and GbarQueryReq was generated.

    Next, in my application, I tried to request using GfooQueryReq.

    final result = await Client(link: HttpLink("http://example.com/graphql")).request(GfooQueryReq()).first;
    

    Then, the request body contains not only fooQuery but also barQuery.

    query fooQuery {\n  __typename\n  foo {\n    __typename\n    foo\n  }\n}\n\nquery barQuery {\n  __typename\n  bar {\n    __typename\n    bar\n  }\n}
    

    If I understand correctly, the request body should contain only fooQuery. Is this expected behavior?

    opened by ikenox 1
  • How to use GlobalID and GGlobalIDBuilder

    How to use GlobalID and GGlobalIDBuilder

    How to set input GlobalID in mutation?

    graphql mutation:

    mutation Favorite ($id: GlobalID!) {
        favorite (input: {id: $id}) {
            success
        }
    }
    

    flutter (dart):

    final favoriteReq = GFavoriteReq(
            (b) => b..vars.id = ??,
    );
    
    opened by sahmadreza 1
  • Last lists of data causing everything to freeze

    Last lists of data causing everything to freeze

    I'm in the process of building a data dashboard for IoT data from our GraphQL endpoint and have been running into several limitations . The most noticeable is that when i query a very large dataset (17,000 rows) then the query causes the whole app to freeze and force me to close the emulator.

    Normally i'm not querying all the data, but when something goes wrong and this happens i would expect there to be some error handeling or exemption thrown rather than causing everything to lock up. I don't know the limitations of dart when it comes to list sizes, but JS handles the same query without any trouble so i think the issue might be a bottleneck in Ferry somewhere 🤷

    My current solution is to limit the query to less than a 1000 rows, but this could be an issue for larger data visualizations.

    opened by lloydrichards 1
  • Question: How to make a fragment with a root union

    Question: How to make a fragment with a root union

    I have a union type Food = Beverage | Fruit | Vegetable

    fragment FoodFrag on Food {
        ... on Beverage {
            ...FoodCommon
            flavour
        }
        ... on Vegetable {
            ...FoodCommon
            vitamins
        }
        ... on Fruit {
            ...FoodCommon
            sweetness
        }
    }
    

    When I try use FoodFrag in other queries/mutations like this...

    # import '../fragments/food_fragments.graphql'
    
    mutation CreateFood ($food_in: FoodInput!){
        createFood(food_in: $food_in) {
            ...FoodFrag
        }
    }
    

    ...I get a stack overflow error from the build runner.

    Am I doing something wrong? Is this a bug with ferry ? Is this a limitation of graphql? Workaround ideas?

    opened by daverin 1
  • fix(ferry)!: remove optimistic response when listener cancels stream

    fix(ferry)!: remove optimistic response when listener cancels stream

    fixes #396

    It might be considered a breaking change though, as some users might rely on the old behavior.

    I would consider it a bug though as the optimistic patch would never be removed and would overwrite the actual cache data.

    opened by knaeckeKami 0
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 283 Nov 28, 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 Nov 23, 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 Oct 20, 2022
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 41 Nov 26, 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. 3k Nov 25, 2022
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 Dec 1, 2022
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 6 Nov 5, 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 May 31, 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