Get the FREE Ultimate OpenClaw Setup Guide →

nylo-networking

Scanned
npx machina-cli add skill nylo-core/claude-code/nylo-networking --openclaw
Files (1)
SKILL.md
13.9 KB

Nylo Networking

Overview

Nylo v7 networking is built on Dio and centered around NyApiService classes in lib/app/networking/. Services provide network() for typed responses and networkResponse() for full NyResponse<T> objects with status codes, headers, and error details.

When to Use

  • Creating or modifying API service classes
  • Making GET, POST, PUT, DELETE, PATCH requests
  • Adding request/response interceptors
  • Caching API responses with cache policies
  • Handling authentication tokens and refresh logic
  • Uploading or downloading files
  • Configuring retry logic, timeouts, or connectivity checks
  • When NOT to use: For local storage without network calls, use nylo-storage instead

Quick Reference

ActionCode
Create API service (CLI)metro make:api_service user
Create with modelmetro make:api_service user --model="User"
Create interceptor (CLI)metro make:interceptor logging
Simple GETawait get<User>("/users/1")
Simple POSTawait post<User>("/users", data: payload)
network() callawait network<User>(request: (r) => r.get("/users/1"))
networkResponse() callawait networkResponse<User>(request: (r) => r.get("/users/1"))
Use from pageawait api<ApiService>((r) => r.fetchUser())
Cache responsenetwork(..., cacheKey: "key", cacheDuration: Duration(hours: 1))
Bearer tokennetwork(..., bearerToken: "token123")
Retry requestsnetwork(..., retry: 3, retryDelay: Duration(seconds: 2))
Clear cacheawait apiService.clearCache("key")

API Service Structure

Services live in lib/app/networking/ and extend NyApiService:

class ApiService extends NyApiService {
  ApiService({BuildContext? buildContext})
      : super(
          buildContext,
          decoders: modelDecoders,
        );

  @override
  String get baseUrl => getEnv('API_BASE_URL');

  @override
  Map<Type, Interceptor> get interceptors => {
    ...super.interceptors,
  };

  Future<User?> fetchUser(int id) async {
    return await network<User>(
      request: (request) => request.get("/users/$id"),
    );
  }

  Future<List<User>?> fetchUsers() async {
    return await network<List<User>>(
      request: (request) => request.get("/users"),
    );
  }

  Future<User?> createUser(Map<String, dynamic> data) async {
    return await network<User>(
      request: (request) => request.post("/users", data: data),
    );
  }
}

Generate a new service with CLI:

metro make:api_service user
metro make:api_service user --model="User"

Request Methods

Convenience Methods

Shorthand wrappers around network():

// GET
Future<User?> fetchUser(int id) async {
  return await get<User>("/users/$id", queryParameters: {"include": "profile"});
}

// POST
Future<User?> createUser(Map<String, dynamic> data) async {
  return await post<User>("/users", data: data);
}

// PUT
Future<User?> updateUser(int id, Map<String, dynamic> data) async {
  return await put<User>("/users/$id", data: data);
}

// DELETE
Future<bool?> deleteUser(int id) async {
  return await delete<bool>("/users/$id");
}

// PATCH
Future<User?> patchUser(int id, Map<String, dynamic> data) async {
  return await patch<User>("/users/$id", data: data);
}

network() Method

Use for advanced features like caching, retries, custom headers:

Future<User?> fetchUser(int id) async {
  return await network<User>(
    request: (request) => request.get("/users/$id"),
    bearerToken: "token123",
    headers: {"X-Custom": "value"},
    retry: 3,
    retryDelay: Duration(seconds: 2),
    cacheKey: "user_$id",
    cacheDuration: Duration(minutes: 30),
    cachePolicy: CachePolicy.staleWhileRevalidate,
  );
}

Key parameters:

ParameterTypePurpose
requestFunction(Dio)HTTP operation (required)
bearerTokenString?Bearer auth token
baseUrlString?Override service base URL
headersMap<String, dynamic>?Custom request headers
retryint?Number of retry attempts
retryDelayDuration?Delay between retries
retryIfbool Function(DioException)?Conditional retry
cacheKeyString?Cache identifier
cacheDurationDuration?Cache TTL
cachePolicyCachePolicy?Caching strategy
checkConnectivitybool?Check network before request
handleSuccessFunction(NyResponse<T>)?Success callback
handleFailureFunction(NyResponse<T>)?Failure callback

networkResponse() Method

Returns NyResponse<T> with full response details:

Future<NyResponse<User>> fetchUser(int id) async {
  return await networkResponse<User>(
    request: (request) => request.get("/users/$id"),
  );
}

// Usage
NyResponse<User> response = await apiService.fetchUser(1);
if (response.isSuccessful) {
  User? user = response.data;
  print('Status: ${response.statusCode}');
} else {
  print('Error: ${response.errorMessage}');
}

NyResponse Properties and Methods

Status Checks

