flutter-http-and-json

Secure HTTP networking and JSON handling for Flutter apps with background parsing support. Implements CRUD operations (GET, POST, PUT, DELETE) using the http package with safe URL construction via Uri.https() and strict status code validation Provides strongly typed JSON serialization and deserialization using Dart 3 pattern matching with factory constructors and toJson() methods Offloads large JSON parsing to background isolates via compute() to prevent UI jank on payloads exceeding 16ms parse time Integrates structured JSON schemas for AI model outputs, including Gemini response validation with firebase_vertexai Requires http package in pubspec.yaml , Dart 3 null safety, and platform-specific internet permissions (Android manifest, macOS entitlements)

INSTALLATION
npx skills add https://github.com/flutter/skills --skill flutter-http-and-json
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2a

**Android (android/app/src/main/AndroidManifest.xml):**

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Required to fetch data from the internet. -->

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

    <application ...>

</manifest>

**macOS (macos/Runner/DebugProfile.entitlements and Release.entitlements):**

<dict>

    <!-- Required to fetch data from the internet. -->

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

    <true/>

</dict>

2. Define the JSON Data Model

Create a strongly typed Dart class to represent the JSON data. Use factory constructors for deserialization and a toJson method for serialization.

import 'dart:convert';

class ItemModel {

  final int id;

  final String title;

  const ItemModel({required this.id, required this.title});

  // Deserialize using Dart 3 pattern matching

  factory ItemModel.fromJson(Map<String, dynamic> json) {

    return switch (json) {

      {'id': int id, 'title': String title} => ItemModel(id: id, title: title),

      _ => throw const FormatException('Failed to parse ItemModel.'),

    };

  }

  // Serialize to JSON

  Map<String, dynamic> toJson() => {

        'id': id,

        'title': title,

      };

}

3. Implement HTTP Operations (CRUD)

Use the http package to perform network requests. Always use Uri.https for safe URL encoding. Validate the status code and throw exceptions on failure.

import 'dart:convert';

import 'package:http/http.dart' as http;

class ApiService {

  final http.Client client;

  ApiService(this.client);

  // GET Request

  Future<ItemModel> fetchItem(int id) async {

    final uri = Uri.https('api.example.com', '/items/$id');

    final response = await client.get(uri);

    if (response.statusCode == 200) {

      return ItemModel.fromJson(jsonDecode(response.body) as Map<String, dynamic>);

    } else {

      throw Exception('Failed to load item: ${response.statusCode}');

    }

  }

  // POST Request

  Future<ItemModel> createItem(String title) async {

    final uri = Uri.https('api.example.com', '/items');

    final response = await client.post(

      uri,

      headers: <String, String>{'Content-Type': 'application/json; charset=UTF-8'},

      body: jsonEncode(<String, String>{'title': title}),

    );

    if (response.statusCode == 201) {

      return ItemModel.fromJson(jsonDecode(response.body) as Map<String, dynamic>);

    } else {

      throw Exception('Failed to create item: ${response.statusCode}');

    }

  }

  // DELETE Request

  Future<void> deleteItem(int id) async {

    final uri = Uri.https('api.example.com', '/items/$id');

    final response = await client.delete(

      uri,

      headers: <String, String>{'Content-Type': 'application/json; charset=UTF-8'},

    );

    if (response.statusCode != 200) {

      throw Exception('Failed to delete item: ${response.statusCode}');

    }

  }

}

4. Implement Background Parsing for Large JSON Arrays

If fetching a large list of objects, move the JSON decoding and mapping to a separate isolate using compute().

import 'package:flutter/foundation.dart';

// Top-level function required for compute()

List<ItemModel> parseItems(String responseBody) {

  final parsed = (jsonDecode(responseBody) as List<Object?>).cast<Map<String, Object?>>();

  return parsed.map<ItemModel>(ItemModel.fromJson).toList();

}

Future<List<ItemModel>> fetchLargeItemList(http.Client client) async {

  final uri = Uri.https('api.example.com', '/items');

  final response = await client.get(uri);

  if (response.statusCode == 200) {

    // Run parseItems in a separate isolate

    return compute(parseItems, response.body);

  } else {

    throw Exception('Failed to load items');

  }

}

5. Define Structured JSON Output for AI Models

When integrating LLMs (like Gemini), enforce reliable JSON output by passing a strict schema in the generation configuration and system instructions.

import 'package:firebase_vertexai/firebase_vertexai.dart';

// Define the expected JSON schema

final _responseSchema = Schema(

  SchemaType.object,

  properties: {

    'width': Schema(SchemaType.integer),

    'height': Schema(SchemaType.integer),

    'items': Schema(

      SchemaType.array,

      items: Schema(

        SchemaType.object,

        properties: {

          'id': Schema(SchemaType.integer),

          'name': Schema(SchemaType.string),

        },

      ),

    ),

  },

);

// Initialize the model with the schema

final model = FirebaseAI.googleAI().generativeModel(

  model: 'gemini-2.5-pro',

  generationConfig: GenerationConfig(

    responseMimeType: 'application/json',

    responseSchema: _responseSchema,

  ),

);

Future<Map<String, dynamic>> analyzeData(String prompt) async {

  final content = [Content.text(prompt)];

  final response = await model.generateContent(content);

  // Safely decode the guaranteed JSON response

  return jsonDecode(response.text!) as Map<String, dynamic>;

}

Constraints

  • Immutable URL Construction: Always use Uri.https() or Uri.parse() to build URLs. Never use raw string concatenation for endpoints with query parameters.
  • Error Handling: Never return null on a failed network request. Always throw an Exception or a custom error class so the UI (e.g., FutureBuilder) can catch and display the error state via snapshot.hasError.
  • Status Code Validation: Always validate response.statusCode. Use 200 for successful GET/PUT/DELETE and 201 for successful POST.
  • Library Restriction: Do not use dart:io HttpClient directly for standard cross-platform networking. Always use the http package to ensure web compatibility.
  • Isolate Communication: When using compute(), ensure the parsing function is a top-level function or a static method, and only pass primitive values or simple objects (like String response bodies) across the isolate boundary. Do not pass http.Response objects.
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card