Skip to Content
Auth-Aware Client

Auth-Aware Kalam Client for Flutter

For a new Flutter app, prefer kalam_sync and KalamScope. That handles lifecycle pause/resume and an account-scoped cache without a custom controller.

Use this page when you stay on kalam_link and need the client to follow login, token refresh, and logout.

Lifecycle

  1. Call KalamClient.init() once before runApp().
  2. Connect after auth is ready — not before the first frame.
  3. Start live queries only after you know which user/session to subscribe.
  4. Dispose the client on logout.

Do not await KalamClient.connect() in main(). Even with lazy WebSocket connect, auth work can delay first render.

Riverpod example

DART
import 'dart:async'; import 'package:flutter/widgets.dart';import 'package:flutter_riverpod/flutter_riverpod.dart';import 'package:kalam_link/kalam_link.dart'; Future<void> main() async {  WidgetsFlutterBinding.ensureInitialized();  await KalamClient.init();  runApp(const ProviderScope(child: MyApp()));} class AppUser {  const AppUser({required this.id});  final String id;} final authTokenProvider = StreamProvider<String?>((ref) {  return authRepository.idTokenChanges();}); final appUserProvider = StreamProvider<AppUser?>((ref) {  return userRepository.sessionChanges();}); final kalamControllerProvider = Provider<KalamController>((ref) {  final controller = KalamController(url: 'https://db.example.com');  ref.onDispose(controller.dispose);  return controller;}); final kalamLifecycleProvider = Provider<void>((ref) {  final controller = ref.watch(kalamControllerProvider);   ref.listen<AsyncValue<String?>>(authTokenProvider, (_, next) {    unawaited(controller.syncAuth(next.valueOrNull));  });   ref.listen<AsyncValue<AppUser?>>(appUserProvider, (_, next) {    final user = next.valueOrNull;    if (user == null) {      unawaited(controller.stopRealtime());      return;    }    unawaited(controller.ensureRealtimeReady(userId: user.id));  });}); class KalamController {  KalamController({required this.url});   final String url;  KalamClient? _client;  String? _currentToken;  String? _subscribedUserId;  StreamSubscription<List<Map<String, KalamCellValue>>>? _messagesSubscription;   Future<void> syncAuth(String? token) async {    if (token == _currentToken) return;    _currentToken = token;     if (token == null) {      _subscribedUserId = null;      await _messagesSubscription?.cancel();      _messagesSubscription = null;      await _client?.dispose();      _client = null;      return;    }     if (_client == null) {      _client = await KalamClient.connect(        url: url,        authProvider: () async => Auth.jwt(_currentToken!),      );      return;    }     await _client!.refreshAuth();  }   Future<void> ensureRealtimeReady({required String userId}) async {    final client = _client;    if (client == null || _subscribedUserId == userId) return;     await _messagesSubscription?.cancel();    _subscribedUserId = userId;    _messagesSubscription = client        .liveTable<Map<String, KalamCellValue>>('messages')        .listen((rows) => debugPrint('rows: ${rows.length}'));  }   Future<void> stopRealtime() async {    _subscribedUserId = null;    await _messagesSubscription?.cancel();    _messagesSubscription = null;  }   Future<void> dispose() async {    await _messagesSubscription?.cancel();    await _client?.dispose();  }}
DART
class MyApp extends ConsumerWidget {  const MyApp({super.key});   @override  Widget build(BuildContext context, WidgetRef ref) {    ref.watch(kalamLifecycleProvider);    return const Directionality(      textDirection: TextDirection.ltr,      child: Placeholder(),    );  }}

On logout, dispose the client. On token rotation, keep the same client and call refreshAuth().

See Authentication, Realtime Subscriptions, and Client Lifecycle.

Last updated on