smart_api 1.0.1
smart_api: ^1.0.1 copied to clipboard
Dynamic Dio wrapper for Flutter — auto raw/multipart detection, global loader/snackbar hooks, configurable session-expiry & error handling, no-internet retry, and one-line API calls (no repeated try/c [...]
smart_api[cite: 1] #
Your previous BaseClient + ApiProvider pattern worked, but required writing the same 15-line try/catch block for every new project[cite: 1]. This package centralizes that logic once, making it fully reusable across all your projects[cite: 1].
Solves[cite: 1] #
- Raw vs. multipart confusion — Send a plain
Map; if it contains aFileor a valid local file path, the package automatically createsFormDataand converts it to aMultipartFile[cite: 1]. HTTP URL strings or plain text remain raw JSON[cite: 1]. No manual conversions required[cite: 1]. - Dynamic UI hooks (Loader / Snackbar / Toast) — Zero hard-coded UI packages (GetX, Fluttertoast, custom widgets, etc.)[cite: 1]. Set your handlers once inside
SmartApiHooks, and every call uses them automatically[cite: 1]. - Centrally handled status codes — 401/403 (session expired), timeouts, connection errors, and generic server errors are handled in one place instead of being rewritten across every API call[cite: 1].
- No-internet retry flow — The
onNoInternet(retry)hook receives a retry callback[cite: 1]. Simply callretry()from your "No Internet" screen to re-fire the exact failed request[cite: 1]. - One line per call —
SmartApiClient.instance.post(...)— NoApiRequest, noRequestType, and no repetitiveDataResponse.fromJsonboilerplate[cite: 1].
Setup (once, in main.dart)[cite: 1] #
SmartApiConfig.init(
baseUrl: "[https://api.example.com](https://api.example.com)",
headerBuilder: () => {"Authorization": "Bearer ${token}"},
);
SmartApiHooks.showLoader = Utils.showLoading;
SmartApiHooks.hideLoader = Utils.hideLoading;
SmartApiHooks.showMessage = (msg, {type}) => Utils.showAlert(message: msg);
SmartApiHooks.onSessionExpired = () => Get.offAllNamed(AppRoutes.signIn);
SmartApiHooks.onNoInternet = (retry) => Get.to(() => NoInternetPage(onRetry: retry));
```[cite: 1]
## Call an API[cite: 1]
```dart
final res = await SmartApiClient.instance.post<UserModel>(
"/api/login",
body: {"email": email, "password": password},
fromJson: (json) => UserModel.fromJson(json),
);
if (res.success) {
// res.data is already typed as UserModel
} else {
Utils.showAlert(message: res.message);
}
```[cite: 1]
File upload — no separate `handleMultipartRequest` needed[cite: 1]:
```dart
await SmartApiClient.instance.put("/api/editProfile", body: {
"name": name,
"image": pickedImage.path, // auto-detected and converted to multipart
});
```[cite: 1]
See `example/usage_example.dart` for a full before/after comparison with your existing `signupApi` / `editProfile` flow[cite: 1].
## Backend response shape differs per project?[cite: 1]
Two levels of control[cite: 1]:
**1. Just rename keys (most cases)** — no custom parser required[cite: 1]:
```dart
SmartApiConfig.init(
baseUrl: "...",
keyMap: const SmartApiKeyMap(
successKeys: ['ok'],
successValues: [true],
messageKeys: ['msg'],
dataKeys: ['payload'],
),
);
```[cite: 1]
**2. Fully custom shape** (nested envelopes, etc.) — write a custom response parser[cite: 1]:
```dart
SmartApiConfig.init(
baseUrl: "...",
responseParser: (json) => SmartApiEnvelope(
success: json['meta']['ok'] == true,
message: json['meta']['msg'] ?? '',
data: json['payload'],
),
);
```[cite: 1]
## File uploads — auto or explicit, single or multiple, custom key names[cite: 1]
By default (`autoDetectFiles: true`), any `File` or valid local file path inside `body` is auto-converted to multipart[cite: 1]. Disable it globally or per request to use explicit file bindings[cite: 1]:
```dart
// Single image with a custom field name:
files: [SmartApiFile.single(key: "image", path: path)]
// Multiple images under the same field:
files: SmartApiFile.list(key: "images", paths: [p1, p2, p3])
// Different keys per file (e.g., front/back ID + selfie):
files: [
SmartApiFile.single(key: "license_front", path: frontPath),
SmartApiFile.single(key: "license_back", path: backPath),
SmartApiFile.single(key: "profile_image", path: selfiePath),
]
```[cite: 1]
Passing `files` always forces multipart mode, regardless of the `autoDetectFiles` setting[cite: 1]. Standard text fields can still be passed in `body` alongside files[cite: 1].
## Query params on any method, not just GET[cite: 1]
```dart
SmartApiClient.instance.post(
"/api/roomSearch",
body: data, // JSON or multipart body
queryParams: {"id": roomId} // Appends ?id=... regardless of HTTP method
);
```[cite: 1]
## Future Roadmap Ideas[cite: 1]
- Add `retryCount` to `SmartApiRequest` for automatic retries on 5xx responses[cite: 1].
- Add response caching (per-endpoint TTL) for list and query endpoints[cite: 1].
- Add a `downloadFile()` helper with progress callbacks using the same hook architecture[cite: 1].