appstrax_services
A library to integrate with Appstrax Services, an authentication, database and file storage service.
1.0.0
144 downloads/mo
#1646 most downloaded on pub.dev
appstrax/appstrax-services-flutter
What this package is like to depend on
Last release 5 days ago
18 Aug 2026
Release timing varies
gaps range from 4 months to 2.7 years
Nearly every release is documented
notes for 5 of 5 stable releases
Nothing withdrawn
no release was ever pulled
3 years old
5 releases · first in 2023
1 release in the last 12 months
see the full history below
Release timeline
5 releases · Feb 2023 to Aug 2026Releases
latest 5-
1.0.018 Aug 2026Release notes
Open source →Addresses the remaining findings from the August 2026 audit, and stabilises the API.
If you are upgrading, you are coming from 0.1.0. 0.2.0 and 0.3.0 were written but never published, so their entries above apply to you as well - Google SSO arrived in 0.2.0, and the bulk of the audit fixes in 0.3.0. Because nothing was released between, every breaking change the audit called for is collected here in one release rather than spread over a deprecation cycle.
Breaking
-
Signing in again is required once, on upgrade. On Apple platforms the keychain item is now
first_unlock_this_device, so a session no longer outlives the app it belongs to or restores onto a different phone. On Android the token moved toflutter_secure_storage11'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.(0.3.0 said this would land in
EncryptedSharedPreferences. That backend was deprecated by Google and removed from the plugin before this release shipped, so it never did - see the dependency note below.) -
flutter_secure_storage9 → 11, which moves three floors. Dart 3.5 → 3.8, Flutter 3.24 → 3.32, and AndroidminSdk21 → 24. The Dart bound is what pub enforces; the Flutter bound is stated so a consumer gets a clear resolution error rather than a confusing SDK one, andminSdkcomes from the plugin's own Gradle configuration, so an app below 24 will fail its Android build.This is the audit's highest-value dependency finding. 9.x rested on Jetpack Security's
EncryptedSharedPreferences, which Google deprecated, and pulledflutter_secure_storage_macos, which is now marked discontinued on pub.dev. The^9.2.4constraint also blocked a consuming app from resolving 10 or 11 at all, so an app that needed the newer plugin for any other reason could not use this SDK. Both discontinued packages drop out of the resolution entirely,js0.6.7 along with them.The Android ciphers are now stated in
SecureStorageServicerather than inherited - AES-GCM for the data, RSA-OAEP to wrap the key. They match the plugin's defaults, but a default is something the plugin may change, and this is the one credential in the package. -
A 403 no longer raises
UnauthorizedException. It raises the newForbiddenExceptioninstead. Anyone catchingUnauthorizedExceptionto mean "the session expired, show the login screen" must addon ForbiddenException- and should, because the two call for opposite responses.The database's collection policies are what made the shared type untenable. A 401 means the session is over and signing in again fixes it; a 403 means the session is perfectly good and the caller is not permitted to do this, so signing in again accomplishes nothing. Sharing a type sent users to a login screen for a button they were never allowed to press.
-
LoginDto.rememberis gone, and signing in always keeps the session.LoginDto(email: ..., password: ..., remember: false)no longer compiles, and the flag is no longer sent to the API.This is a behaviour change as well as a compile break.
remember: falsewas the only way a session did not survive a restart: it kept the refresh token in memory and never wrote it to secure storage. A user who signs in now stays signed in until they sign out or the refresh token expires - including on a shared device, wherelogout()is now the only way to end a session early. -
An HTTP error no longer puts the response body in
toString(). It readsInvalid Request: invalidEmailOrPasswordrather than the body verbatim.This one is worth reading twice, because the old behaviour was a quiet leak. An API is free to echo a rejected request back in a validation error, and logging a caught exception -
catch (err) { log('$err'); }, the pattern this README recommended - therefore wrote whatever was submitted into your crash reporter, passwords included. The body is still onerr.body, now alongside a newerr.status, anderr.coderemains the supported way to branch. -
A cleartext
apiUrlis rejected in release builds, loopback included. Thelocalhost/127.0.0.1/::1/10.0.2.2exemption is now conditional onkDebugMode.10.0.2.2is only "the Android emulator's host" by convention - it is otherwise a routable private address, so a shipped app pointed at it sent passwords in the clear over whatever network it was on. -
FindResultis deleted. Nothing in the package produced it, and its type parameter did nothing -datawas hardcoded toList<User>, soFindResult<Person>compiled and handed back users. It existed to serve anappstraxUsersservice the README documented and the package never shipped. -
FindResultDtois no longer generic. Drop the type argument.datawas aList<DocumentDto>whateverTwas, so the parameter was a promise the class did not keep. UseCrudService<T>to get typed models back. -
SsoCancelledExceptionandSsoCallbackMismatchExceptionnow extendAppstraxException. They implementedExceptiondirectly, soon AppstraxException- the obvious way to catch everything this SDK raises - silently missed a cancelled sign-in, the most common of the lot. Both now carry acode. TheirtoString()gains theException:prefix as the price of the shared base class. -
Utilsis replaced byshared/jwt.dart.Utils().decodeToken(t)becomesdecodeToken(t)andUtils().isTokenExpired(t)becomesisTokenExpired(t);decodeTokennow returnsMap<String, dynamic>rather thandynamic.generateRandomStringandsleepare gone - nothing in the SDK used them. -
The package declares
platforms: android, ios. It importsdart:iofor file upload, from a file every service depends on, so it has never compiled for web - pub simply did not say so, and a web developer found out via a compile error inside someone else's code. Desktop is untested rather than broken.
Fixed
-
A refresh whose response is lost no longer signs the user out. The token refresh was retried, on the reasoning that a dropped packet should not end a session. It had the opposite effect: the API rotates refresh tokens, so a replay after a lost response presents a token the server has already spent, which comes back
401, which means the session is over. It is no longer replayed, so the refresh fails cleanly and the stored token survives to be tried again. -
A refresh that does not rotate the token is reported instead of stored. Only a non-null refresh token was ever written, so a response that omitted one left the spent token in secure storage - and the next refresh then failed
401and signed the user out, two requests after the actual cause. -
A malformed response names the endpoint instead of throwing a
TypeError. 0.3.0 set out to remove that whole class of defect and left three parsers outside the net: a200from/loginmissingtokenstill raisedtype 'Null' is not a subtype of type 'String'from inside the SDK.Tokens,SsoProviderInfo,TwoFactorAuthDto,Message,DocumentDtoandUserare now hand-written against checked readers. (strict-castscannot catch these - it forbids implicit casts, and every one of them was explicit.)Useris included because being all-nullable protected it from the missing-field failure but not from two others:rolescast each element, so one number in the list threw, andcreatedAtwas parsed without checking it was a date. It is the most frequently parsed response in the SDK, so it was the last place worth leaving an unchecked read. -
A
2xxresponse that is not JSON is reported as a failed request. A captive portal answering200with an HTML page raisedFormatException: Unexpected character (at character 1), naming neither the status nor what arrived. -
An upload can no longer hang forever. The timeout was attached to sending the request, which resolves as soon as response headers arrive; the read that followed had no ceiling at all. Uploads also no longer share the 30-second request budget - see
uploadTimeout. -
The refresh token is sent once per request, not twice. It went out in a
Cookieand in the JSON body, which is two chances for a proxy log or a crash dump to capture it, for no gain:x-auth-transport: bodyalready tells the API which to read. -
Using the SDK before initializing says so. It raised
Invalid argument(s) (baseUrl missing, please initialize.): Must not be null- which states the opposite of the problem, since nothing was null. Now aStateErrornaminginitializeAppstraxServices. -
A write conflict no longer reports as an internal server error. The database answers 409 when a document changed between the policy check and the write, and 409 had no case in the status mapping - so it fell through to
FetchDataException, whose prefix isInternal Server Error:. It now raisesConflictException, which is what it is: the request was fine and can be retried once the caller has re-read the document. The SDK does not retry it, because resolving a conflict means deciding what the record should now contain. -
A saved token that cannot be read no longer bricks startup.
SecureStorageService.initialize()sat one layer above the catch-all inAppstraxAuth.initialize()and had none of its own, so a platform that could not decrypt what it found failedinitializeAppstraxServices- and because the value stayed in storage, it failed on every later launch too, with no way out but reinstalling. Unreadable is now treated as absent and the value is cleared.The
flutter_secure_storageupgrade is what made this worth guarding rather than theorising about, but the platform has always had its own reasons: a KeyStore entry invalidated by a new fingerprint, ciphertext restored onto a device whose key did not travel with it, a locked keyring. -
A failed session restore is no longer silent. It still cannot throw - an exception there fails app startup permanently, because the offending value stays in secure storage - but it now reports to
onRestoreErrorand to the debug console. -
The user endpoints no longer make two extra round trips.
changePassword,saveUserData,verifyEmailAddressand four others each opened withisAuthenticated(), which refreshes the token and fetches/session. Three sequential requests for one action, and none of them read the user. -
An SSO failure keeps the provider's wording off the message. The callback's
errorquery parameter became the exception message an application would show. It is now onSsoRedirectException.providerError, with fixed text inmessage.
Added
-
ForbiddenException(403) andConflictException(409), described above. -
Operator.contains, which sendsCONTAINS. The API has supported it for some time and this SDK was the only place it could not be expressed. Not the same question asOperator.qIn:INasks whether a scalar field is one of several values,CONTAINSasks whether an array field holds one. -
uploadTimeoutoninitializeAppstraxServices, defaulting to five minutes. Thirty seconds is generous for a JSON call and far too short for a large file on a mobile connection - this package's own test fixture is a 10 MB video. -
onRestoreErroroninitializeAppstraxServices, for the startup failure that is not allowed to throw. -
disposeAppstraxServices(), which releases the HTTP client and clears in-memory session state without signing the user out. Optional in an app that runs until the process ends; necessary anywhere the SDK is initialized more than once - a hot restart, a test suite, switching API. Previously each initialize leaked the last client and left the previous session's user in memory. -
HttpException.status, the HTTP status as a field. It was either absent or smuggled intoFetchDataException's message text. -
Codes on every SDK-raised error, so
err.codeworks for the SSO failures as it already did for the session ones.
Notes on the database API
None of these need a change in your code; they are behaviour on the API's side that this SDK now documents rather than lets you discover.
-
Collections are registered, not created by writing to them. Writing to a name nobody registered answers 404 instead of creating the collection. Existing installs are unaffected - the API registers every collection that already holds data with a permissive legacy policy when it boots, so an upgrade changes nothing until an administrator authors a real policy.
-
find()returns at most 1000 documents, whether or not a limit is given. An absent limit used to mean no limit at all; it is now the ceiling. A larger limit is clamped rather than refused, and nothing is hidden by that:FindResultDto.limitreports the limit actually applied andcountis the total you are allowed to read, socount > data.lengthmeans page withoffset. -
A document may arrive with fields missing, where a policy allows the document but not every field on it.
DocumentDto.datais a map and copes; aCrudServicemodel that declares those fields as required will not. -
CrudService.save()cannot be used with a collection that restricts writable fields. It sends the whole model, and the API rejects any field the policy does not allow the caller to write - including a field sent back exactly as it was read, since the write mask is checked before immutable fields are restored. Such a model must declare only writable fields, or useappstraxDb.edit()with just the fields being changed.
Internal
-
Dev dependencies current:
flutter_lints5 → 6,build_runner→ 2.15.1. The two lints flutter_lints 6 adds -use_null_aware_elementsandunnecessary_underscores- are applied rather than suppressed. -
The
testdev dependency is gone. One integration file importedpackage:testwhile the seventeen around it usedflutter_test, which is a mismatch worth removing on its own -package:testprovides no Flutter binding, and that file needs one. It had also begun to conflict with thetest_apiversionflutter_testpins. -
avoid_dynamic_callsis enabled.strict-castscatches an implicit cast, but reaching into adynamicis a dynamic call - and since every response body enters this package asdynamic, that was the larger half of the same problem. -
The database, storage, CRUD and SSO-browser layers have unit tests for the first time. They were reachable only through
test/integration, which needs a live API and so never runs in CI - and they are exactly the layers 0.3.0 rewrote. The offline suite went from 140 tests to 201, and those four files from no coverage to 100%, 100%, 77% and 100%. -
SsoBrowseris exercised through the plugin's own method channel, so a cancelled sheet and a callback that arrived at the wrong address are now testable without a device.
-
-
0.1.012 Dec 2023 -
0.0.318 Aug 2023 -
0.0.224 Feb 2023 -
0.0.122 Feb 2023