Flutter SDK

The Akedly Flutter SDK provides a streamlined Dart interface for OTP authentication in Flutter applications across all platforms. With automatic transaction management and robust error handling, it's the fastest way to add authentication to your Flutter app.

Installation

Add the Akedly package to your pubspec.yaml file and install dependencies.

Installation

YAML
pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  akedly: ^0.0.3

Install

BASH
flutter pub get

Requirements

  • Flutter 2.0 or higher
  • Dart 2.12 or higher
  • Akedly account with API key and pipeline ID

Quick Start

Initialize the Akedly client with your credentials and use the simplified two-method API.

Setup

DART
lib/services/auth.dart
import 'package:akedly/akedly.dart';

final akedlyClient = AkedlyClient(
  apiKey: 'your_api_key_here',
  pipelineId: 'your_pipeline_id_here',
);

Authentication Flow

The Flutter SDK provides an even simpler interface with automatic transaction management:

Send OTP: Creates, activates transaction and sends OTP in one call Verify OTP: Verifies the user's input using the returned verification ID

Authentication Flow

DART
class AuthService {
  final AkedlyClient _akedly = AkedlyClient(
    apiKey: 'your_api_key_here',
    pipelineId: 'your_pipeline_id_here',
  );

  Future<String?> sendOTP(String phoneNumber) async {
    try {
      // Sends OTP and returns verification ID
      final verificationId = await _akedly.sendOTP(phoneNumber);
      return verificationId;
    } catch (e) {
      print('Failed to send OTP: $e');
      return null;
    }
  }

  Future<bool> verifyOTP(String verificationId, String otp) async {
    try {
      final isValid = await _akedly.verifyOTP(verificationId, otp);
      return isValid;
    } catch (e) {
      print('OTP verification failed: $e');
      return false;
    }
  }
}

Complete Widget Example

Flutter Widget

DART
lib/widgets/otp_auth_widget.dart
import 'package:flutter/material.dart';
import 'package:akedly/akedly.dart';

class OTPAuthWidget extends StatefulWidget {
  final Function(bool) onAuthenticationComplete;

  const OTPAuthWidget({
    Key? key,
    required this.onAuthenticationComplete,
  }) : super(key: key);

  @override
  _OTPAuthWidgetState createState() => _OTPAuthWidgetState();
}

class _OTPAuthWidgetState extends State<OTPAuthWidget> {
  final _phoneController = TextEditingController();
  final _otpController = TextEditingController();
  final _formKey = GlobalKey<FormState>();
  
  late final AkedlyClient _akedly;
  
  String? _verificationId;
  bool _isLoading = false;
  bool _otpSent = false;
  String? _errorMessage;
  int _resendCountdown = 0;

  @override
  void initState() {
    super.initState();
    _akedly = AkedlyClient(
      apiKey: 'your_api_key_here',
      pipelineId: 'your_pipeline_id_here',
    );
  }

  @override
  void dispose() {
    _phoneController.dispose();
    _otpController.dispose();
    super.dispose();
  }

