HonzikH
Convex Community6d ago
2 replies
Honzik

Flutter client

I picked Convex mostly for the devex and real-time model, but Flutter still doesn’t have a first-class client. I ended up building one and also a Flutter codegen that sits on top of it.

First I tried the HTTP API + OpenAPI route and just didnt like it. You lose the real-time/subscription ergonomics, and the generated surface ends up feeling kind of weird compared to how Convex is meant to be used. So I wrote a Flutter code generator that consumes the generated TS API and produces a typed Dart API that mirrors your Convex functions (args + return types). It also handles things like ids, enums, and nested object types.

Example of what the generated Dart API looks like
 final result = await _convexClient.api.quizSessions.mutations
    .create(locale: locale, quizId: quizId);

return QuizSession.fromConvexCreate(
  result.sessionId,
  hasRated: result.hasRated,
);

for a convex function like this:

export const create = protectedMutation([
  LicenseType.Regular,
  LicenseType.Lifetime,
  LicenseType.Limited,
])({
  args: {
    quizId: v.id("quizzes"),
    locale: validLocales,
  },
  returns: v.object({
    sessionId: v.id("quizSessions"),
    hasRated: v.boolean(),
  }),
});


The other missing piece was a real WebSocket client in Flutter, so I built that too and the codegen targets it. It supports Flutter native and web. On native I’m using convex.rs, and on web I’m using convex/browser. Queries, mutations, subscriptions all work, and auth is handled properly including token refresh on both native and web.

Example of the API:
final result = await _client.rawQuery(
  'preview/imgMatchQueries:getImgMatchData',
  <String, dynamic>{
    'imgMatchTemplateId': imgMatchTemplateId,
    'locale': locale.jsonValue,
  },
);

return PreviewImgMatchQueriesGetImgMatchDataResult.fromJson(
  result as Map<String, dynamic>,
);


Auth handling:
convexClient.client.setAuthErrorHandler((error) async {
  final newToken = await getIt.get<AuthBloc>().refreshToken();
  if (newToken != null) {
    return AuthErrorAction.refreshToken(token: newToken);
  }
  return AuthErrorAction.clearAuth();
});


Its deployed in production for our app and has been stable for us so far.

If people are interested, I can share the repo current status, and whats implemented. Also happy to post examples for subscriptions and some of the more complex type mappings.

Thanks!
Was this page helpful?