kickin_network 0.0.2+1 copy "kickin_network: ^0.0.2+1" to clipboard
kickin_network: ^0.0.2+1 copied to clipboard

Kickin network provides utilities to handle network efficiently and reliably

kickin_network #

A Flutter package for making HTTP requests cleanly and reliably. It is built on top of Dio, with typed responses, optional caching, and internet connectivity monitoring baked in.

Part of the Kickin toolkit for Flutter.


Installation #

flutter pub add kickin_network

Or add it manually to your pubspec.yaml:

dependencies:
  kickin_network: ^0.0.2+1

How it works #

The package gives you three things to work with:

  1. An API hub (KRestApiBase): one class per app that holds your base URL, Dio config, and interceptors.
  2. Feature API clients (KRestApi): one class per area of your app (e.g. UsersApi, ChatsApi). Each client is owned by the hub.
  3. Requests (KGetRequest, KPostRequest, etc.): declared once on your client, then called with .send() or .catchErrorOnSend() wherever you need them.

Every response comes back as ApiResult<T>, which holds either a typed value or an error ending with no uncaught exceptions.


Quick start #

Step 1 — Create your API hub #

import 'package:kickin_network/kickin_network.dart';

class Api extends KRestApiBase {
  Api._();
  static final instance = Api._();

  // Feature clients live here
  late final users = UsersApi(this);

  Future<void> init() async {
    await super.intialize(
      baseUrl: 'https://api.yourapp.com',
    );
  }
}

Step 2 — Create a feature client #

class UsersApi extends KRestApi {
  UsersApi(super.parent);

  // Declare your requests once
  late final getProfile = KGetRequest(
    this,
    path: '/users/me',
    decoder: (data, _) => UserModel.fromMap(data),
  );
}

Step 3 — Call it anywhere #

await Api.instance.init();

final result = await Api.instance.users.getProfile.catchErrorOnSendResult();

if (result.isSuccess) {
  print(result.value); // UserModel
} else {
  print(result.error); // human-readable error string
}

That's the core loop. Everything else is opt-in.


Sending requests #

Each request type has a few ways to execute it. Pick the one that fits your use case:

Method Returns Throws on error?
.send() TDecoded? Yes
.catchErrorOnSend() TDecoded? No, returns null
.sendResponse() KResponse<Raw, TDecoded> Yes
.catchErrorOnSendResponse() KResponse<Raw, TDecoded> No
.sendResult() ApiResult<TDecoded> Yes
.catchErrorOnSendResult() ApiResult<TDecoded> No, recommended

Recommendation: Use catchErrorOnSendResult() in most cases. It never throws and gives you both value and error in one object.


Request types #

Class HTTP method
KGetRequest GET
KPostRequest POST
KPutRequest PUT
KPatchRequest PATCH
KDeleteRequest DELETE
KDownloadRequest GET (file download)

Cloning requests for dynamic paths #

Use .copyWith() to create a modified copy of any request; useful for dynamic routes:

// Base request defined on the client
late final getUserById = KGetRequest(this, path: '/users/me', ...);

// Clone it with a different path at call time
final result = await getUserById
    .copyWith(pathTransform: (p) => '/users/$userId')
    .catchErrorOnSendResult();

Sending data (POST / PATCH / PUT) #

late final updateBio = KPatchRequest(
  this,
  path: '/users/me',
  decoder: (_, res) => res.statusCode == 200,
);

// At call time, attach the body
final result = await updateBio
    .copyWith(data: {'bio': 'Hello!'}, headers: addAuthHeader())
    .catchErrorOnSendResult();

Responses #

ApiResult<T> #

The cleanest response type (check isSuccess to see if it was successful):

final result = await api.users.getProfile.catchErrorOnSendResult();

if (result.isSuccess) {
  print(result.value); // your decoded model
} else {
  print(result.error); // error message string
}

KResponse<Raw, Formatted> #

Extends Dio's Response, so you can access raw HTTP details too:

final response = await api.users.getProfile.catchErrorOnSendResponse();

print(response.statusCode);
print(response.raw);     // raw payload
print(response.value);   // decoded model
print(response.error);   // error if any

Interceptors & global error handling #

Add global interceptors (e.g. token refresh) on your hub:

Api.instance.primaryInterceptors.add(
  TokenRefreshInterceptor(onSessionInvalid: () async { /* logout */ }),
);

Override globalErrorOverride to customise how errors are parsed:

class Api extends KRestApiBase {
  @override
  String? globalErrorOverride(Response response, Object? error, [StackTrace? st]) {
    final result = super.globalErrorOverride(response, error, st);
    if (result is List) return result.take(3).join(', ');
    return result?.toString();
  }
}

The default implementation already handles common HTTP status codes (400, 401, 403, 404, 500, etc.) with readable messages.


Logging #

Control what gets logged during development via LogOptions:

await super.intialize(
  baseUrl: '...',
  logOptions: LogOptions(
    parts: {LogPart.requestBody, LogPart.responseBody, LogPart.errors},
    maxLogLength: 2000,
  ),
);

Preset shortcuts:

Preset What it logs
LogOptions.debugAll() Everything
LogOptions.debugRequest() Request only (query, headers, body)
LogOptions.debugResponse() Response only (headers, body, errors)
LogOptions.normal() Query, request headers/body, response body
LogOptions.none() Nothing

