hawcx_flutter_sdk 1.0.3 copy "hawcx_flutter_sdk: ^1.0.3" to clipboard
hawcx_flutter_sdk: ^1.0.3 copied to clipboard

Hawcx Flutter SDK plugin providing V5 auth, session, and push flows via native Hawcx SDKs.

example/lib/main.dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:hawcx_flutter_sdk/hawcx_flutter_sdk.dart';

import 'hawcx_config.dart';

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

class HawcxExampleApp extends StatelessWidget {
  const HawcxExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Hawcx Flutter Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        useMaterial3: true,
      ),
      home: const HawcxExampleHome(),
    );
  }
}

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

  @override
  State<HawcxExampleHome> createState() => _HawcxExampleHomeState();
}

class _HawcxExampleHomeState extends State<HawcxExampleHome> {
  final HawcxClient _client = HawcxClient();

  final TextEditingController _userIdController =
      TextEditingController(text: 'user@example.com');
  final TextEditingController _otpController = TextEditingController();

  StreamSubscription<HawcxEvent>? _eventsSub;

  bool _initialized = false;
  bool _authInProgress = false;
  bool _otpRequired = false;
  String? _initError;
  String? _statusMessage;
  bool _statusIsError = false;
  HawcxAuthHandle? _authHandle;
  bool _suppressCancelMessage = false;
  final List<String> _logs = <String>[];

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

