art_adk 1.0.0 copy "art_adk: ^1.0.0" to clipboard
art_adk: ^1.0.0 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:convert';
import 'package:art_adk/art_adk.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.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;
  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 {
    try {
      final clientCreds = await AppConfigLoader.loadCredentials();
      final passcode = await AuthService.connectWithPasscode(
        username: "auth_user_name",
        firstName: "auth_user_first_name",
        lastName: "auth_user_last_name",
        credentials: clientCreds,
      );

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

      final config = AdkConfig(
        uri: 'your_server_websocket_uri',
        authToken: passcode,
        getCredentials: () => updatedCreds,
      );

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

      instance.on('connection', (dynamic data) {
      });

      instance.on('close', (dynamic _) {
      });

      await instance.connect();
    } catch (error) {
      debugPrint("connection Error $error");
    }
  }

  Future<void> generateKeyPair() async {
    try {
      await adk?.generateKeyPair();
    } catch (error) {
      debugPrint("generateKeyPair Error $error");

    }
  }

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


    try {
      final sub = await currentAdk.subscribe(channel: 'channel_name');
      activeSubscription = sub;
      await fetchPresence(sub);

      if (sub is LiveObjSubscription) {
        await listenCRDT();
      } else {
      }
      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: "to_user-name",
              text: data['message'] as String,
              isMe: false,
              timestamp: DateTime.now(),
            ),
          );
        }
      });
    } catch (error) {
      debugPrint("subscribe error $error");
    }
  }

  Future<void> fetchPresence(BaseSubscription sub) async {
    try {
      await sub.fetchPresence(

        callback: (List<String> users) {
          debugPrint('👥 Online users: $users');
        },
      );

    } catch (error) {
      debugPrint('❌ Presence error: $error');
    }
  }

  Future<void> sendMessage() async {
    await activeSubscription?.push(
      event: 'message',
      data: <String, dynamic>{'message': "your message"},
      options: PushConfig(to: <String>["to_user-name"]),
    );

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

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

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

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

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

      if (data is! Map) {
        debugPrint('❌ 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) {
          debugPrint('⚠️ 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);
          debugPrint(' Parsed $username: ($x, $y)');
        } else {
          debugPrint('⚠️ Missing x or y for $username. index=$index');
        }
      }

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

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

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

    liveSub.state()['user']["auth_user_name"].set(<String, dynamic>{
      'index': <String, dynamic>{'x': x, 'y': y},
    });
    await liveSub.flush();
  }

  Future<void> addInterceptor() async {
    final name = interceptorInput.trim();
    if (name.isEmpty) {
      return;
    }
    final currentAdk = adk;
    if (currentAdk == null) {
      debugPrint('⚠️ 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,
            ) {
              debugPrint("🔀 Intercepted on '$name': $data");
          resolve(data);
        },
      );
      interceptors = <String>[...interceptors, name];
      interceptorInput = '';
      debugPrint('✅ Interceptor added: $name');
    } catch (error) {
      debugPrint('❌ 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),
    ];
    debugPrint('🗑 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();
    debugPrint("➕ pushed '$item', count=$count");
  }

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

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

  Future<void> disconnect() async {
    await activeSubscription?.unsubscribe();
    await adk?.disconnect();
    activeSubscription = null;
    messages.clear();
    userPositions = <String, Offset>{};
    debugPrint('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")),
            ],
          ),
        ),
      ),
    );
  }
}

class ADKServicesFile {
  ADKServicesFile({
    required this.environment,
    required this.projectKey,
    required this.orgTitle,
    required this.clientID,
    required this.clientSecret,
  });

  factory ADKServicesFile.fromJson(Map<String, dynamic> json) {
    return ADKServicesFile(
      environment: json['Environment'] as String? ?? '',
      projectKey: json['ProjectKey'] as String? ?? '',
      orgTitle: json['Org-Title'] as String? ?? '',
      clientID: json['Client-ID'] as String? ?? '',
      clientSecret: json['Client-Secret'] as String? ?? '',
    );
  }

  final String environment;
  final String projectKey;
  final String orgTitle;
  final String clientID;
  final String clientSecret;
}

class AppConfigLoader {
  static Future<CredentialStore> loadCredentials() async {
    final data = await rootBundle.loadString('assets/adk-services.json');
    final file = ADKServicesFile.fromJson(
      jsonDecode(data) as Map<String, dynamic>,
    );

    return CredentialStore(
      environment: file.environment,
      projectKey: file.projectKey,
      orgTitle: file.orgTitle,
      clientID: file.clientID,
      clientSecret: file.clientSecret,
    );
  }
}

class PasscodeRequest {
  PasscodeRequest({
    required this.username,
    required this.firstName,
    required this.lastName,
  });

  final String username;
  final String firstName;
  final String lastName;

  Map<String, dynamic> toJson() {
    return <String, dynamic>{
      'username': username,
      'first_name': firstName,
      'last_name': lastName,
    };
  }
}

class AuthService {
  static Future<String> connectWithPasscode({
    required String username,
    required String firstName,
    required String lastName,
    required CredentialStore credentials,
  }) async {
    final uri = Uri.parse(
      'https://dev.arealtimetech.com/ws/v1/connect/passcode',
    );

    final response = await post(
      uri,
      headers: <String, String>{
        'Client-Id': credentials.clientID,
        'Client-Secret': credentials.clientSecret,
        'ProjectKey': credentials.projectKey,
        'Environment': credentials.environment,
        'X-Org': credentials.orgTitle,
        'Content-Type': 'application/json',
      },
      body: jsonEncode(
        PasscodeRequest(
          username: username,
          firstName: firstName,
          lastName: lastName,
        ).toJson(),
      ),
    );

    final responseBody = response.body;
    debugPrint('Passcode Response: $responseBody');
    if (response.statusCode < 200 || response.statusCode >= 300) {
      throw Exception(
        'Passcode request failed (${response.statusCode}): $responseBody',
      );
    }

    final decoded = jsonDecode(responseBody) as Map<String, dynamic>;
    final nestedData = decoded['data'];

    final passcode =
        (nestedData is Map<String, dynamic> ? nestedData['passcode'] : null) ??
            decoded['passcode'];

    if (passcode is! String || passcode.isEmpty) {
      throw Exception('Unable to parse passcode from response');
    }

    return passcode;
  }
}

class UIMessage {
  UIMessage({
    required this.id,
    required this.sender,
    required this.text,
    required this.isMe,
    required this.timestamp,
  });

  final String id;
  final String sender;
  final String text;
  final bool isMe;
  final DateTime timestamp;
}
4
likes
0
points
261
downloads

Documentation

Documentation

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
Repository (GitHub)
View/report issues

Topics

#websocket #realtime #messaging #pubsub #flutter

License

unknown (license)

Dependencies

flutter, http, pinenacl, web_socket_channel

More

Packages that depend on art_adk