appstrax_services 1.0.0
appstrax_services: ^1.0.0 copied to clipboard
A library to integrate with Appstrax Services, an authentication, database and file storage service.
Table of Contents #
- Overview
- Getting Started
- Installation
- Setup
- Auth Service
- Google SSO
- Database Service
- Storage Service
Overview #
Appstrax in partnership with CodeCapsules provides software tools and infrastructure to assist in the development, hosting and managing of your applications.
Getting Started #
To use the available services, an instance of the Auth, Database and Storage API need to be configured internally.
Each instance deploys a front-end to interact with registered users, application data and uploaded Files accordingly.
To get this instance set-up, first send an email to info@appstrax.tech and we'll help create, host and manage your Appstrax API here: https://codecapsules.io/
Installation #
Add appstrax_services as a project dependency:
flutter pub add appstrax_services
Setup #
Initialize the appstrax_services libraries:
// import the services library
import 'package:appstrax_services/initialize.dart';
// initialize the service
await initializeAppstraxServices(
apiUrl: 'https://<your-appstrax-services-instance>',
apiKey: '<your-appstrax-services-instance-api-key>',
);
apiUrl must use https — every request carries a password or a token. Loopback
addresses (localhost, 127.0.0.1, ::1, 10.0.2.2) are exempt in debug
builds so you can develop against a local API; a release build rejects them.
Your API key is not a secret. It ships inside your app, where anyone with the APK or IPA can read it, so treat it as identifying the project rather than authenticating the caller — every authorization decision must rest on the signed-in user's access token, server-side.
Supported platforms: Android (API 24+) and iOS. The package uses dart:io,
so it does not compile for web. Requires Dart 3.8 / Flutter 3.32 or later —
flutter_secure_storage 11, which holds the refresh token, sets both floors.
Options #
await initializeAppstraxServices(
apiUrl: '...',
apiKey: '...',
// Your own client, for certificate pinning, a proxy, or request logging.
// It is wrapped for retries, and closed by you rather than by the SDK.
httpClient: myPinnedClient,
// How long one request may take, retries included. Defaults to 30 seconds.
timeout: const Duration(seconds: 30),
// The same, for a file upload — both sending it and reading the answer.
// Separate because 30 seconds is far too short for a large file on a mobile
// connection. Defaults to 5 minutes.
uploadTimeout: const Duration(minutes: 5),
// Called if a saved session could not be restored on startup. Restoring is
// never allowed to throw, so this is the only way to see that it happened.
onRestoreError: (error, stackTrace) => log('$error', stackTrace: stackTrace),
);
Reads are retried with exponential backoff when a connection fails or the server
answers 502/503/504. Writes are never replayed, so a failed create cannot leave
two records behind — and neither is the token refresh, because the API rotates
refresh tokens, so replaying a lost one would present a credential the server has
already spent.
Shutting down #
Optional if your app runs until the process ends. Necessary if you initialize more than once — a hot restart, a test suite, or switching which API you talk to:
await disposeAppstraxServices();
This releases the SDK's HTTP client and clears in-memory session state. It does
not sign the user out: the refresh token stays in secure storage, so a later
initializeAppstraxServices restores the session as it would after a restart.
Call appstraxAuth.logout() first if you want it ended. An httpClient you
passed in is left open, since it belongs to you.
Upgrading to 1.0.0 #
Coming from 0.1.0, which is the last version published — 0.2.0 and 0.3.0 were written but never released, so read their entries in the changelog too. Google SSO arrived in 0.2.0 and the audit fixes in 0.3.0.
Signed-in users have to sign in once more. On Apple platforms the refresh
token now lives in a keychain item that does not outlive the app; on Android it
moved to flutter_secure_storage 11's cipher storage, which does not read what
9.x wrote. Either way a token from an earlier version is not found, and the user
signs in again — once.
Two floors moved, both because of flutter_secure_storage 11:
| Requirement | Was | Now |
|---|---|---|
| Dart SDK | 3.5 | 3.8 |
| Flutter | 3.24 | 3.32 |
Android minSdk |
21 | 24 |
Everything else is a compile error or a string you were not supposed to match on:
| Change | What to do |
|---|---|
HttpException.toString() no longer includes the response body |
Branch on err.code; read err.body and the new err.status when you need detail |
FindResult deleted |
Nothing produced it — remove the reference |
FindResultDto<T> is no longer generic |
Drop the type argument |
SsoCancelledException and SsoCallbackMismatchException now extend AppstraxException |
toString() gains the Exception: prefix; catch by type or code instead |
Utils replaced by shared/jwt.dart |
Utils().decodeToken(t) → decodeToken(t); Utils().isTokenExpired(t) → isTokenExpired(t) |
Utils().generateRandomString and Utils().sleep removed |
Nothing in the SDK used them; use Random.secure() and Future.delayed |
A cleartext apiUrl is rejected in release builds, loopback included |
Use https outside debug |
A malformed response raises FormatException naming the endpoint |
It used to be a TypeError from inside the SDK; catch FormatException if you were catching that |
| The token refresh is no longer retried | Nothing to do — a refresh now fails cleanly instead of risking a sign-out |
Auth Service #
This library integrates with the Appstrax Auth API which allows you to easily interact with authentication in your mobile applications.
To get started import appstraxAuth into your project:
import 'package:appstrax_services/auth.dart';
Registration #
RegisterDto registerDto = RegisterDto(
email: 'joe@soap.com',
password: '<password>',
data: {
// any additional/custom user fields
'name': 'Joe',
'surname': 'Soap',
...
}
);
try {
AuthResult result = await appstraxAuth.register(registerDto);
User user = result.user!;
print('User Successfully Registered, userId: ${user.id}');
} catch (err) {
// something went wrong while registering the user
handleError(err);
}
Login #
LoginDto loginDto = LoginDto(
email: 'email',
password: '<password>',
);
try {
AuthResult result = await appstraxAuth.login(loginDto);
User user = result.user!;
print('User Successfully Logged In, userId: ${user.id}');
} catch (err) {
// something went wrong while logging in
handleError(err);
}
Google SSO #
Sign in with Google in one call. The provider's sign-in page opens in the
system browser — ASWebAuthenticationSession on iOS, Custom Tabs on Android —
never an in-app web view, so the user's Google password is never exposed to your
app and an existing Google session can be reused.
Your app receives ordinary Appstrax tokens, exactly as it does from
appstraxAuth.login. Google tokens are never used as Appstrax credentials.
Requirements #
| Platform | Requirement |
|---|---|
| Android | API 24+ |
| iOS | 17.4 or later |
| macOS | 14.4 or later |
Google SSO returns to your app over an HTTPS link, which Apple's authentication session only supports from iOS 17.4 / macOS 14.4. Email and password sign-in is unaffected on older versions — check the platform version before showing a Google button if you support them.
loginWith is Android, iOS and macOS only. The HTTPS callback it relies on has
no equivalent on Web, Windows or Linux, so hide the Google button there.
1. Register your app #
In the Appstrax admin panel, under Auth Configuration → SSO Settings:
- Enable Google and enter the OAuth client ID and secret.
- Under Mobile apps, add your app:
- Android — the package name, plus the SHA-256 fingerprints of your signing certificates.
- iOS — the app ID in
TEAMID.bundle.idform.
Add both your release and debug signing fingerprints. With only the release one, link verification fails silently on debug builds and Android opens a browser instead of your app — so it works in production but not on your machine. Print them with:
# debug
keytool -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore
# release
keytool -list -v -alias <your-alias> -keystore <your-keystore>
Registering an app publishes the association files the operating system checks,
and allows https://<your-api-domain>/sso-callback as a return URL for that app.
You do not need to allowlist a redirect URL yourself.
2. Configure your app #
Android — add the callback activity to android/app/src/main/AndroidManifest.xml,
inside <application>, replacing the host with your API domain:
<activity
android:name="com.linusu.flutter_web_auth_2.CallbackActivity"
android:exported="true"
android:taskAffinity="">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="<your-api-domain>"
android:pathPrefix="/sso-callback" />
</intent-filter>
</activity>
iOS — add the Associated Domains capability in Xcode with the entry
applinks:<your-api-domain>.
Then confirm Android actually verified the link on a real device:
adb shell pm get-app-links <your.package.name>
Every domain should report verified. If it does not, the fingerprints above
are usually the reason.
3. Sign in #
try {
AuthResult result = await appstraxAuth.loginWith(SSO.google);
if (result.status == AuthStatus.linkRequired) {
// This email already has a password account - see Linking below.
result = await appstraxAuth.linkSsoAccount(
'<password>',
pendingLinkToken: result.pendingLinkToken,
);
}
if (result.status == AuthStatus.pendingTwoFactorAuthCode) {
User user = await appstraxAuth.verifyTwoFactorAuthCode('<code>');
print('Signed in with Google, userId: ${user.id}');
} else if (result.status == AuthStatus.authenticated) {
User user = result.user!;
print('Signed in with Google, userId: ${user.id}');
}
} on SsoCancelledException {
// the user dismissed the sign-in sheet - usually nothing to report. Any
// session they were already signed in to is left untouched.
} catch (err) {
// something went wrong while signing in
handleError(err);
}
The session is stored the same way as a password login, so
initializeAppstraxServices restores it the next time your app starts.
If the user was already signed in, that session is only replaced once Google has returned successfully — cancelling or failing part way through leaves them signed in as they were.
Linking an existing account #
If the Google account's email address already belongs to an account with a
password, Appstrax will not sign the user straight in — that would let anyone who
controls the address take over the account. Instead loginWith returns
AuthStatus.linkRequired, and you ask for the existing password to prove
ownership:
AuthResult result = await appstraxAuth.loginWith(SSO.google);
if (result.status == AuthStatus.linkRequired) {
// Prompt for the password on their existing account, then:
result = await appstraxAuth.linkSsoAccount(enteredPassword);
}
The pending link token is remembered from loginWith, so passing it explicitly
is optional. It is held in memory for the ten minutes the API accepts it, and
survives anything your app does in the meantime — a wrong password throws without
ending the attempt, so the user can simply try again. The token is only dropped
when the link succeeds, when you start another sign-in, or when you log out; it
does not survive a restart, so loginWith has to be called again after one. If
the account has two factor authentication enabled, linking returns
AuthStatus.pendingTwoFactorAuthCode — call verifyTwoFactorAuthCode to finish.
Checking which providers are enabled #
Use this to hide the Google button when SSO has not been configured:
List<SsoProviderInfo> providers = await appstraxAuth.getSsoProviders();
bool googleEnabled = providers.any((p) => p.id == SSO.google.id && p.enabled);
Options #
await appstraxAuth.loginWith(
SSO.google,
options: SsoLoginOptions(
// Defaults to {apiUrl}/sso-callback. Only set this if your app claims a
// different https URL, and allowlist it in the admin panel.
redirectUri: 'https://<your-api-domain>/sso-callback',
// Ignore any Google account already signed in on the device. Defaults to
// false so returning users can just pick their account.
preferEphemeralSession: false,
),
);
Forgot Password #
ForgotPasswordDto forgotPasswordDto = ForgotPasswordDto(
email: 'email'
);
try {
Message message = await appstraxAuth.forgotPassword(forgotPasswordDto);
print('forgotPassword email sent: ${message.message}');
} catch (err) {
// something went wrong sending the email
handleError(err);
}
An email will be sent to the user with a password reset code, this code is only valid for 24 hours.
Reset Password #
ResetPasswordDto resetPasswordDto = ResetPasswordDto(
email: 'email',
code: '<code>',
password: '<password>',
);
try {
Message message = await appstraxAuth.resetPassword(resetPasswordDto);
print('Password Successfully Reset: ${message.message}');
} catch (err) {
// something went wrong while resetting the password
handleError(err);
}
The password is now reset, however the user will now need to login with their new password.
Change Password #
ChangePasswordDto changePasswordDto = ChangePasswordDto(
password: '<currentPassword>',
newPassword: '<newPassword>',
);
try {
User user = await appstraxAuth.changePassword(changePasswordDto);
print('Changed password for userId: ${user.id}');
} catch (err) {
// something went wrong while changing the password
handleError(err);
}
Users can only change their password if they are already authenticated.
Save User Data/Profile #
try {
User updatedUser = await appstraxAuth.saveUserData({
// any additional/custom user fields
'name': 'Joe',
'surname': 'Soap',
'career': 'Software Engineer',
...
});
print('user data successfully updated, userId: ${updatedUser.id}');
} catch (err) {
// something went wrong while updating the user data
handleError(err);
}
Users can only update their data if they are already authenticated.
Logout #
// The local session is always cleared. The return value tells you whether the
// server acknowledged, which matters on a shared device: if it is false, a
// refresh token you can no longer see may still be live.
bool revoked = await appstraxAuth.logout();
if (!revoked) {
// offline, most likely - worth retrying when a connection is back
}
Handling Errors #
Errors have types, so you can respond to the reason rather than matching on the message:
try {
await appstraxAuth.login(LoginDto(email: email, password: password));
} on UnauthorizedException catch (err) {
// The API's own reason, as an AuthErrors constant.
if (err.code == AuthErrors.invalidEmailOrPassword) {
showMessage('That email address and password do not match.');
} else if (err.code == AuthErrors.userBlocked) {
showMessage('This account has been blocked.');
}
} on RateLimitedException {
showMessage('Too many attempts. Try again in a few minutes.');
} on TimeoutException {
showMessage('The server took too long to answer.');
}
The errors the SDK raises itself have their own types:
| Type | code |
Raised when |
|---|---|---|
NotAuthenticatedException |
notAuthenticated |
No session, or one that can no longer be refreshed |
TokenExpiredException |
tokenExpired |
The access token expired and could not be renewed |
LoginRequiredException |
— | A two factor code arrived before any sign-in started |
AlreadyAuthenticatedException |
— | A two factor code arrived for a finished session |
MissingSsoLinkTokenException |
missingSsoLinkToken |
linkSsoAccount with no pending link |
SsoCancelledException |
ssoCancelled |
The user dismissed the sign-in sheet |
SsoCallbackMismatchException |
ssoRedirectMismatch |
Sign-in returned to an unexpected address |
SsoRedirectException |
ssoRedirect |
The provider reported a failure |
The errors raised from an HTTP status have their own types too — BadRequestException,
UnauthorizedException, ForbiddenException, NotFoundException, ConflictException,
RateLimitedException and FetchDataException. UnauthorizedException (401) and
ForbiddenException (403) are the pair worth keeping apart: the first means the
session is over, the second means the session is fine and the operation is not
allowed. See Collections and policies for where the
database raises them.
All of them extend AppstraxException, which is an Exception, so
on AppstraxException catches every one — including the two SSO errors, which
before 1.0.0 implemented Exception directly and slipped through it.
Errors are safe to log #
toString() on an SDK error names the reason, not the response body:
catch (err) {
log('$err'); // "Invalid Request: invalidEmailOrPassword"
}
That matters because an API is free to echo a rejected request back in a
validation error, and before 1.0.0 the body was the message — so logging a
caught exception could publish a submitted password to your crash reporter. The
body is still there when you need it, on err.body, along with err.status.
Treat both as untrusted: prefer err.code for branching, and do not log
err.body wholesale.
Auth Error Messages #
class AuthErrors {
// registration errors
static const emailAddressAlreadyExists = 'emailAddressAlreadyExists';
static const badlyFormattedEmailAddress = 'badlyFormattedEmailAddress';
static const noPasswordSupplied = 'noPasswordSupplied';
// login errors
static const invalidEmailOrPassword = 'invalidEmailOrPassword';
static const userBlocked = 'userBlocked';
static const invalidTwoFactorAuthCode = 'invalidTwoFactorAuthCode';
// forgot/reset password errors
static const emailAddressDoesNotExist = 'emailAddressDoesNotExist';
static const invalidResetCode = 'invalidResetCode';
// session errors, raised by the SDK rather than returned by the API
static const notAuthenticated = 'notAuthenticated';
static const tokenExpired = 'tokenExpired';
// unknown errors
static const unexpectedError = 'unexpectedError';
}
Database Service #
This library integrates with the Appstrax Database API which allows you to easily interact with your database in your mobile applications.
To get started import appstraxDb into your project:
import 'package:appstrax_services/database.dart';
Collections and policies #
Two things about your API decide what this service can do, and both are set up in the admin panel rather than from here.
A collection has to exist before you can write to it. Writing to a name that
was never registered answers NotFoundException instead of creating it, so that
is the first thing to check when a call 404s on a collection you expected to be
there. Collections that already held data before your API was upgraded were
registered for you, so existing apps carry on unchanged.
Each collection carries a policy saying who may read, create, update and delete, and which fields each of those may touch. This SDK cannot read or change a policy — an administrator authors it in the admin panel. What reaches your app is the outcome:
| Exception | Meaning | What to do |
|---|---|---|
ForbiddenException |
The policy denies this operation, or a field you sent | Not a session problem — do not sign the user out |
ConflictException |
The document changed while you were writing it | Re-read it, apply your change again, send it |
NotFoundException |
The document is missing or hidden from you, or the collection is not registered | Check the collection exists in the admin panel |
BadRequestException |
A field you may not write, or an invalid where/order |
err.body names the field |
try {
await appstraxDb.create('orders', {'total': 42});
} on ForbiddenException {
showMessage('You do not have permission to do that.');
} on ConflictException {
// Safe to retry once you have re-read the document.
showMessage('Someone else changed this. Reloading…');
} on NotFoundException {
showMessage('That collection or document does not exist.');
}
Branch on the type, not on err.code. For a policy failure the API sends
prose — Insufficient Permissions — rather than one of the machine-readable
constants the auth endpoints use.
ForbiddenException and UnauthorizedException are deliberately separate. A 401
means the session is over and the user should sign in; a 403 means the session is
perfectly good and signing in again would change nothing.
Fields you can read but not write #
A policy can allow a field to be read and not written. Two consequences:
- A document may arrive with fields missing, where the policy hides them.
DocumentDto.datais a map and copes; your own model will not if it declares those fields as required. - Sending a field you may not write is rejected —
400 Fields not writable: x— even if you send it back exactly as you read it. This is what makesCrudService.save()unusable on such a collection unless the model declares only writable fields, sincesave()sends the whole model. UseappstraxDb.edit()with just the fields you mean to change instead.
How many rows come back #
find() returns at most 1000 documents, whether or not you ask for a limit —
a query with no limit is a request for the first thousand, not for all of them.
A larger limit is clamped rather than refused.
Nothing is hidden by that. FindResultDto.limit is the limit actually applied,
and count is the total number of matching documents you are allowed to read, so
count greater than data.length means there are more:
var offset = 0;
final all = <DocumentDto>[];
while (true) {
final page = await appstraxDb.find(
'orders',
query: FetchQuery(limit: 1000, offset: offset),
);
all.addAll(page.data ?? []);
if (all.length >= (page.count ?? 0)) break;
offset += 1000;
}
Create #
// Create a new Document, returns a DocumentDto from the 'testing' collection
try {
var document = await appstraxDb.create('testing', {
'name': 'John',
'surname': 'Doe',
'dogs': 3,
'career': 'Software Engineer',
});
createdId = document.id;
print('Document created docId: $createdId');
} catch (err) {
// something went wrong while creating
handleError(err);
}
Edit #
// edit documents
try {
var document = await appstraxDb.edit('testing', createdId, {
'name': 'John',
'surname': 'Doe',
'dogs': 3,
'career': 'Senior Software Engineer',
'added': 'this field'
});
print('Document edited: ${document.id}');
} catch (err) {
// something went wrong while editing
handleError(err);
}
Delete #
// delete documents
try {
await appstraxDb.delete('testing', createdId as String);
print('Document deleted.');
} catch (err) {
// something went wrong while deleting
handleError(err);
}
Find By Id #
// findById returns a DocumentDto from the 'testing' collection
try {
var document = await appstraxDb.findById('testing', createdId as String);
print('Document foundById: ${document.id}');
} catch (err) {
// something went wrong while querying the 'testing' collection
handleError(err);
}
Find #
// find returns a FindResultDto
try {
var documents = await appstraxDb.find('testing');
if (documents.data?.length != null) {
documents.data?.forEach((user) {
print('docId: ${user.id}');
});
}
} catch (err) {
// something went wrong while querying the 'testing' collection
handleError(err);
}
// find accepts a FetchQuery as in examples above
// You can create compound queries
// Here we search for documents where name=='John' AND dogs IN [1, 5, 7, 2]
try {
FindResultDto documents = await appstraxDb.find(
'testing',
query: FetchQuery(
where: {
Operator.and: [
{
'name': {Operator.equal: 'John'}
},
{
'dogs': {
Operator.qIn: [1, 5, 7, 2]
}
},
]
},
order: {'email': OrderDirection.asc},
offset: 0,
limit: 5,
),
);
if (documents.data?.length != null) {
documents.data?.forEach((user) {
print('docId: ${user.id}');
});
}
print('Documents found using find(query)');
} catch (err) {
// something went wrong while querying the 'testing' collection
handleError(err);
}
CRUD Service #
The CrudService is a abstract class which wraps the Create, Read, Update and Delete functions in appstraxDb. The CrudService is declared with a model which extends the Model class, this model is the type that the service Creates, Reads, Updates and Deletes.
Similarly to appstraxDb, the CrudService uses a collection name which represents the "table" that the data is stored in on the server.
To get started import CrudService & Model into your project:
import 'package:appstrax_services/database.dart';
Create your custom Model & Service:
// Create a custom Model that implements the base Model
import 'package:json_annotation/json_annotation.dart';
import 'package:appstrax_services/shared/models/model.dart';
part 'person.g.dart';
@JsonSerializable()
class Person implements Model {
@override
String? id;
@override
DateTime? createdAt;
@override
DateTime? updatedAt;
String name;
String surname;
String career;
int dogs;
Person({
this.id,
this.createdAt,
this.updatedAt,
required this.name,
required this.surname,
required this.career,
required this.dogs,
});
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
@override
Map<String, dynamic> toJson() => _$PersonToJson(this);
}
// Generate your person.g.dart file from the terminal with:
flutter pub run build_runner build
// Create a custom service for the Model that extends the CRUD service
class PersonService extends CrudService<Person> {
PersonService() : super('person', Person.fromJson);
}
var personService = PersonService();
We can now use the ProfileService as follows:
// Now you have access to all the CRUD functions on your personService
try {
Person person = Person(
name: 'James',
surname: 'Doe',
career: 'farmer',
dogs: 9,
);
// save checks for an ID, if it exists the doc is edited else it is created
var createdPerson = await personService.save(person);
personCreatedId = createdPerson.id;
print('Person created: ${createdPerson.id}');
} catch (err) {
// something went wrong while saving
handleError(err);
}
// The CRUD service returns the type created ie: Person
try {
var foundPerson = await personService.findById(personCreatedId as String);
print('Person foundById: ${foundPerson.id}');
} catch (err) {
// something went wrong while querying
handleError(err);
}
// find
try {
var foundPersons = await personService.find();
if (foundPersons != null) {
for (var per in foundPersons) {
print('DocId: ${per.id}');
}
}
} catch (err) {
// something went wrong while querying
handleError(err);
}
// The find() accepts a FetchQuery, same as above
try {
var foundPersons = await personService.find(
query: FetchQuery(where: {
'name': {Operator.equal: 'James'}
}),
);
if (foundPersons != null) {
for (var per in foundPersons) {
print('DocId: ${per.id}');
}
}
} catch (err) {
// something went wrong while querying
handleError(err);
}
Storage Service #
This library integrates with the Appstrax Storage API which allows you to easily interact with storage in your mobile applications.
To get started import appstraxStorage into your project:
import 'package:appstrax_services/storage.dart';
Examples #
// Upload File
try {
Response res = await appstraxStorage.uploadFile(file, '<folder>/',
options: HttpOptions(
onUploadProgress: (progress) => print(
'Upload Progress: ${progress.percent}',
),
));
downloadUrl = res.downloadUrl;
print('DownloadUrl: ${res.downloadUrl}');
} catch (err) {
// something went wrong while uploading
handleError(err);
}
// Delete File using downloadUrl
try {
await appstraxStorage.deleteFile(downloadUrl as String);
print('Deleted successfully');
} catch (err) {
// something went wrong while deleting
handleError(err);
}
Query Models #
The find() function accepts FetchQuery as an argument:
Fetch Query #
class FetchQuery {
// Conditions to query data
Map<String, dynamic>? where;
// ASC, DESC Order
Map<String, dynamic>? order;
// Pagination variables
int? offset;
int? limit;
}
class OrderDirection {
// Ascending Order Direction
static const asc = 'ASC';
// Descending Order Direction
static const desc = 'DESC';
}
Query Operators #
class Operator {
// Equal To Operator
static const String equal = 'EQUAL';
// Not Equal To Operator
static const String notEqual = 'NOT_EQUAL';
// And Operator
static const String and = 'AND';
// Or Operator
static const String or = 'OR';
// Greater Than Operator
static const String gt = 'GT';
// Greater Than or Equal To Operator
static const String gte = 'GTE';
// Less Than Operator
static const String lt = 'LT';
// Less Than or Equal To Operator
static const String lte = 'LTE';
// Like Operator
static const String like = 'LIKE';
// Not Like Operator
static const String notLike = 'NOT_LIKE';
// In Operator
static const String qIn = 'IN';
// Whether an array field holds a value. `IN` asks whether a scalar field is
// one of several values; `CONTAINS` asks whether an array field holds one.
static const String contains = 'CONTAINS';
}
Query Results #
The find() function returns a FindResultDto:
// querying the data returns a FindResultDto from the collection
class FindResultDto {
List<DocumentDto>? data;
dynamic where;
dynamic order;
int? limit;
int? offset;
int? count;
FindResultDto({
this.data,
this.where,
this.order,
this.limit,
this.offset,
this.count,
});
}
class DocumentDto {
final String id;
final Map<String, dynamic> data;
final DateTime createdAt;
final DateTime updatedAt;
DocumentDto(
this.id,
this.data,
this.createdAt,
this.updatedAt,
);
}