    if (!kIsWeb &&
        (defaultTargetPlatform == TargetPlatform.android ||
            defaultTargetPlatform == TargetPlatform.iOS)) {
      _eventsSub = _client.events.listen(
        _onEvent,
        onError: (Object error, StackTrace stack) {
          _appendLog('Event stream error: $error');
        },
      );
      if (defaultHawcxConfig == null) {
        _initError =
            'Missing Hawcx config. Update example/lib/hawcx_config.dart.';
        _appendLog(_initError!);
        _setStatus(_initError!, isError: true);
      } else {
        _initialize();
      }
    } else {
      _appendLog(
        'This example is intended for iOS/Android. Current platform: '
        '${kIsWeb ? 'web' : defaultTargetPlatform}',
      );
    }
  }

  @override
  void dispose() {
    _eventsSub?.cancel();
    _userIdController.dispose();
    _otpController.dispose();
    super.dispose();
  }

  void _appendLog(String line) {
    setState(() {
      final timestamp = DateTime.now().toIso8601String();
      _logs.insert(0, '[$timestamp] $line');
    });
  }

  void _onEvent(HawcxEvent event) {
    _appendLog(_describeEvent(event));

    switch (event) {
      case AuthOtpRequiredEvent():
        setState(() => _otpRequired = true);
        _setStatus('OTP required', isError: false);
        break;
      case AuthSuccessEvent():
        _setStatus('Authentication complete (tokens stored by SDK).',
            isError: false);
        _endAuthFlow();
        break;
      case AuthErrorEvent(:final payload):
        _setStatus('Auth error: ${payload.message}', isError: true);
        _endAuthFlow();
        break;
      case AuthorizationCodeEvent():
        _setStatus(
          'Authentication complete. Authorization code received — exchange via backend.',
          isError: false,
        );
        _cancelAuthFlow(suppressMessage: true);
        break;
      case AdditionalVerificationRequiredEvent(:final payload):
        _setStatus(
          'Additional verification required: ${payload.sessionId}',
          isError: true,
        );
        _cancelAuthFlow(suppressMessage: true);
        break;
      default:
        break;
    }
  }

  String _describeEvent(HawcxEvent event) {
    switch (event) {
      case AuthOtpRequiredEvent():
        return 'Auth: otp_required';
      case AuthSuccessEvent(:final payload):
        return 'Auth: auth_success isLoginFlow=${payload.isLoginFlow} '
            'accessToken=${payload.accessToken != null ? '<present>' : '<null>'} '
            'refreshToken=${payload.refreshToken != null ? '<present>' : '<null>'}';
      case AuthErrorEvent(:final payload):
        return 'Auth: auth_error code=${payload.code} message=${payload.message}';
      case AuthorizationCodeEvent(:final payload):
        return 'Auth: authorization_code code=${payload.code} expiresIn=${payload.expiresIn}';
      case AdditionalVerificationRequiredEvent(:final payload):
        return 'Auth: additional_verification_required sessionId=${payload.sessionId} detail=${payload.detail}';
      case SessionSuccessEvent():
        return 'Session: session_success';
      case SessionErrorEvent(:final payload):
        return 'Session: session_error code=${payload.code} message=${payload.message}';
      case PushLoginRequestEvent(:final payload):
        return 'Push: push_login_request requestId=${payload.requestId}';
      case PushErrorEvent(:final payload):
        return 'Push: push_error code=${payload.code} message=${payload.message}';
      case HawcxUnknownEvent(:final type):
        return 'Unknown: $type';
    }
  }

  Future<void> _runGuarded(Future<void> Function() operation) async {
    try {
      await operation();
    } catch (error) {
      _appendLog('Error: $error');
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(error.toString())),
      );
    }
  }

  Future<void> _initialize() {
    return _runGuarded(() async {
      final config = defaultHawcxConfig;
      if (config == null) {
        _initError = 'Missing Hawcx config.';
        _appendLog(_initError!);
        _setStatus(_initError!, isError: true);
        return;
      }
      await _client.initialize(config);
      setState(() => _initialized = true);
      _appendLog('Initialized');
      _setStatus('Initialized', isError: false);
    });
  }

  Future<void> _authenticate() {
    return _runGuarded(() async {
      if (_authInProgress) {
        _appendLog('Auth already in progress.');
        _setStatus('Auth already in progress.', isError: true);
        return;
      }
      if (_initError != null) {
        _appendLog(_initError!);
        _setStatus(_initError!, isError: true);
        return;
      }
      if (!_initialized) {
        await _initialize();
      }
      final userId = _userIdController.text;
      setState(() {
        _authInProgress = true;
        _otpRequired = false;
        _statusMessage = 'Authenticating...';
        _statusIsError = false;
      });
      _otpController.clear();
      if (_authHandle != null) {
        _cancelAuthFlow(suppressMessage: true);
      }
      _authHandle = _client.authenticate(
        userId: userId,
        onOtpRequired: () {
          _appendLog('Auth: OTP required');
          setState(() => _otpRequired = true);
          _setStatus('OTP required', isError: false);
        },
        onAuthorizationCode: (payload) {
          _appendLog('Auth: Authorization code received (${payload.code})');
          _setStatus(
            'Authentication complete. Authorization code received — exchange via backend.',
            isError: false,
          );
        },
        onAdditionalVerificationRequired: (payload) {
          _appendLog(
              'Auth: Additional verification required (${payload.sessionId})');
          _setStatus(
            'Additional verification required: ${payload.sessionId}',
            isError: true,
          );
        },
      );
      _watchAuthHandle(_authHandle!);
      _appendLog('Auth: started for userId=$userId');
    });
  }

  Future<void> _submitOtp() {
    return _runGuarded(() async {
      await _client.submitOtp(_otpController.text);
      _appendLog('Auth: submitOtp called');
      _setStatus('OTP submitted. Awaiting verification...', isError: false);
    });
  }

  void _endAuthFlow() {
    setState(() {
      _authInProgress = false;
      _otpRequired = false;
    });
    _authHandle = null;
    _suppressCancelMessage = false;
  }

  void _cancelAuthFlow({required bool suppressMessage}) {
    _suppressCancelMessage = suppressMessage;
    _authHandle?.cancel();
    _authHandle = null;
    setState(() {
      _authInProgress = false;
      _otpRequired = false;
    });
  }

  void _watchAuthHandle(HawcxAuthHandle handle) {
    handle.future.then<void>((_) {}, onError: (Object error, StackTrace stack) {
      if (error is HawcxAuthException &&
          error.code == HawcxAuthException.cancelledCode) {
        if (_suppressCancelMessage) {
          _suppressCancelMessage = false;
          return;
        }
        _appendLog('Auth cancelled.');
        _setStatus('Authentication cancelled.', isError: false);
        _endAuthFlow();
        return;
      }

      _appendLog('Auth error: $error');
      _setStatus('Auth error: $error', isError: true);
      _endAuthFlow();
    });
  }

  void _setStatus(String message, {required bool isError}) {
    setState(() {
      _statusMessage = message;
      _statusIsError = isError;
    });
  }

  @override
  Widget build(BuildContext context) {
    final isMobile = !kIsWeb &&
        (defaultTargetPlatform == TargetPlatform.android ||
            defaultTargetPlatform == TargetPlatform.iOS);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Hawcx Flutter Example'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        actions: [
          IconButton(
            tooltip: 'Clear log',
            onPressed: () => setState(_logs.clear),
            icon: const Icon(Icons.delete_outline),
          ),
        ],
      ),
      body: SafeArea(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              if (!isMobile)
                Card(
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Text(
                      'This example is intended for iOS/Android.\n'
                      'Current platform: ${kIsWeb ? 'web' : defaultTargetPlatform}',
                    ),
                  ),
                ),
              _section(
                title: 'Authenticate',
                children: [
                  TextField(
                    controller: _userIdController,
                    decoration: const InputDecoration(
                      labelText: 'User ID',
                      hintText: 'user@example.com',
                    ),
                    autocorrect: false,
                  ),
                  const SizedBox(height: 12),
                  ElevatedButton(
                    onPressed: isMobile && _initialized && !_authInProgress
                        ? _authenticate
                        : null,
                    child: const Text('Authenticate'),
                  ),
                  if (_statusMessage != null) ...[
                    const SizedBox(height: 12),
                    Text(
                      _statusMessage!,
                      style: TextStyle(
                        color: _statusIsError
                            ? Theme.of(context).colorScheme.error
                            : Theme.of(context).colorScheme.primary,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ],
                  if (_otpRequired) ...[
                    const SizedBox(height: 12),
                    TextField(
                      controller: _otpController,
                      decoration: const InputDecoration(
                        labelText: 'OTP',
                        hintText: '123456',
                      ),
                      autofillHints: const [AutofillHints.oneTimeCode],
                      keyboardType: TextInputType.number,
                    ),
                    const SizedBox(height: 12),
                    OutlinedButton(
                      onPressed: isMobile && _initialized ? _submitOtp : null,
                      child: const Text('Submit OTP'),
                    ),
                  ],
                ],
              ),
              _section(
                title: 'Log',
                children: [
                  Container(
                    padding: const EdgeInsets.all(12),
                    decoration: BoxDecoration(
                      border: Border.all(color: Theme.of(context).dividerColor),
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: _logs.isEmpty
                        ? const Text('No events yet.')
                        : SelectableText(_logs.take(80).join('\n\n')),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _section({required String title, required List<Widget> children}) {
    return Card(
      margin: const EdgeInsets.only(bottom: 16),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(title, style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 12),
            ...children,
          ],
        ),
      ),
    );
  }
}
0
likes
140
points
271
downloads

Publisher

unverified uploader

Weekly Downloads

Hawcx Flutter SDK plugin providing V5 auth, session, and push flows via native Hawcx SDKs.

Homepage
Repository (GitHub)

Documentation

API reference

License

unknown (license)

Dependencies

flutter, http, plugin_platform_interface

More

Packages that depend on hawcx_flutter_sdk

Packages that implement hawcx_flutter_sdk