Logging only runs in debug mode.


Optional: Caching — KApiCacheMixin #

Add in-memory caching to your hub with a mixin. Optionally sync it to disk.

class Api extends KRestApiBase with KApiCacheMixin {
  static final instance = Api._();
  Api._();
}

Enable disk persistence (uses Hive via kickin_storage):

await Api.instance.intialize(
  baseUrl: '...',
  syncCacheToStorage: true,
  cacheBoxName: 'my_app_cache', // optional
);

Use cache inside a feature client:

class UsersApi extends KRestApi<UserModel> {
  UsersApi(super.parent);

  Future<UserModel?> getCachedProfile() async {
    if (cache != null) return cache; // return cached value
    final result = await getProfile.catchErrorOnSend();
    if (result != null) setCache(result);
    return result;
  }
}
  • Each client gets its own cache slot meaning no conflicts between clients.
  • Disk writes are batched with a 300 ms debounce to avoid excessive I/O.
  • On app restart, the cache is restored from disk before any request fires.

Clean up:

Api.instance.disposeCache(); // cancel pending writes

Optional: Internet monitoring — KApiMonitorMixin #

Requires platform setup: see Platform setup below.

Add real-time connectivity monitoring to your hub:

class Api extends KRestApiBase with KApiCacheMixin, KApiMonitorMixin {
  static final instance = Api._();
  Api._();
}
// Start/stop monitoring globally
Api.instance.startMonitoring();
Api.instance.stopMonitoring();

// React to connectivity changes
void _onStatusChange(InternetStatus status) {
  if (status == InternetStatus.disconnected) showOfflineBanner();
}

Api.instance.addListener(_onStatusChange);

// Remove when done (e.g. in dispose)
Api.instance.removeListener(_onStatusChange);
  • Listeners are paused (not cancelled) when stopMonitoring() is called so they resume cleanly.
  • Call disposeMonitor() to clean up all subscriptions when the app shuts down.
@override
void dispose() {
  Api.instance.disposeCache();
  Api.instance.disposeMonitor();
}

Platform setup #

Required only when using KApiMonitorMixin.

Android #

Add to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

macOS #

Add to both macos/Runner/DebugProfile.entitlements and macos/Runner/Release.entitlements:

<key>com.apple.security.network.client</key>
<true/>

Full example #

import 'package:kickin_network/kickin_network.dart';

// ── Hub ──────────────────────────────────────────────────────────────
class Api extends KRestApiBase with KApiCacheMixin {
  Api._();
  static final instance = Api._();

  late final users = UsersApi(this);

  Future<void> init() async {
    await super.intialize(
      baseUrl: 'https://api.myapp.com',
      syncCacheToStorage: true,
      logOptions: LogOptions.debugAll(),
    );

    primaryInterceptors.add(
      TokenRefreshInterceptor(onSessionInvalid: () async {}),
    );
  }
}

// ── Feature client ───────────────────────────────────────────────────
class UsersApi extends KRestApi<UserModel> {
  UsersApi(super.parent);

  late final getProfile = KGetRequest(
    this,
    path: '/users/me',
    decoder: (data, _) => UserModel.fromMap(data['data']),
  );

  late final updateFcmToken = KPatchRequest(
    this,
    path: '/users/me/fcm-token',
    decoder: (_, res) => res.statusCode == 200,
  );
}

// ── Extensions (keep call sites clean) ───────────────────────────────
extension UsersApiExt on UsersApi {
  Map<String, String> _authHeader() => {'Authorization': 'Bearer $yourToken'};

  Future<ApiResult<UserModel?>> fetchProfile() =>
      getProfile.copyWith(headers: _authHeader()).catchErrorOnSendResult();

  Future<ApiResult<bool?>> pushFcmToken(String token) =>
      updateFcmToken
          .copyWith(headers: _authHeader(), data: {'fcmToken': token})
          .catchErrorOnSendResult();
}

// ── Usage ─────────────────────────────────────────────────────────────
Future<void> main() async {
  await Api.instance.init();

  final result = await Api.instance.users.fetchProfile();

  if (result.isSuccess) {
    print('Hello, ${result.value!.name}');
  } else {
    print('Error: ${result.error}');
  }
}

API reference summary #

Class / Mixin Purpose
KRestApiBase Base class for your app's API hub
KApiCacheMixin Adds in-memory + optional disk caching to the hub
KApiMonitorMixin Adds internet connectivity monitoring to the hub
KRestApi<T> Base class for feature API clients
KRestRequest<T> Base class for all request wrappers
KGetRequest HTTP GET
KPostRequest HTTP POST
KPutRequest HTTP PUT
KPatchRequest HTTP PATCH
KDeleteRequest HTTP DELETE
KDownloadRequest File download
KResponse<Raw, Formatted> Full Dio response + typed decoded value
ApiResult<T> Lightweight result type: value or error
LogOptions Configure what gets logged and how

WebSocket and GraphQL support are planned for future releases.

3
likes
160
points
122
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Kickin network provides utilities to handle network efficiently and reliably

Repository (GitHub)
View/report issues

License

BSD-3-Clause (license)

Dependencies

dio, flutter, internet_connection_checker_plus, kickin_storage, logger

More

Packages that depend on kickin_network