docs
/
Client Integration

Flutter requests

Reads and writes over any datatype, the escape hatch for everything else, and what the errors mean.

Datatypes, not tables

AppEngine does not hand out a fixed set of tables. Every record has a datatype — sf_product, reservation, customer, or one you define — and the same handful of calls work on all of them. That is the part worth understanding first: you are not limited to the datatypes the platform ships with.

Records come back as plain maps with the fields under data. The client does not impose models, because your app knows its own shapes better than a generic client can.

Reading

final page = await appmint.repository.find(
  'sf_order',
  filter: {'data.status': 'open'},
  pageSize: 20,
  sort: {'createdate': -1},
);

page.items;    // List<Map<String, dynamic>>
page.total;
page.hasMore;

filter is passed through as-is, so it takes the query shape the server understands, including operators:

filter: {'data.total': {r'$gt': 100}}

One record by id:

final order = await appmint.repository.findById('sf_order', id);
`findByAttribute` matches loosely

It can return records that merely resemble the value you passed. Read what comes back and confirm each record is the one you meant before acting on it — and never pipe its results straight into delete. That mistake has destroyed live records.

Writing

final created = await appmint.repository.create('my_datatype', {'title': 'Hello'});

await appmint.repository.update('my_datatype', id, {'title': 'Replaced'});

await appmint.repository.updateFields('my_datatype', id, {
  'data.title': 'Renamed',
  'data.settings.theme': 'dark',
});

await appmint.repository.delete('my_datatype', id);

Two things that cost people an afternoon if nobody says them:

updateFields takes dot paths, not a nested map. Passing {'data': {'settings': {'theme': 'dark'}}} replaces that whole branch and drops its other keys, quietly.

Create is a PUT on the server, not a POST. The client handles that; you only meet it if you call the endpoint yourself and get a 404.

Everything else

The package wraps the calls every app needs. Anything else is one line away, with headers and tokens still handled:

await appmint.http.post('/storefront/pos/tab/$id/settle', body: {...});
await appmint.http.get('/events/tickets', query: {'eventId': id});

await appmint.http.upload('/file/upload', bytes: bytes, fileName: 'photo.jpg');

Pass sendUserToken: false for a call that should go out with app authentication alone.

Watching what it does

appmint.http.onCall = (call) {
  print('${call.method} ${call.path} → ${call.status} '
        '(${call.took.inMilliseconds}ms) '
        'app:${call.appAuthenticated} user:${call.sentUserToken}');
};

AppmintCall carries no token values — only whether each was attached, which is the genuinely confusing part of this API. The authentication example puts this on screen beside the app.

Set logRequests: true on the config for the same thing in the console.

Errors

Failures arrive as AppmintException, and message is safe to show — AppEngine writes its refusals in plain language and the client passes them through rather than replacing them.

TypeMeans
AppmintExceptionThe server answered and refused. statusCode, and reason where it gives one — invalid_code and challenge_expired need different cures
AppmintAppAuthExceptionThe app itself could not authenticate — wrong appId/key/secret, or wrong org. No request can succeed until it is fixed
AppmintSessionExpiredExceptionThe session is gone and could not be renewed. Send them back to sign-in
AppmintNetworkExceptionThe request never reached the server. Distinct from a 5xx, which means the server answered and failed

That last distinction is deliberate: a 400 is an answer, and calling it a network error sends people to check their wifi instead of their password.