PropertyChecks
isSuccessful200-299
isClientError400-499
isServerError500-599
isRedirect300-399
isUnauthorized401
isForbidden403
isNotFound404
isTimeout408
isRateLimited429
hasDataData is not null

Data Access

Property/MethodReturns
dataDecoded model instance (T?)
rawDataUnprocessed response content
statusCodeHTTP status code
headersResponse headers
errorMessageError description

Helper Methods

// Get data or throw
User user = response.dataOrThrow('User not found');

// Get data with fallback
User user = response.dataOr(User.guest());

// Execute on success
String? greeting = response.ifSuccessful((user) => 'Hello ${user.name}');

// Pattern matching
String result = response.when(
  success: (user) => 'Welcome, ${user.name}!',
  failure: (response) => 'Error: ${response.statusMessage}',
);

// Extract specific header
String? auth = response.getHeader('Authorization');

Using API Services from Pages

Direct Instantiation

class _MyPageState extends NyPage<MyPage> {
  ApiService _apiService = ApiService();

  @override
  get init => () async {
    List<User>? users = await _apiService.fetchUsers();
  };
}

api() Helper

class _MyPageState extends NyPage<MyPage> {
  @override
  get init => () async {
    User? user = await api<ApiService>((request) => request.fetchUser(1));
  };
}

api() with Callbacks

await api<ApiService>(
  (request) => request.fetchUser(1),
  onSuccess: (response, data) {
    // data is the morphed User instance
  },
  onError: (DioException err) {
    // Handle error
  },
  cacheKey: "user_1",
  cacheDuration: Duration(minutes: 30),
);

Model Decoders

Register JSON-to-model mappings in lib/bootstrap/decoders.dart:

final Map<Type, dynamic> modelDecoders = {
  User: (data) => User.fromJson(data),
  List<User>: (data) =>
      List.from(data).map((json) => User.fromJson(json)).toList(),
};

The type argument in network<User>() matches the decoder key to automatically morph JSON responses into model instances.

Interceptors

Interceptors modify all requests/responses globally. Located in lib/app/networking/dio/interceptors/.

Register Interceptors

@override
Map<Type, Interceptor> get interceptors => {
  ...super.interceptors,
  BearerAuthInterceptor: BearerAuthInterceptor(),
  LoggingInterceptor: LoggingInterceptor(),
};

Create Custom Interceptor

metro make:interceptor logging
class LoggingInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    print('REQUEST[${options.method}] => PATH: ${options.path}');
    super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print('RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
    handler.next(response);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    print('ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}');
    handler.next(err);
  }
}

Caching

Basic Caching

Future<List<Country>?> fetchCountries() async {
  return await network<List<Country>>(
    request: (request) => request.get("/countries"),
    cacheKey: "app_countries",
    cacheDuration: Duration(hours: 1),
  );
}

Cache Policies

PolicyBehaviorUse When
CachePolicy.networkOnlyAlways fetch from networkData must be real-time
CachePolicy.cacheFirstUse cache, fallback to networkRarely-changing data, prioritize speed
CachePolicy.networkFirstTry network, fallback to cacheMust-be-fresh data, offline fallback
CachePolicy.cacheOnlyCache only, error if emptyOffline-only mode
CachePolicy.staleWhileRevalidateReturn cache immediately, refresh in backgroundNeed instant UI, accept brief staleness
return await network<List<Country>>(
  request: (request) => request.get("/countries"),
  cacheKey: "app_countries",
  cacheDuration: Duration(hours: 1),
  cachePolicy: CachePolicy.staleWhileRevalidate,
);

Clear Cache

await apiService.clearCache("app_countries"); // Specific key
await apiService.clearAllCache();             // All cached responses

Authentication Headers

Auto-Attach Auth Headers

class ApiService extends NyApiService {
  @override
  Future<RequestHeaders> setAuthHeaders(RequestHeaders headers) async {
    String? token = Auth.data(field: 'token');
    if (token != null) {
      headers.addBearerToken(token);
    }
    headers.addHeader('X-App-Version', '1.0.0');
    return headers;
  }
}

Skip Auth Headers for Public Endpoints

await network(
  request: (request) => request.get("/public-endpoint"),
  shouldSetAuthHeaders: false,
);

Token Refresh

class ApiService extends NyApiService {
  @override
  Future<bool> shouldRefreshToken() async {
    // Check if token is expired
    return false;
  }

  @override
  Future<void> refreshToken(Dio dio) async {
    // dio is a fresh instance without interceptors (prevents loops)
    dynamic response = (await dio.post(
      "https://example.com/refresh-token"
    )).data;

    await Auth.set((data) {
      data['token'] = response['token'];
      return data;
    });
  }
}

Retry Logic

// Basic retry
await network(
  request: (request) => request.get("/users"),
  retry: 3,
);

// With delay
await network(
  request: (request) => request.get("/users"),
  retry: 3,
  retryDelay: Duration(seconds: 2),
);

