Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## 20.3.3

* Fix boolean parameter not handled correctly in Client requests

## 20.3.2

* Fix OAuth2 browser infinite redirect issue
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2025 Appwrite (https://appwrite.io) and individual contributors.
Copyright (c) 2026 Appwrite (https://appwrite.io) and individual contributors.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Expand Down
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@
[![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
[![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord)

**This SDK is compatible with Appwrite server version 1.8.x. For older versions, please check [previous releases](https://github.com/appwrite/sdk-for-flutter/releases).**

Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the Flutter SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)

**This SDK is compatible with Appwrite server version latest. For older versions, please check [previous releases](https://github.com/appwrite/sdk-for-flutter/releases).**

Appwrite is an open-source backend as a service server that abstracts and simplifies complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the Flutter SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)

![Appwrite](https://github.com/appwrite/appwrite/raw/main/public/images/github.png)

Expand All @@ -21,7 +19,7 @@ Add this to your package's `pubspec.yaml` file:

```yml
dependencies:
appwrite: ^20.3.2
appwrite: ^20.3.3
```
You can install packages from the command line:
Expand Down
4 changes: 3 additions & 1 deletion docs/examples/account/create-jwt.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ Client client = Client()

Account account = Account(client);

Jwt result = await account.createJWT();
Jwt result = await account.createJWT(
duration: 0, // optional
);
4 changes: 2 additions & 2 deletions docs/examples/avatars/get-screenshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Uint8List bytes = await avatars.getScreenshot(
width: 800, // optional
height: 600, // optional
quality: 85, // optional
output: Output.jpg, // optional
output: ImageFormat.jpg, // optional
)

final file = File('path_to_file/filename.ext');
Expand Down Expand Up @@ -61,7 +61,7 @@ FutureBuilder(
width:800 , // optional
height:600 , // optional
quality:85 , // optional
output: Output.jpg, // optional
output: ImageFormat.jpg, // optional
), // Works for both public file and private file, for private files you need to be logged in
builder: (context, snapshot) {
return snapshot.hasData && snapshot.data != null
Expand Down
8 changes: 7 additions & 1 deletion docs/examples/databases/update-document.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ Document result = await databases.updateDocument(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>',
data: {}, // optional
data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>', // optional
);
8 changes: 7 additions & 1 deletion docs/examples/databases/upsert-document.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ Document result = await databases.upsertDocument(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>',
data: {},
data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 30,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>', // optional
);
8 changes: 7 additions & 1 deletion docs/examples/tablesdb/update-row.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ Row result = await tablesDB.updateRow(
databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>',
rowId: '<ROW_ID>',
data: {}, // optional
data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>', // optional
);
8 changes: 7 additions & 1 deletion docs/examples/tablesdb/upsert-row.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ Row result = await tablesDB.upsertRow(
databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>',
rowId: '<ROW_ID>',
data: {}, // optional
data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>', // optional
);
3 changes: 1 addition & 2 deletions lib/enums.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ part 'src/enums/credit_card.dart';
part 'src/enums/flag.dart';
part 'src/enums/theme.dart';
part 'src/enums/timezone.dart';
part 'src/enums/output.dart';
part 'src/enums/image_format.dart';
part 'src/enums/execution_method.dart';
part 'src/enums/image_gravity.dart';
part 'src/enums/image_format.dart';
part 'src/enums/execution_trigger.dart';
part 'src/enums/execution_status.dart';
6 changes: 4 additions & 2 deletions lib/services/account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,12 @@ class Account extends Service {
/// Appwrite server-side API and SDKs. The JWT secret is valid for 15 minutes
/// from its creation and will be invalid if the user will logout in that time
/// frame.
Future<models.Jwt> createJWT() async {
Future<models.Jwt> createJWT({int? duration}) async {
const String apiPath = '/account/jwts';

final Map<String, dynamic> apiParams = {};
final Map<String, dynamic> apiParams = {
if (duration != null) 'duration': duration,
};
Comment on lines +120 to +125
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, check the createJWT method implementation
cat -n lib/services/account.dart | sed -n '110,135p'

Repository: appwrite/sdk-for-flutter

Length of output: 1130


🏁 Script executed:

# Check docstring pattern for other methods in this file
rg -A 10 "Future<" lib/services/account.dart | head -50

Repository: appwrite/sdk-for-flutter

Length of output: 1492


🏁 Script executed:

# Search for duration parameter documentation
rg -n "duration" lib/services/account.dart

Repository: appwrite/sdk-for-flutter

Length of output: 181


🏁 Script executed:

# Search for any documentation about duration parameter
rg -n "duration" --type md --type txt docs/ 2>/dev/null | head -20

Repository: appwrite/sdk-for-flutter

Length of output: 135


🏁 Script executed:

# Check if there's an API specification or reference
find . -name "*.md" -o -name "*.txt" | xargs rg -l "createJWT\|duration" 2>/dev/null | head -10

Repository: appwrite/sdk-for-flutter

Length of output: 50


🏁 Script executed:

# Check for other similar parameters and how they're documented in docstrings
rg -B 5 "Future<models\." lib/services/account.dart | grep -A 5 "///"

Repository: appwrite/sdk-for-flutter

Length of output: 15603


Add parameter documentation for duration to the docstring.

The implementation is correct and follows the established pattern for optional parameters. However, the docstring (lines 115-119) completely omits the new duration parameter. Update the docstring to document what this parameter does and its valid range or constraints. The example in docs/examples/account/create-jwt.md shows it accepts values like 0, which could serve as a reference.

🤖 Prompt for AI Agents
In @lib/services/account.dart around lines 120 - 125, Update the createJWT
method docstring to include the new optional parameter `duration`: state that
`duration` is an optional integer (non-negative, can be 0) representing the
token lifetime in seconds (or the same unit used by the API), note any
constraints such as non-negative values and typical limits if known, and give a
short example/mention that passing 0 is supported per the examples; reference
the createJWT function name so the docstring change is applied to that method.


final Map<String, String> apiHeaders = {
'content-type': 'application/json',
Expand Down
2 changes: 1 addition & 1 deletion lib/services/avatars.dart
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ class Avatars extends Service {
int? width,
int? height,
int? quality,
enums.Output? output}) async {
enums.ImageFormat? output}) async {
const String apiPath = '/avatars/screenshots';

final Map<String, dynamic> params = {
Expand Down
4 changes: 2 additions & 2 deletions lib/services/databases.dart
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ class Databases extends Service {
{required String databaseId,
required String collectionId,
required String documentId,
required Map data,
Map? data,
List<String>? permissions,
String? transactionId}) async {
final String apiPath =
Expand All @@ -228,7 +228,7 @@ class Databases extends Service {
.replaceAll('{documentId}', documentId);

final Map<String, dynamic> apiParams = {
'data': data,
if (data != null) 'data': data,
'permissions': permissions,
'transactionId': transactionId,
};
Expand Down
2 changes: 1 addition & 1 deletion lib/services/storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class Storage extends Service {
.replaceAll('{fileId}', fileId);

final Map<String, dynamic> apiParams = {
'name': name,
if (name != null) 'name': name,
'permissions': permissions,
};
Comment on lines 113 to 116
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n lib/services/storage.dart | head -130 | tail -30

Repository: appwrite/sdk-for-flutter

Length of output: 1287


🏁 Script executed:

# Check the function signature to see parameter types
rg -n "permissions" lib/services/storage.dart -B5 -A2

Repository: appwrite/sdk-for-flutter

Length of output: 1694


🏁 Script executed:

# Check how apiParams is used after being defined
rg -n "apiParams" lib/services/storage.dart -A10

Repository: appwrite/sdk-for-flutter

Length of output: 3629


Inconsistent null handling for permissions parameter.

The name parameter is conditionally included only when non-null (line 114), but permissions on line 115 is unconditionally included. Since permissions is also nullable (List<String>?), this will send a null value to the API when not provided, whereas the createFile method in the same file correctly uses conditional inclusion for permissions (line 62).

Apply the same conditional pattern:

🔧 Suggested fix
     final Map<String, dynamic> apiParams = {
       if (name != null) 'name': name,
-      'permissions': permissions,
+      if (permissions != null) 'permissions': permissions,
     };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
final Map<String, dynamic> apiParams = {
'name': name,
if (name != null) 'name': name,
'permissions': permissions,
};
final Map<String, dynamic> apiParams = {
if (name != null) 'name': name,
if (permissions != null) 'permissions': permissions,
};
🤖 Prompt for AI Agents
In @lib/services/storage.dart around lines 113 - 116, The apiParams map in the
uploadFile (or wherever this snippet is) unconditionally includes 'permissions'
which is nullable, causing a null to be sent to the API; change the map
construction for apiParams to include 'permissions' only when permissions !=
null (same conditional inclusion pattern as used in createFile) so that
'permissions' is omitted when null rather than set to null.


Expand Down
2 changes: 1 addition & 1 deletion lib/src/client_browser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class ClientBrowser extends ClientBase with ClientMixin {
'x-sdk-name': 'Flutter',
'x-sdk-platform': 'client',
'x-sdk-language': 'flutter',
'x-sdk-version': '20.3.2',
'x-sdk-version': '20.3.3',
'X-Appwrite-Response-Format': '1.8.0',
};

Expand Down
2 changes: 1 addition & 1 deletion lib/src/client_io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class ClientIO extends ClientBase with ClientMixin {
'x-sdk-name': 'Flutter',
'x-sdk-platform': 'client',
'x-sdk-language': 'flutter',
'x-sdk-version': '20.3.2',
'x-sdk-version': '20.3.3',
'X-Appwrite-Response-Format': '1.8.0',
};

Expand Down
2 changes: 2 additions & 0 deletions lib/src/client_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ mixin ClientMixin {
if (value != null) {
if (value is int || value is double) {
filteredParams[key] = value.toString();
} else if (value is bool) {
filteredParams[key] = value.toString();
} else if (value is List) {
filteredParams["$key[]"] = value;
} else {
Expand Down
3 changes: 2 additions & 1 deletion lib/src/enums/o_auth_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ enum OAuthProvider {
yandex(value: 'yandex'),
zoho(value: 'zoho'),
zoom(value: 'zoom'),
mock(value: 'mock');
mock(value: 'mock'),
mockUnverified(value: 'mock-unverified');

const OAuthProvider({required this.value});

Expand Down
17 changes: 0 additions & 17 deletions lib/src/enums/output.dart

This file was deleted.

4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: appwrite
version: 20.3.2
description: Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API
version: 20.3.3
description: Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API
homepage: https://appwrite.io
repository: https://github.com/appwrite/sdk-for-flutter
issue_tracker: https://github.com/appwrite/sdk-generator/issues
Expand Down
1 change: 0 additions & 1 deletion test/services/databases_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,6 @@ void main() {
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>',
data: {},
);
expect(response, isA<models.Document>());
});
Expand Down