RT API
A standardized API service layer for Flutter applications built on GetX's GetConnect, following RulTech's architecture pattern.
Features
- Callback-Based Architecture: Two initialization patterns (function-based and mixin-based) for flexible API lifecycle handling
- Automatic Error Handling: Built-in handling for network errors, timeouts, and HTTP exceptions
- Connectivity Detection: Automatic internet connectivity checks before API calls
- Bearer Token Management: Automatic token handling with GetStorage integration
- Configurable Status Codes: Support for custom allowed status codes
- Debug Logging: Comprehensive debug logging that only activates in debug mode
- Fluent API: Chainable configuration methods for clean, readable code
- Snackbar Support: Optional automatic error snackbar display
Getting Started
Prerequisites
Add the following dependencies to your pubspec.yaml:
dependencies:
get: ^4.6.6
get_storage: ^2.1.1
connectivity_plus: ^6.0.5
Installation
Add rt_api to your pubspec.yaml:
dependencies:
rt_api:
path: ../rt_api # or your package path
Base URL Configuration
Configure the base URL in one of two ways:
Option 1: Using GetStorage (Recommended)
await GetStorage.init();
GetStorage().write("baseUrl", "https://api.example.com/");
Option 2: Override in AppString mixin
// In lib/rt_api/api_strings.dart
mixin AppString {
String get baseUrl => "https://api.example.com/";
}
Usage
Basic Setup
Function-Based Approach
final api = ApiService.init(
ApiHandler(
apiStartCallback: () => print('API Started'),
apiSuccessfulCallback: (ApiSuccess data) {
print('Success: ${data.data}');
},
apiFailedWithMsg: (ApiError error) {
print('Error: ${error.message}');
},
apiFailedCheckYourInternet: (ApiError error) {
print('No Internet: ${error.message}');
},
),
showSnackBar: true,
baseUrl: "https://api.example.com/", // Optional
);
Mixin-Based Approach (Preferred for Controllers)
class MyController extends GetxController with ClassApiHandler {
late final ApiService api;
@override
void onInit() {
super.onInit();
api = ApiService.initWithThis(this);
}
@override
void apiSuccessfulCallback(ApiSuccess apiData) {
// Handle success
print('Data: ${apiData.data}');
}
@override
void apiFailedWithMsg(ApiError error) {
// Handle error
print('Error: ${error.message}');
}
@override
void apiFailedCheckYourInternet(ApiError error) {
// Handle no internet
print('Check your connection');
}
@override
void apiStartCallback() {
// API call started
print('Loading...');
}
}
Making API Calls
GET Request
// Simple GET
await api.getRequest('/users');
// GET with query parameters
await api.getRequest(
'/users',
query: {'page': '1', 'limit': '10'},
);
// GET with custom headers
await api
.setHeaders({'Custom-Header': 'value'})
.getRequest('/users');
POST Request
// POST with body
await api.postRequest(
'/login',
{
'email': 'user@example.com',
'password': 'password123',
},
);
// POST with bearer token
await api
.setBearerToken()
.postRequest('/create-post', postData);
PUT Request
await api
.setBearerToken()
.putRequest('/users/123', {
'name': 'John Doe',
'email': 'john@example.com',
});
PATCH Request
await api
.setBearerToken()
.patchRequest('/users/123', {
'name': 'Jane Doe',
});
DELETE Request
await api
.setBearerToken()
.deleteRequest('/users/123');
Fluent Configuration
Chain configuration methods for clean setup:
await api
.setHeaders({'Accept': 'application/json'})
.setBearerToken()
.setTimeOut(30)
.setMaxAuthRetries(2)
.setShowSnackBar(false)
.postRequest('/endpoint', body);
Debug Logging
The package includes comprehensive debug logging that only activates in debug mode (when kDebugMode is true). By default, debug logging is enabled.
Control Debug Logging
// Debug logging is ON by default
final api = ApiService.init(handler);
// To disable debug logging
api.showDebugLog(false);
// To enable debug logging
api.showDebugLog(true);
Debug Log Output
When enabled in debug mode, you'll see detailed logs:
✅ RT-API-Call: >> Initializing ApiService
✅ RT-API-Call: >> Base URL: https://api.example.com/
✅ RT-API-Call: >> POST Request - URL: /api/login
✅ RT-API-Call: >> POST Body: {email: user@example.com, password: ***}
✅ RT-API-Call: >> Sending POST request to: https://api.example.com/api/login
✅ RT-API-Call: >> POST Response - Status: 200
✅ RT-API-Call: >> Success Response - Data Type: _Map<String, dynamic>
✅ RT-API-Call: >> API Success - Status: 200, Message: Successful
Error logs are prefixed with ❌:
❌ RT-API-Call: ERROR >> No internet connection detected
❌ RT-API-Call: ERROR >> POST Exception: Connection timeout
Important: Debug logs are automatically disabled in release builds for optimal performance.
Custom Allowed Status Codes
By default, only status code 200 is considered successful. You can customize this:
final api = ApiService.init(
handler,
allowedStatusCodes: [200, 201, 204], // Custom success codes
);
Bearer Token Authentication
// Store token in GetStorage
GetStorage().write('apiToken', 'your-auth-token');
// Use bearer token in requests
await api
.setBearerToken()
.getRequest('/protected-endpoint');
Response Handling
ApiSuccess Structure
class ApiSuccess<T> {
int statusCode = 200;
String message = "Successful";
bool result = false;
T? data; // Generic typed response data
}
ApiError Structure
class ApiError {
int statusCode = codeResponseNull;
String message = "Error: ";
dynamic errorObject; // Raw error response for custom parsing
}
Status Codes
const codeNoInternet = 100; // No internet connection
const codeResponseNull = 103; // Null response body
const codeTimeOut = 104; // Request timeout
const codeUnknown = 105; // Unknown error
const codePerformLogoutOnTokenExpires = 412; // Token expired
const codePerformValidStatusCode = 400; // Validation error
Complete Example
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:rt_api/rt_api.dart';
class UserController extends GetxController with ClassApiHandler {
late final ApiService api;
final users = <User>[].obs;
final isLoading = false.obs;
@override
void onInit() {
super.onInit();
api = ApiService.initWithThis(this);
fetchUsers();
}
Future<void> fetchUsers() async {
await api
.setBearerToken()
.setTimeOut(30)
.getRequest('/users');
}
Future<void> createUser(Map<String, dynamic> userData) async {
await api
.setBearerToken()
.postRequest('/users', userData);
}
@override
void apiStartCallback() {
isLoading.value = true;
}
@override
void apiSuccessfulCallback(ApiSuccess apiData) {
isLoading.value = false;
if (apiData.data is List) {
users.value = (apiData.data as List)
.map((json) => User.fromJson(json))
.toList();
}
}
@override
void apiFailedWithMsg(ApiError error) {
isLoading.value = false;
Get.snackbar('Error', error.message);
}
@override
void apiFailedCheckYourInternet(ApiError error) {
isLoading.value = false;
Get.snackbar('Connection Error', 'Please check your internet');
}
}
Technical Information
Environment Requirements
- Flutter: 3.32.0 (minimum 3.2.3 recommended)
- Dart SDK: 3.8.0 (constraint:
>=3.2.3 <=4.0.0) - Supported Platforms: Android, iOS, macOS, Web
- Development Environment:
- macOS 26.0.1 (darwin-arm64)
- Xcode 26.1 (Build 17B55)
- Android SDK 36.1.0
- Java: OpenJDK 21.0.8
- VS Code 1.105.1
- Android Studio 2025.1
Dependencies
Core Dependencies
- get (^4.6.6): State management and HTTP client (GetConnect)
- get_storage (^2.1.1): Local storage for base URL and auth token
- connectivity_plus (^6.0.5): Network connectivity detection
Development Dependencies
- flutter_lints (^4.0.0): Enforces Flutter best practices and code quality
All dependencies use caret (^) constraints for automatic minor/patch updates. Run flutter pub upgrade regularly to stay current.
Architecture Overview
This package follows RulTech's architecture pattern: Architecture Snippet
Core Design Pattern: Callback-Based API Handler
The package uses two initialization patterns for handling API lifecycle callbacks:
- ApiHandler (Function-based): Pass callback functions to handle API lifecycle
- ClassApiHandler (Mixin-based): Implement mixin methods in your class for tighter integration
Response Flow Architecture
All API methods follow this pattern:
- Connectivity check → Returns
ApiErrorwithcodeNoInternet(100) if offline - HTTP request → Uses GetX's GetConnect methods
- Response processing →
processAndHandleResponse()determines success/error - Callback invocation → Calls appropriate handler method (success or error)
- Return value → Returns
ApiSuccessorApiErrorfor direct handling
Key Components
Base URL Configuration Priority
- Constructor parameter
baseUrl - GetStorage with key
"baseUrl" - AppString mixin override
If none are set, throws UnimplementedError.
Status Code Handling
Custom status codes can be configured via allowedStatusCodes parameter:
ApiService(allowedStatusCodes: [200, 201, 204])
Default: [200]
Debug Logging Configuration
- Production Safe: Uses
kDebugModeconstant (Flutter foundation) - Default State: Enabled (
_showDebugLog = true) - Control Method:
showDebugLog(bool) - Output Format: Prefixed with tag
"RT-API-Call:", wrapped at 1024 characters - Performance: Zero overhead in release builds
Error Handling Strategy
All HTTP methods catch 5 exception types:
GetHttpException- GetConnect HTTP errorsUnauthorizedException- 401 responsesUnexpectedFormat- JSON parsing failuresException- Generic Dart exceptionsFlutterErrorDetails- Flutter framework errors
All converge to _handleError() which creates ApiError and invokes callbacks.
Package Structure
rt_api/
├── lib/
│ ├── rt_api.dart # Public API exports
│ └── rt_api/
│ ├── api_service.dart # Main service class with HTTP methods
│ ├── api_strings.dart # Base URL configuration mixin
│ ├── Handler/
│ │ ├── api_handler.dart # Function-based callback handler
│ │ └── class_api_handler.dart # Mixin-based callback handler
│ └── helper/
│ ├── api_error.dart # Error response model
│ ├── api_success.dart # Success response model
│ └── get_connect_helper.dart # Abstract GetConnect wrapper
├── test/
│ └── rt_api_test.dart # Unit tests
├── analysis_options.yaml # Linter configuration
├── CHANGELOG.md # Version history
└── pubspec.yaml # Package dependencies
Design Principles
1. Modular Approach
- Single Responsibility: Each class/file handles one concern
ApiService→ HTTP operationsApiHandler/ClassApiHandler→ Callback managementApiError/ApiSuccess→ Response models
- Separation of Concerns: Business logic separated from API layer
- Handler Pattern: Two initialization patterns for flexibility
2. Common Constants
- Centralized Status Codes: All defined at top of
api_service.dart - String Constants: Base URL in
api_strings.dartmixin - No Magic Numbers: All values are named constants
3. No Deprecated Code
- Current Dependencies: All packages use latest stable versions
- Version Strategy: Caret (^) for automatic minor/patch updates
- Migration Ready: When Flutter/Dart updates deprecate APIs, package updates immediately
- Lint Enforcement:
flutter_lints: ^4.0.0enforces latest practices
4. Encapsulation
- Private Members: Prefixed with underscore for internal use
- Controlled Access: Public getters only when needed
- Protected Methods: Internal methods are private
- Clean Public API: Only necessary methods exposed via
lib/rt_api.dart
5. Version Management
- Latest Versions: Always use latest or
anyfor library versions - Flexible Constraints: Caret (^) allows updates within major version
- Regular Updates: Run
flutter pub upgraderegularly - Breaking Change Policy: Only lock versions if breaking changes exist
Performance Considerations
- Zero Overhead in Production: Debug logging completely disabled in release builds
- Efficient Connectivity Checks: Checks occur before expensive HTTP operations
- Lazy Initialization: Resources initialized only when needed
- Memory Efficient: Callbacks are optional, no forced object retention
Security Features
- Token Management: Secure token storage via GetStorage
- Automatic Token Injection: Bearer token added to headers automatically
- Token Expiration Handling: Status code 412 signals token expiration
- No Token Logging: Sensitive data not included in debug logs
Testing
Unit tests can be added to test/rt_api_test.dart. Recommended test patterns:
- Mock GetStorage for offline/online scenarios
- Test both ApiHandler and ClassApiHandler initialization
- Verify callback invocation for all response types
- Test connectivity detection
Contributing
For issues and feature requests, please contact RulTech development team.
License
See LICENSE file for details.