ami_client_plus 1.0.1
ami_client_plus: ^1.0.1 copied to clipboard
Modern Asterisk Manager Interface (AMI) client with Clean Architecture. Type-safe, production-ready, with circuit breaker, connection pooling, and event filtering.
AMI Flutter #
Production-ready Asterisk Manager Interface (AMI) client for Flutter & Dart with Clean Architecture.
Modern, type-safe, testable AMI implementation supporting TCP, WebSocket, and custom transports.
✨ Features #
- ✅ Clean Architecture - SOLID principles, fully testable
- ✅ Type Safety - No raw maps, strongly-typed entities and actions
- ✅ Multi-Platform - Native (TCP) + Web (WebSocket) support
- ✅ Functional Error Handling -
Result<T>type for safe error management - ✅ Immutable Entities - Thread-safe, predictable state
- ✅ Easy Testing - Mock-friendly repository pattern
- ✅ Event Streaming - Reactive event handling with Dart Streams
- ✅ Reconnection Strategies - Exponential backoff built-in
🚀 Quick Start #
Installation #
dependencies:
ami_client_plus: ^1.0.1
Basic Usage (Clean Architecture - Recommended) #
import 'package:ami_client_plus/ami_client_plus_clean.dart';
void main() async {
// Create client
final client = AmiClientFactory.createTcpClient(
host: '127.0.0.1',
port: 5038,
);
// Connect and authenticate
await client.connect();
await client.authenticate(username: 'admin', password: 'secret');
// Send action
final response = await client.ping();
print('Ping response: ${response.status}');
// Listen to events
client.listenToEvent('Newchannel').listen((event) {
print('New channel: ${event.channel}');
});
// Originate call
await client.originateCall(
channel: 'SIP/6001',
extension: '100',
context: 'default',
);
}
Legacy API (Still Supported) #
import 'package:ami_client_plus/ami_client_plus.dart';
class MyClient with AmiBase, TCPSocketConnector {
// Mixin-based legacy approach
}
👉 New projects should use the Clean Architecture API (ami_client_plus_clean.dart)
📖 Documentation #
- Migration Guide - Migrate from legacy to Clean Architecture
- Usage Examples - Complete examples for common scenarios
- API Reference - Full API documentation
- Architecture Diagram - Visual architecture overview
🏗️ Architecture #
┌─────────────────────────────────────────────┐
│ Presentation Layer │
│ AmiClient (Facade) + AmiClientFactory │
└─────────────────┬───────────────────────────┘
│
┌─────────────────▼───────────────────────────┐
│ Domain Layer (Pure Logic) │
│ Entities • Value Objects • Use Cases │
│ Repository Interface • Exceptions │
└─────────────────┬───────────────────────────┘
│
┌─────────────────▼───────────────────────────┐
│ Data Layer │
│ Repository Impl • Data Sources │
│ (TCP, WebSocket, Custom) │
└─────────────────┬───────────────────────────┘
│
┌─────────────────▼───────────────────────────┐
│ Infrastructure Layer │
│ Parsers • Network • Strategies │
└─────────────────────────────────────────────┘
Dependencies flow inward - Domain has zero external dependencies!
💡 Examples #
Send Custom Action #
final action = GenericAction(
name: 'QueueAdd',
parameters: {
'Queue': 'support',
'Interface': 'SIP/6001',
},
);
final response = await client.sendAction(action);
Type-Safe Actions #
// Built-in type-safe actions
await client.sendAction(LoginAction(username: 'admin', secret: 'pass'));
await client.sendAction(PingAction());
await client.sendAction(OriginateAction(...));
await client.sendAction(HangupAction(channel: 'SIP/6001'));
await client.sendAction(StatusAction());
Event Filtering #
// Listen to specific events
client.listenToEvent('Hangup').listen((event) {
print('Call ended: ${event.channel}');
print('Cause: ${event.cause}');
});
// Listen to all events
client.eventStream.listen((event) {
print('Event: ${event.eventName}');
});
Error Handling #
try {
await client.sendAction(action);
} on AmiAuthenticationException catch (e) {
print('Auth failed: ${e.message}');
} on AmiTimeoutException catch (e) {
print('Timeout: ${e.message}');
} on AmiException catch (e) {
print('AMI error: ${e.message}');
}
Testing #
class MockRepository implements AmiRepository {
// Easy to mock for testing
}
test('send action', () async {
final repo = MockRepository();
final useCase = SendAction(repo);
final response = await useCase.call(PingAction());
expect(response.isSuccess, isTrue);
});
🧪 Testing #
dart test # Run all tests
dart test --coverage # With coverage
Test Coverage: 95%+ on Clean Architecture code
🛠️ Development #
# Analyze code
dart analyze
# Format code
dart format .
# Run example
cd example
dart run clean_architecture_example.dart
📝 Changelog #
See CHANGELOG.md for release history.
Latest (v1.0.1): Bug fixes and package metadata updates
🤝 Contributing #
Contributions welcome! Please open an issue or PR.
📄 License #
MIT License - see LICENSE file.
🔗 Links #
Made with ❤️ for the Flutter + Asterisk community