Skip to main content

zally_flutter_sdk 1.0.0-beta.1 copy "zally_flutter_sdk: ^1.0.0-beta.1" to clipboard
zally_flutter_sdk: ^1.0.0-beta.1 copied to clipboard

Continuous Authentication for Flutter. Wraps the native zally iOS and Android SDKs behind one Dart API.

example/lib/main.dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:zally_flutter_sdk/zally_flutter_sdk.dart';

/// Supply at run time so nothing secret is committed:
///
/// ```
/// flutter run --dart-define=ZALLY_APP_UUID=... --dart-define=ZALLY_TOKEN_URL=https://...
/// ```
///
/// ZALLY_TOKEN_URL is fetched with GET each time the SDK asks for a token. The
/// response body is the JWT, or JSON with a top-level `token` field.
const String _defaultAppUuid = String.fromEnvironment('ZALLY_APP_UUID');
const String _defaultTokenUrl = String.fromEnvironment('ZALLY_TOKEN_URL');

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'zally Flutter SDK example',
      theme: ThemeData(
        colorSchemeSeed: const Color(0xFF1F3A5F),
        useMaterial3: true,
      ),
      home: const ZallyExampleScreen(),
    );
  }
}

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

  @override
  State<ZallyExampleScreen> createState() => _ZallyExampleScreenState();
}

class _ZallyExampleScreenState extends State<ZallyExampleScreen> {
  final ZallySdk _zally = ZallySdk.instance;
  final TextEditingController _appUuid = TextEditingController(
    text: _defaultAppUuid,
  );
  final TextEditingController _tokenUrl = TextEditingController(
    text: _defaultTokenUrl,
  );
  final List<String> _log = <String>[];

  StreamSubscription<UserState>? _stateSub;
  UserState? _state;
  bool _busy = false;
  int _tokenRequests = 0;

  @override
  void initState() {
    super.initState();
    _stateSub = _zally.userStateStream.listen(
      (UserState s) => setState(() => _state = s),
      onError: (Object e) => _append('userStateStream error: $e'),
    );
  }

  @override
  void dispose() {
    _stateSub?.cancel();
    _appUuid.dispose();
    _tokenUrl.dispose();
    super.dispose();
  }

  void _append(String line) {
    final now = DateTime.now();
    final stamp =
        '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}';
    setState(() => _log.insert(0, '$stamp  $line'));
  }

  /// The client token provider. The SDK calls this on initialize and again
  /// whenever its refresh token expires. Each call is logged so the
  /// re-invocation is visible on screen.
  Future<String?> _getClientToken() async {
    _tokenRequests++;
    _append('requestClientToken #$_tokenRequests');
    final url = _tokenUrl.text.trim();
    if (url.isEmpty) {
      _append('  no token URL set, answering null');
      return null;
    }
    final client = HttpClient();
    try {
      final request = await client.getUrl(Uri.parse(url));
      final response = await request.close();
      final body = await response.transform(utf8.decoder).join();
      if (response.statusCode != 200) {
        _append('  token endpoint returned ${response.statusCode}');
        return null;
      }
      final trimmed = body.trim();
      if (trimmed.startsWith('{')) {
        final decoded = jsonDecode(trimmed);
        final token = decoded is Map ? decoded['token'] as String? : null;
        _append(
          token == null ? '  JSON body has no token field' : '  token received',
        );
        return token;
      }
      _append('  token received');
      return trimmed;
    } finally {
      client.close();
    }
  }

  Future<void> _run(String name, Future<Object?> Function() action) async {
    setState(() => _busy = true);
    _append('$name()');
    try {
      final result = await action();
      _append('  ok${result == null ? '' : ': $result'}');
    } on ZallyException catch (e) {
      _append(
        '  ${e.code}${e.authReason == null ? '' : ' ${e.authReason!.name}'}: ${e.message}',
      );
    } catch (e) {
      _append('  unexpected: $e');
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    final state = _state;
    return Scaffold(
      appBar: AppBar(title: const Text('zally Flutter SDK example')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            TextField(
              controller: _appUuid,
              decoration: const InputDecoration(labelText: 'App UUID'),
            ),
            const SizedBox(height: 8),
            TextField(
              controller: _tokenUrl,
              decoration: const InputDecoration(
                labelText: 'Token URL (GET, returns JWT)',
              ),
            ),
            const SizedBox(height: 12),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: <Widget>[
                FilledButton(
                  onPressed: _busy
                      ? null
                      : () => _run(
                          'initialize',
                          () => _zally.initialize(
                            appUUID: _appUuid.text,
                            getClientToken: _getClientToken,
                          ),
                        ),
                  child: const Text('Initialize'),
                ),
                FilledButton.tonal(
                  onPressed: _busy ? null : () => _run('start', _zally.start),
                  child: const Text('Start'),
                ),
                FilledButton.tonal(
                  onPressed: _busy ? null : () => _run('stop', _zally.stop),
                  child: const Text('Stop'),
                ),
                OutlinedButton(
                  onPressed: _busy
                      ? null
                      : () => _run('isInitialized / isRunning', () async {
                          final init = await _zally.isInitialized();
                          final running = await _zally.isRunning();
                          return 'initialized=$init running=$running';
                        }),
                  child: const Text('Query'),
                ),
                OutlinedButton(
                  onPressed: _busy
                      ? null
                      : () => _run('getUserState', _zally.getUserState),
                  child: const Text('Refresh state'),
                ),
              ],
            ),
            const SizedBox(height: 12),
            Card(
              child: Padding(
                padding: const EdgeInsets.all(12),
                child: state == null
                    ? const Text('No user state received yet')
                    : Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          Text('training: ${state.trainingStateRaw}'),
                          Text('zId: ${state.zIdStatusRaw}'),
                          Text(
                            'isUser: ${state.isUser}   isImposter: ${state.isImposter}',
                          ),
                          Text(
                            'isOutOfTraining: ${state.isOutOfTraining}   isInactive: ${state.isInactive}',
                          ),
                        ],
                      ),
              ),
            ),
            const SizedBox(height: 12),
            Expanded(
              child: DecoratedBox(
                decoration: BoxDecoration(
                  border: Border.all(color: Theme.of(context).dividerColor),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: ListView.builder(
                  padding: const EdgeInsets.all(8),
                  itemCount: _log.length,
                  itemBuilder: (BuildContext context, int i) => Text(
                    _log[i],
                    style: const TextStyle(
                      fontFamily: 'monospace',
                      fontSize: 12,
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
1
likes
0
points
373
downloads

Documentation

Documentation

Publisher

verified publisherzally.com

Weekly Downloads

Continuous Authentication for Flutter. Wraps the native zally iOS and Android SDKs behind one Dart API.

Repository (GitHub)
View/report issues

Topics

#authentication #biometrics #security

License

unknown (license)

Dependencies

flutter

More

Packages that depend on zally_flutter_sdk

Packages that implement zally_flutter_sdk