  Future<void> _sendOTP() async {
    if (!_formKey.currentState!.validate()) return;
    
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });
    
    try {
      final verificationId = await _akedly.sendOTP(_phoneController.text.trim());
      
      if (verificationId != null) {
        setState(() {
          _verificationId = verificationId;
          _otpSent = true;
          _resendCountdown = 60;
        });
        
        _startResendCountdown();
        
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(
              content: Text('OTP sent successfully!'),
              backgroundColor: Colors.green,
            ),
          );
        }
      } else {
        setState(() {
          _errorMessage = 'Failed to send OTP. Please try again.';
        });
      }
    } catch (e) {
      setState(() {
        _errorMessage = 'Error: ${e.toString()}';
      });
    } finally {
      setState(() => _isLoading = false);
    }
  }

  Future<void> _verifyOTP() async {
    if (_verificationId == null || _otpController.text.trim().length < 6) return;
    
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });
    
    try {
      final isValid = await _akedly.verifyOTP(
        _verificationId!, 
        _otpController.text.trim(),
      );
      
      if (isValid) {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(
              content: Text('Authentication successful!'),
              backgroundColor: Colors.green,
            ),
          );
        }
        widget.onAuthenticationComplete(true);
      } else {
        setState(() {
          _errorMessage = 'Invalid OTP. Please try again.';
          _otpController.clear();
        });
      }
    } catch (e) {
      setState(() {
        _errorMessage = 'Verification failed: ${e.toString()}';
      });
    } finally {
      setState(() => _isLoading = false);
    }
  }

  void _startResendCountdown() {
    Future.delayed(const Duration(seconds: 1), () {
      if (mounted && _resendCountdown > 0) {
        setState(() => _resendCountdown--);
        _startResendCountdown();
      }
    });
  }

  void _resetForm() {
    setState(() {
      _otpSent = false;
      _verificationId = null;
      _errorMessage = null;
      _resendCountdown = 0;
    });
    _otpController.clear();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Form(
        key: _formKey,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            if (!_otpSent) ...[
              // Phone number input screen
              Text(
                'Enter your phone number',
                style: Theme.of(context).textTheme.headlineSmall,
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 24),
              TextFormField(
                controller: _phoneController,
                decoration: const InputDecoration(
                  labelText: 'Phone Number',
                  hintText: '+1234567890',
                  prefixIcon: Icon(Icons.phone),
                  border: OutlineInputBorder(),
                ),
                keyboardType: TextInputType.phone,
                validator: (value) {
                  if (value == null || value.trim().isEmpty) {
                    return 'Please enter your phone number';
                  }
                  if (!value.trim().startsWith('+')) {
                    return 'Phone number must include country code (e.g., +1234567890)';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 24),
              ElevatedButton(
                onPressed: _isLoading ? null : _sendOTP,
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                ),
                child: _isLoading 
                  ? const SizedBox(
                      height: 20,
                      width: 20,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                  : const Text('Send OTP', style: TextStyle(fontSize: 16)),
              ),
            ] else ...[
              // OTP verification screen
              Text(
                'Enter verification code',
                style: Theme.of(context).textTheme.headlineSmall,
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 8),
              Text(
                'Code sent to ${_phoneController.text}',
                style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                  color: Colors.grey[600],
                ),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 24),
              TextFormField(
                controller: _otpController,
                decoration: const InputDecoration(
                  labelText: 'Enter OTP',
                  hintText: '123456',
                  prefixIcon: Icon(Icons.security),
                  border: OutlineInputBorder(),
                ),
                keyboardType: TextInputType.number,
                textAlign: TextAlign.center,
                style: const TextStyle(
                  fontSize: 18,
                  letterSpacing: 4,
                  fontWeight: FontWeight.bold,
                ),
                maxLength: 6,
                onChanged: (value) {
                  if (value.length == 6) {
                    _verifyOTP();
                  }
                },
              ),
              const SizedBox(height: 24),
              ElevatedButton(
                onPressed: _isLoading || _otpController.text.length < 6 
                  ? null 
                  : _verifyOTP,
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                ),
                child: _isLoading 
                  ? const SizedBox(
                      height: 20,
                      width: 20,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                  : const Text('Verify OTP', style: TextStyle(fontSize: 16)),
              ),
              const SizedBox(height: 16),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  TextButton(
                    onPressed: _resetForm,
                    child: const Text('Change Phone Number'),
                  ),
                  TextButton(
                    onPressed: _resendCountdown > 0 ? null : _sendOTP,
                    child: Text(
                      _resendCountdown > 0 
                        ? 'Resend in ${_resendCountdown}s'
                        : 'Resend OTP',
                    ),
                  ),
                ],
              ),
            ],
            
            // Error message display
            if (_errorMessage != null) ...[
              const SizedBox(height: 16),
              Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Colors.red[50],
                  border: Border.all(color: Colors.red[200]!),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Row(
                  children: [
                    Icon(Icons.error_outline, color: Colors.red[700]),
                    const SizedBox(width: 8),
                    Expanded(
                      child: Text(
                        _errorMessage!,
                        style: TextStyle(color: Colors.red[700]),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }
}

Usage Example

Usage

DART
lib/screens/auth_screen.dart
import 'package:flutter/material.dart';
import 'package:your_app/widgets/otp_auth_widget.dart';

class AuthScreen extends StatelessWidget {
  const AuthScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Phone Verification'),
        centerTitle: true,
      ),
      body: OTPAuthWidget(
        onAuthenticationComplete: (success) {
          if (success) {
            // Navigate to authenticated screen
            Navigator.of(context).pushReplacementNamed('/home');
          }
        },
      ),
    );
  }
}

API Methods

  • Name
    AkedlyClient(apiKey, pipelineId)
    Type
    constructor
    Description

    Initialize the client with your Akedly credentials from the dashboard

  • Name
    sendOTP(phoneNumber)
    Type
    Future<String?>
    Description

    Send OTP to phone number. Returns verification ID for the next step, or null if failed

  • Name
    verifyOTP(verificationId, otp)
    Type
    Future<bool>
    Description

    Verify the OTP using verification ID and user input. Returns true if valid, false otherwise

Error Handling

The SDK handles all common errors automatically, but you should still implement proper error handling:

Error Handling

DART
class AuthService {
  final AkedlyClient _akedly = AkedlyClient(
    apiKey: 'your_api_key_here',
    pipelineId: 'your_pipeline_id_here',
  );

  Future<AuthResult> sendOTP(String phoneNumber) async {
    try {
      final verificationId = await _akedly.sendOTP(phoneNumber);
      
      if (verificationId != null) {
        return AuthResult.success(data: verificationId);
      } else {
        return AuthResult.failure(message: 'Failed to send OTP');
      }
    } on AkedlyException catch (e) {
      // Handle specific Akedly errors
      switch (e.code) {
        case 'RATE_LIMITED':
          return AuthResult.failure(
            message: 'Too many attempts. Please wait ${e.retryAfter} seconds.',
            retryAfter: e.retryAfter,
          );
        case 'INVALID_PHONE':
          return AuthResult.failure(message: 'Invalid phone number format');
        case 'DELIVERY_FAILED':
          return AuthResult.failure(message: 'Failed to deliver OTP. Please check your phone number.');
        default:
          return AuthResult.failure(message: e.message);
      }
    } catch (e) {
      return AuthResult.failure(message: 'Network error: ${e.toString()}');
    }
  }

  Future<AuthResult> verifyOTP(String verificationId, String otp) async {
    try {
      final isValid = await _akedly.verifyOTP(verificationId, otp);
      
      if (isValid) {
        return AuthResult.success();
      } else {
        return AuthResult.failure(message: 'Invalid OTP. Please try again.');
      }
    } catch (e) {
      return AuthResult.failure(message: 'Verification failed: ${e.toString()}');
    }
  }
}

class AuthResult {
  final bool isSuccess;
  final String? message;
  final String? data;
  final int? retryAfter;

  AuthResult.success({this.data}) 
    : isSuccess = true, message = null, retryAfter = null;
  
  AuthResult.failure({required this.message, this.retryAfter}) 
    : isSuccess = false, data = null;
}

Platform Support

Platform-Specific Features

  • Name
    Android & iOS
    Type
    platforms
    Description

    Full support with automatic SMS detection and app-to-app verification flows

  • Name
    Web
    Type
    platforms
    Description

    Complete functionality with responsive UI components optimized for web browsers

  • Name
    Desktop (macOS, Linux, Windows)
    Type
    platforms
    Description

    Full API access with desktop-optimized UI patterns and keyboard navigation

Configuration

Configuration

DART
lib/config/akedly_config.dart
class AkedlyConfig {
  static const bool _isDevelopment = bool.fromEnvironment('dart.vm.product') == false;
  
  static String get apiKey {
    return _isDevelopment 
      ? 'your_development_api_key' 
      : 'your_production_api_key';
  }
  
  static String get pipelineId {
    return _isDevelopment 
      ? 'your_development_pipeline_id' 
      : 'your_production_pipeline_id';
  }
  
  static AkedlyClient createClient() {
    return AkedlyClient(
      apiKey: apiKey,
      pipelineId: pipelineId,
    );
  }
}

Best Practices

  • Singleton Pattern: Create a single instance of AkedlyClient and reuse it throughout your app
  • Error Boundaries: Implement comprehensive error handling with user-friendly messages
  • Loading States: Always show loading indicators during API calls
  • Input Validation: Validate phone numbers and OTPs before sending to the API
  • Rate Limiting: Handle rate limits gracefully with countdown timers
  • Accessibility: Ensure your OTP input widgets are accessible to screen readers

Troubleshooting

Common Issues

  • Name
    Initialization Error
    Type
    error
    Description

    Verify your API key and pipeline ID are correct. Check the Akedly dashboard for the latest credentials.

  • Name
    OTP Not Received
    Type
    error
    Description

    Ensure the phone number includes the country code and is in the correct format (+1234567890).

  • Name
    Verification Timeout
    Type
    error
    Description

    OTPs expire after a set time. Request a new OTP if the user takes too long to enter it.

  • Name
    Network Connectivity
    Type
    error
    Description

    Handle network errors gracefully and provide retry mechanisms for users.

Debug Mode

Debug Configuration

DART
final akedlyClient = AkedlyClient(
  apiKey: 'your_api_key_here',
  pipelineId: 'your_pipeline_id_here',
  debugMode: true, // Enables detailed logging
);

Was this page helpful?