Skip to main content

zally_flutter_sdk 1.2.0 copy "zally_flutter_sdk: ^1.2.0" to clipboard
zally_flutter_sdk: ^1.2.0 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
160
points
353
downloads

Documentation

Documentation
API reference

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

Apache-2.0 (license)

Dependencies

flutter

More

Packages that depend on zally_flutter_sdk

Packages that implement zally_flutter_sdk