art_adk 0.0.1 copy "art_adk: ^0.0.1" to clipboard
art_adk: ^0.0.1 copied to clipboard

A Flutter SDK for ARealtimeTech (ART) — real-time WebSocket messaging, channel subscriptions, presence, encrypted channels, and CRDT shared objects.

example/lib/main.dart

import 'dart:developer';

import 'package:art_adk/art_adk.dart';
import 'package:example/ui_message.dart';
import 'package:flutter/material.dart';

import 'auth/app_auth_config.dart';
import 'auth/auth_service.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Adk? adk;
  bool isLoading = false;
  bool pulse = false;
  bool isSubscribed = false;
  bool isSubscribing = false;
  bool isConnected = false;
  String messageText = '';
  String clientID = '';
  String toUser = 'kiran';
  String channelName = 'Flutter-ADK-TAG';
  String authUsername = 'yuresh';
  BaseSubscription? activeSubscription;
  final List<UIMessage> messages = <UIMessage>[];
  LiveObjSubscription? get liveObjSubscription {
    final subscription = activeSubscription;
    if (subscription is LiveObjSubscription) {
      return subscription;
    }
    return null;
  }
  List<String> interceptors = <String>[];
  String interceptorInput = '';
  Map<String, Offset> userPositions = <String, Offset>{};


  @override
  void initState() {
    super.initState();
  }

  Future<void> connect() async {
    isLoading = true;
    try {
      final clientCreds = await AppConfigLoader.loadCredentials();
      final passcode = await AuthService.connectWithPasscode(
        username: authUsername,
        firstName: authUsername,
        lastName: authUsername,
        credentials: clientCreds,
      );

      var updatedCreds = clientCreds.copyWith(accessToken: passcode);

      final config = AdkConfig(
        uri: 'dev.arealtimetech.com/ws',
        authToken: passcode,
        getCredentials: () => updatedCreds,
      );

      final instance = Adk(adkConfig: config);
      adk = instance;

      instance.on('connection', (dynamic data) {
        isConnected = true;
        pulse = true;
        log('✅ Connected');
      });

      instance.on('close', (dynamic _) {
        log('❌ Connection closed');
        isConnected = false;
        pulse = false;
      });

      await instance.connect();
    } catch (error) {
      log('Connection error: $error');
    } finally {
      isLoading = false;
    }
  }

  Future<void> generateKeyPair() async {
    try {
      await adk?.generateKeyPair();
      log('🔐 Key pair generated');
    } catch (error) {
      log('error generateKeyPair: $error');
    } finally {
    }
  }


  Future<void> subscribe() async {
    final currentAdk = adk;
    if (currentAdk == null) {
      return;
    }

    isSubscribing = true;

    try {
      log('Subscribing to $channelName');
      final sub = await currentAdk.subscribe(channel: channelName);
      activeSubscription = sub;
      isSubscribed = true;
      isSubscribing = false;
      log('✅ Subscribed to $channelName');
      await fetchPresence(sub);

      if (sub is LiveObjSubscription) {
        log('📊 CRDT-enabled subscription');
        await listenCRDT();
      } else {
        log('🔒 Secure/Normal subscription');
      }

      sub.emitter.on('message', (dynamic data) {
        if (data is Map<String, dynamic> && data['message'] is String) {
          messages.add(
            UIMessage(
              id: DateTime.now().microsecondsSinceEpoch.toString(),
              sender: toUser,
              text: data['message'] as String,
              isMe: false,
              timestamp: DateTime.now(),
            ),
          );
        }
      });
    } catch (error) {
      log('Subscribe error: $error');
      isSubscribing = false;
    }
  }

  Future<void> fetchPresence(BaseSubscription sub) async {
    try {
      await sub.fetchPresence(
        callback: (List<String> users) {
          log('👥 Online users: $users');
        },
      );
    } catch (error) {
      log('❌ Presence error: $error');
    }
  }

  Future<void> sendMessage() async {
    if (messageText.isEmpty) {
      return;
    }

    final text = messageText;
    messageText = '';

    await activeSubscription?.push(
      event: 'message',
      data: <String, dynamic>{'message': text},
      options: PushConfig(to: <String>[toUser]),
    );

    messages.add(
      UIMessage(
        id: DateTime.now().microsecondsSinceEpoch.toString(),
        sender: authUsername,
        text: text,
        isMe: true,
        timestamp: DateTime.now(),
      ),
    );
  }

  Future<void> listenCRDT() async {
    final liveSub = liveObjSubscription;
    if (liveSub == null) {
      log('⚠️ CRDT not available - using secure/normal channel');
      return;
    }

    final query = liveSub.query(path: 'user');
    final initial = await query.execute();
    log('📊 Initial CRDT: $initial');

    await query.listen((dynamic data) {
      log('🔄 CRDT update: $data');

      if (data == null) {
        userPositions = <String, Offset>{};
        return;
      }

      if (data is! Map) {
        log('❌ Invalid data format: ${data.runtimeType}');
        return;
      }

      double? extractCoordinate(dynamic value) {
        if (value is num) {
          return value.toDouble();
        }

        if (value is Map) {
          final directValue = value['value'];
          if (directValue is num) {
            return directValue.toDouble();
          }

          final nestedValue = value['index'];
          if (nestedValue is Map) {
            final inner = nestedValue['value'];
            if (inner is num) {
              return inner.toDouble();
            }
          }
        }

        return null;
      }

      final result = <String, Offset>{};

      for (final MapEntry<dynamic, dynamic> entry in data.entries) {
        final username = '${entry.key}';
        final userData = entry.value;

        if (userData is! Map) {
          continue;
        }

        final index = userData['index'];
        if (index is! Map) {
          log('⚠️ Missing index for $username');
          continue;
        }

        final x = extractCoordinate(index['x']);
        final y = extractCoordinate(index['y']);

        if (x != null && y != null) {
          result[username] = Offset(x, y);
          log('✅ Parsed $username: ($x, $y)');
        } else {
          log('⚠️ Missing x or y for $username. index=$index');
        }
      }

      userPositions = result;
      log('📍 Updated ${result.length} user positions');
    });
  }

  Future<void> updateMyPosition({required double x, required double y}) async {
    final liveSub = liveObjSubscription;
    if (liveSub == null) {
      log('⚠️ CRDT not available for this channel type');
      return;
    }

    userPositions = <String, Offset>{
      ...userPositions,
      authUsername: Offset(x, y),
    };

    liveSub.state()['user'][authUsername].set(<String, dynamic>{
      'index': <String, dynamic>{'x': x, 'y': y},
    });
    await liveSub.flush();
    log('✅ Position sent successfully');
  }

  Future<void> addInterceptor() async {
    final name = interceptorInput.trim();
    if (name.isEmpty) {
      return;
    }
    final currentAdk = adk;
    if (currentAdk == null) {
      log('⚠️ Please authenticate first');
      return;
    }

    try {
      await currentAdk.intercept(
        interceptor: name,
        fn:
            (
            Map<String, dynamic> data,
            void Function(dynamic data) resolve,
            void Function(String error) reject,
            ) {
          log("🔀 Intercepted on '$name': $data");
          resolve(data);
        },
      );
      interceptors = <String>[...interceptors, name];
      interceptorInput = '';
      log('✅ Interceptor added: $name');
    } catch (error) {
      log('❌ Failed to add interceptor: $error');
    } finally {
    }
  }

  void removeInterceptor({required int index}) {
    if (index < 0 || index >= interceptors.length) {
      return;
    }
    final removed = interceptors[index];
    interceptors = <String>[
      ...interceptors.take(index),
      ...interceptors.skip(index + 1),
    ];
    log('🗑 Removed interceptor: $removed');
  }

  Future<void> crdtArrayPush(String item) async {
    final liveSub = liveObjSubscription;
    if (liveSub == null) {
      return;
    }
    final count = liveSub.state()['items'].push(item);
    await liveSub.flush();
    log("➕ pushed '$item', count=$count");
  }

  Future<void> crdtArrayPop() async {
    final liveSub = liveObjSubscription;
    if (liveSub == null) {
      return;
    }
    final removed = liveSub.state()['items'].pop();
    await liveSub.flush();
    log('📤 popped: $removed');
  }

  Future<void> setCRDTTitle(String title) async {
    final liveSub = liveObjSubscription;
    if (liveSub == null) {
      log('⚠️ Not a CRDT channel');
      return;
    }
    liveSub.state()['document']['title'].set(title);
    await liveSub.flush();
    log('✏️ title set → $title');
  }

  Future<void> disconnect() async {
    await activeSubscription?.unsubscribe();
    await adk?.disconnect();

    activeSubscription = null;
    isConnected = false;
    isSubscribed = false;
    pulse = false;
    messages.clear();
    userPositions = <String, Offset>{};

    log('Disconnected');
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("ADK Example")),
        body: Center(
          child: Column(crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            mainAxisSize: MainAxisSize.max,
            children: [
              SizedBox(height: 5,),
              ElevatedButton(onPressed: connect, child: Text("Connect to ADK")),
              SizedBox(height: 5,),
              ElevatedButton(onPressed: sendMessage, child: Text("Send Message")),
              SizedBox(height: 5,),
              ElevatedButton(
                  onPressed: () => updateMyPosition(x:100,y: 200),
                  child: Text("Update My Position")),
              SizedBox(height: 5,),
              ElevatedButton(
                  onPressed: () => setCRDTTitle('My Doc'), child: Text("Set Title")),
              SizedBox(height: 5,),
              ElevatedButton(
                  onPressed: () =>crdtArrayPush('Hello ${DateTime.now()}'), child: Text("Array Push")),
              SizedBox(height: 5,),
              ElevatedButton(
                  onPressed: crdtArrayPop, child: Text("Array Pop")),
              SizedBox(height: 5,),
              ElevatedButton(
                  onPressed: disconnect, child: Text("Disconnect")),
            ],
          ),
        ),
      ),
    );
  }
}
4
likes
140
points
261
downloads

Documentation

Documentation
API reference

Publisher

verified publisherarealtimetech.com

Weekly Downloads

A Flutter SDK for ARealtimeTech (ART) — real-time WebSocket messaging, channel subscriptions, presence, encrypted channels, and CRDT shared objects.

Homepage

Topics

#websocket #realtime #messaging #pubsub #flutter-sdk

License

MIT (license)

Dependencies

flutter, http, pinenacl, web_socket_channel

More

Packages that depend on art_adk