// Conditional retry (only on 500 errors)
await network(
  request: (request) => request.get("/users"),
  retry: 3,
  retryIf: (DioException err) {
    return err.response?.statusCode == 500;
  },
);

File Operations

Upload

Future<UploadResponse?> uploadAvatar(String filePath) async {
  return await upload<UploadResponse>(
    '/upload',
    filePath: filePath,
    fieldName: 'avatar',
    additionalFields: {'userId': '123'},
    onProgress: (sent, total) {
      print('Progress: ${(sent / total * 100).toStringAsFixed(0)}%');
    },
  );
}

// Multiple files
Future<UploadResponse?> uploadDocuments() async {
  return await uploadMultiple<UploadResponse>(
    '/upload',
    files: {
      'avatar': '/path/to/avatar.jpg',
      'document': '/path/to/doc.pdf',
    },
  );
}

Download

await download(
  url,
  savePath: savePath,
  onProgress: (received, total) {
    if (total != -1) {
      print('Progress: ${(received / total * 100).toStringAsFixed(0)}%');
    }
  },
  deleteOnError: true,
);

Timeouts and Base Options

class ApiService extends NyApiService {
  ApiService({BuildContext? buildContext}) : super(
    buildContext,
    decoders: modelDecoders,
    baseOptions: (BaseOptions opts) {
      return opts
        ..connectTimeout = Duration(seconds: 5)
        ..sendTimeout = Duration(seconds: 5)
        ..receiveTimeout = Duration(seconds: 5);
    },
  );
}

Runtime adjustments:

apiService.setConnectTimeout(Duration(seconds: 10));
apiService.setReceiveTimeout(Duration(seconds: 30));
apiService.setSendTimeout(Duration(seconds: 10));

Connectivity Checks

Fail fast when offline instead of waiting for timeout:

// Service-level
class ApiService extends NyApiService {
  @override
  bool get checkConnectivityBeforeRequest => true;
}

// Per-request
await network(
  request: (request) => request.get("/users"),
  checkConnectivity: true,
);

Common Mistakes

MistakeFix
Forgetting to register model in decoders.dartAdd both User and List<User> decoder entries for the model type
Using network() when you need status codesUse networkResponse() to get NyResponse<T> with statusCode, headers, etc.
Token refresh interceptor loopThe refreshToken() method receives a fresh Dio instance without interceptors; always use that instance
Not handling null return from network()network<T>() returns T?; always handle the null case
Caching without a cacheKeyBoth cacheKey and cacheDuration are required for caching to work
Using cacheOnly without pre-populating cachecacheOnly throws an error if no cached data exists; use cacheFirst or networkFirst for initial requests
Hardcoding base URLUse getEnv('API_BASE_URL') from .env file for environment-specific configuration
Not calling handler.next() in interceptorsAlways call handler.next(response) or super.onRequest(options, handler) to continue the chain

Source

git clone https://github.com/nylo-core/claude-code/blob/main/skills/nylo-networking/SKILL.mdView on GitHub

Overview

Nylo Networking is built on Dio and centers around NyApiService classes in lib/app/networking/. It provides network() for typed responses and networkResponse() for full NyResponse<T> objects with status codes, headers, and error details. The system supports creating services, interceptors, caching, token handling, and file uploads/downloads in a Nylo v7 Flutter app.

How This Skill Works

Developers create services that extend NyApiService, define baseUrl and interceptors, and use network() or networkResponse() to perform requests. Typed results leverage generics for model decoding, while networkResponse() exposes NyResponse<T> for status, headers, and error details. Interceptors and options like caching, tokens, and retries are configured at the service level.

When to Use It

  • Creating or modifying API service classes
  • Making GET, POST, PUT, DELETE, PATCH requests
  • Adding request/response interceptors
  • Caching API responses with cache policies
  • Handling authentication tokens and refresh logic

Quick Start

  1. Step 1: Generate a service scaffold: metro make:api_service user
  2. Step 2: Implement methods using network(), e.g., Future<User?> fetchUser(int id) => network<User>(request: (r) => r.get('/users/$id'))
  3. Step 3: Use in a page: await api<ApiService>((r) => r.fetchUser(1)) or configure caching/retry as needed

Best Practices

  • Extend NyApiService for all endpoints and organize services under lib/app/networking/
  • Use network() for typed models and networkResponse() when you need full NyResponse details
  • Configure interceptors for logging, auth headers, and global error handling
  • Enable caching with cacheKey, cacheDuration, and appropriate cachePolicy
  • Implement robust retry, timeout, and connectivity checks; plan token refresh flow with bearerToken

Example Use Cases

  • ApiService with fetchUser(id) using network<User>(request: (r) => r.get('/users/$id'))
  • Caching a user list: network<List<User>>(..., cacheKey: 'users', cacheDuration: Duration(hours: 1))
  • Uploading a file with multipart and auth token via interceptors
  • Retry logic: network(..., retry: 3, retryDelay: Duration(seconds: 2))
  • Token refresh flow integrated via bearerToken and refresh mechanism in interceptors

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers