Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Around `graphql_flutter` are builds awesome tools like:
✅   [Optimistic results](./packages/graphql_flutter/README.md#optimism)
✅   [Modularity](./packages/graphql/README.md#links)
✅   [Client-state management](./packages/graphql/README.md#direct-cache-access-api)
✅   [Operation cancellation](./packages/graphql/README.md#cancellation)
⚠️   [Automatic Persisted Queries](./packages/graphql/README.md#persistedquerieslink-experimental-warning-out-of-service-warning) (out of service)

## Contributing
Expand Down
32 changes: 32 additions & 0 deletions examples/starwars/ios/Flutter/ephemeral/flutter_lldb_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#
# Generated file, do not edit.
#

import lldb

def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict):
"""Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages."""
base = frame.register["x0"].GetValueAsAddress()
page_len = frame.register["x1"].GetValueAsUnsigned()

# Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the
# first page to see if handled it correctly. This makes diagnosing
# misconfiguration (e.g. missing breakpoint) easier.
data = bytearray(page_len)
data[0:8] = b'IHELPED!'

error = lldb.SBError()
frame.GetThread().GetProcess().WriteMemory(base, data, error)
if not error.Success():
print(f'Failed to write into {base}[+{page_len}]', error)
return

def __lldb_init_module(debugger: lldb.SBDebugger, _):
target = debugger.GetDummyTarget()
# Caveat: must use BreakpointCreateByRegEx here and not
# BreakpointCreateByName. For some reasons callback function does not
# get carried over from dummy target for the later.
bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$")
bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__))
bp.SetAutoContinue(True)
print("-- LLDB integration loaded --")
5 changes: 5 additions & 0 deletions examples/starwars/ios/Flutter/ephemeral/flutter_lldbinit
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#
# Generated file, do not edit.
#

command script import --relative-to-command-file flutter_lldb_helper.py
255 changes: 255 additions & 0 deletions packages/graphql/CANCELLATION_USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
# Cancellation Support

The graphql package now supports cancellation of `query` and `mutate`
operations. This allows you to cancel in-flight operations when they're no
longer needed (e.g., when a user navigates away or cancels an action).

## Basic Usage

### Option 1: Using CancellationToken directly

```dart
import 'package:graphql/client.dart';

// Create a cancellation token
final cancellationToken = CancellationToken();

// Execute a query with the cancellation token
final resultFuture = client.query(
QueryOptions(
document: gql(r'''
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
'''),
variables: {'id': '123'},
cancellationToken: cancellationToken,
),
);

// Later, if you need to cancel the operation:
cancellationToken.cancel();

// The resultFuture will complete with a QueryResult containing
// a CancelledException
try {
final result = await resultFuture;
print('Result: ${result.data}');
} catch (e) {
if (e is OperationException &&
e.linkException is CancelledException) {
print('Operation was cancelled');
}
}

// Don't forget to dispose the token when done
cancellationToken.dispose();
```

### Option 2: Using the convenience methods

The package provides `queryCancellable` and `mutateCancellable` convenience
methods that automatically create a `CancellationToken` for you:

```dart
import 'package:graphql/client.dart';

// Execute a cancellable query
final operation = client.queryCancellable(
QueryOptions(
document: gql(r'''
query GetPosts {
posts {
id
title
content
}
}
'''),
),
);

// Access the result future
final resultFuture = operation.result;

// Cancel the operation if needed
operation.cancel();

// Handle the result
try {
final result = await resultFuture;
print('Posts: ${result.data}');
} catch (e) {
if (e is OperationException &&
e.linkException is CancelledException) {
print('Query was cancelled');
}
}
```

## Mutation Example

```dart
import 'package:graphql/client.dart';

// Execute a cancellable mutation
final operation = client.mutateCancellable(
MutationOptions(
document: gql(r'''
mutation CreatePost($title: String!, $content: String!) {
createPost(title: $title, content: $content) {
id
title
content
}
}
'''),
variables: {
'title': 'New Post',
'content': 'This is the content',
},
),
);

// Cancel if user navigates away
// operation.cancel();

try {
final result = await operation.result;
print('Created post: ${result.data}');
} catch (e) {
if (e is OperationException &&
e.linkException is CancelledException) {
print('Mutation was cancelled');
}
}
```

## Use Cases

### 1. User Navigation

Cancel pending requests when a user navigates away from a page:

```dart
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
CancellableOperation<QueryResult>? _operation;

@override
void initState() {
super.initState();
_operation = client.queryCancellable(
QueryOptions(document: gql('query { ... }')),
);
}

@override
void dispose() {
// Cancel the operation when the widget is disposed
_operation?.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _operation?.result,
builder: (context, snapshot) {
// Build your UI
},
);
}
}
```

### 2. Search Debouncing

Cancel previous search requests when a new one is initiated:

```dart
CancellableOperation<QueryResult>? _searchOperation;

void search(String query) {
// Cancel the previous search
_searchOperation?.cancel();

// Start a new search
_searchOperation = client.queryCancellable(
QueryOptions(
document: gql(r'''
query Search($query: String!) {
search(query: $query) {
id
name
}
}
'''),
variables: {'query': query},
),
);

_searchOperation!.result.then((result) {
// Handle search results
}).catchError((e) {
if (e is OperationException &&
e.linkException is CancelledException) {
// Search was cancelled, ignore
return;
}
// Handle other errors
});
}
```

### 3. Timeout with Custom Message

Combine with timeouts for better control:

```dart
final cancellationToken = CancellationToken();

// Set a custom timeout
Timer(Duration(seconds: 10), () {
cancellationToken.cancel();
});

final result = await client.query(
QueryOptions(
document: gql('query { ... }'),
cancellationToken: cancellationToken,
),
);
```

## Important Notes

1. **Disposing CancellationTokens**: When using `CancellationToken` directly,
remember to call `dispose()` when you're done with it to clean up resources.

2. **Convenience Methods**: The `queryCancellable` and `mutateCancellable`
methods automatically create and manage `CancellationToken` instances for
you.

3. **Error Handling**: Cancelled operations will complete with an
`OperationException` containing a `CancelledException` as the
`linkException`.

4. **Network Cleanup**: Cancelling an operation will abort the underlying HTTP
request. The `HttpLink` provided by this package supports true network
cancellation on both IO (using `dart:io` `HttpClient.abort()`) and Web
platforms (using `XMLHttpRequest.abort()`). In Chrome DevTools, you will see
cancelled requests show as "(canceled)" in the Network tab. Note: If you
provide a custom `http.Client` to `HttpLink`, cancellation may not work
unless your client supports it.

5. **Cache Updates**: Cancelled operations will not update the cache with any
results they may have received before cancellation.
Loading