volt_net 2.1.3 copy "volt_net: ^2.1.3" to clipboard
volt_net: ^2.1.3 copied to clipboard

Motor de requisições HTTP de alta performance com Cache Híbrido (L1/L2), SQLite e Processamento em Isolates.

volt_net #

pub package Coverage Status License: MIT

O volt_net é uma camada de orquestração HTTP para aplicações Flutter que precisam de desempenho, resiliência offline, cache híbrido e parsing de JSON fora da thread principal. A biblioteca é agnóstica ao backend e trabalha com APIs REST convencionais.

Principais recursos #

Recurso Descrição
Cache híbrido Cache L1 em RAM e L2 persistente em SQLite, com TTL e remoção física de entradas expiradas.
Stale-while-revalidate Exibe o cache imediatamente e consulta a API em seguida, emitindo o dado atualizado por callback.
Network-first O fluxo padrão de GET consulta a rede; o cache pode ser usado explicitamente como leitura antecipada ou fallback offline.
Offline Sync POST, PUT e DELETE podem ser enfileirados e reenviados quando a conectividade retornar.
Parsing em isolate getModelResult, getListResult e postModel processam JSON fora da thread principal.
Interceptors Interceptação centralizada de requisições, respostas e erros.
Debounce Redução de chamadas repetidas em buscas e ações de alta frequência.
Resilient Batch Execução sequencial de operações com idempotência e rollback local.
Logging e CURL Logs estruturados e comandos CURL para depuração quando logging está habilitado.

Arquitetura #

O fluxo padrão é network-first. Quando o modo stale-while-revalidate está habilitado e a chamada permite leitura de cache, a aplicação recebe o valor persistido sem esperar a rede. A consulta online continua imediatamente; quando concluída, a resposta é salva em disco, promovida para L1 e entregue ao callback onUpdate.

sequenceDiagram
    participant UI as UI Flutter
    participant VN as VoltNet
    participant L1 as L1 RAM
    participant L2 as L2 SQLite
    participant API as API REST

    UI->>VN: GET com cacheEnabled/readCache
    VN->>L1: Procura entrada válida
    alt Cache disponível + staleWhileRevalidate
        L1-->>UI: Entrega cache imediatamente
        VN-)API: Revalidação em segundo plano
    else Cache em L2
        VN->>L2: Procura entrada válida
        L2-->>UI: Entrega cache imediatamente
        VN-)API: Revalidação em segundo plano
    else Cache ausente ou inválido
        VN->>API: Requisição online
    end

    API-->>VN: Dados atualizados
    VN->>L2: Persiste resposta
    VN->>L1: Promove resposta para RAM
    VN-->>UI: onUpdate(dados novos)

Expiração e limpeza do cache #

O TTL é aplicado no momento da leitura. Quando uma entrada persistida ultrapassa o TTL informado, ela deixa de ser retornada e o registro correspondente é removido do SQLite. O cache em memória também remove a entrada expirada da L1.

flowchart TD
    A[Entrada no cache] --> B{TTL informado?}
    B -- Não --> C[Entrada disponível conforme a política da chamada]
    B -- Sim --> D{TTL expirou?}
    D -- Não --> E[Retorna entrada]
    D -- Sim --> F[Remove da L1/L2]
    F --> G[Consulta rede ou usa fallback offline]

Stale-while-revalidate #

Ative a política global antes de runApp. A configuração não bloqueia a inicialização da aplicação e passa a valer para chamadas que usam readCache: true.

stateDiagram-v2
    [*] --> Cache
    Cache --> Tela: Exibe imediatamente
    Cache --> Rede: staleWhileRevalidate = true
    Rede --> AtualizaCache: Resposta 2xx
    AtualizaCache --> L1: Promoção para RAM
    AtualizaCache --> TelaAtualizada: onUpdate
    Rede --> Fallback: Falha de rede
    Fallback --> Tela: Usa cache expirado, se disponível

Configuração no main.dart #

A inicialização deve ocorrer depois de WidgetsFlutterBinding.ensureInitialized() e antes de runApp.

import 'package:flutter/material.dart';
import 'package:volt_net/volt_net.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Volt.initialize(
    databaseName: 'volt_net_cache.db',
    maxMemoryItems: 200,
    enableSync: true,
    defaultTimeout: const Duration(seconds: 20),
    logging: true,
    staleWhileRevalidate: true,
  );

  runApp(const MyApp());
}

Os parâmetros principais são databaseName, maxMemoryItems, enableSync, defaultTimeout, logging e staleWhileRevalidate. Para receber o cache imediatamente, a chamada também precisa usar cacheEnabled: true, type e readCache: true.

Configuração de URL e autenticação #

A aplicação deve fornecer uma implementação de BaseApiUrlConfig para centralizar URL base, headers e token.

class ApiConfig extends BaseApiUrlConfig {
  @override
  String resolveBaseUrl() => 'https://api.exemplo.com/v1';

  @override
  Future<Map<String, String>> getHeader() async => {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      };

  @override
  Future<String> getToken() async {
    // Leia o token do armazenamento seguro da aplicação.
    return 'Bearer token-da-aplicacao';
  }
}

GET #

GET network-first #

O GET padrão consulta a rede e salva respostas bem-sucedidas quando o cache está habilitado.

final request = GetRequest<ApiConfig>();
final config = ApiConfig();

final result = await request.get(
  config,
  '/users',
  cacheEnabled: true,
  type: CacheType.both,
  ttl: const Duration(minutes: 5),
);

if (result.isSuccess) {
  print(result.jsonBody);
}

GET com cache imediato e atualização da UI #

Para telas como home, feed ou dashboard, use readCache: true, staleWhileRevalidate: true e onUpdate. A primeira resposta pode ser o cache; a resposta online será emitida depois sem bloquear a primeira renderização.

class FeedPageState extends State<FeedPage> {
  final request = GetRequest<ApiConfig>();
  final config = ApiConfig();
  ResultApi? feed;

  Future<void> loadFeed() async {
    final immediate = await request.get(
      config,
      '/feed',
      cacheEnabled: true,
      type: CacheType.both,
      readCache: true,
      ttl: const Duration(minutes: 5),
      staleWhileRevalidate: true,
      onUpdate: (fresh) {
        if (!mounted) return;
        setState(() => feed = fresh);
      },
    );

    if (!mounted) return;
    setState(() => feed = immediate);
  }
}

Quando o modo global estiver ativo, staleWhileRevalidate pode ser omitido na chamada. O parâmetro por requisição permite substituir a configuração global quando necessário.

GET com parsing em isolate #

final result = await request.getModelResult<User>(
  config,
  '/profile',
  User.fromJson,
  cacheEnabled: true,
  type: CacheType.both,
  ttl: const Duration(minutes: 5),
);

if (result.isSuccess && result.model != null) {
  print(result.model!.name);
}

Para listas, use getListResult<T> com o mesmo padrão de configuração.

POST #

POST suporta JSON, multipart, cancelamento e fila offline.

final postRequest = PostRequest<ApiConfig>();

final result = await postRequest.post(
  config,
  endpoint: '/posts',
  data: {
    'title': 'Novo post',
    'body': 'Conteúdo do post',
  },
  offlineSync: true,
);

if (result.isPending) {
  print('POST enfileirado para sincronização posterior.');
}

Com parsing tipado:

final result = await postRequest.postModel<Post>(
  config,
  '/posts',
  Post.fromJson,
  data: {'title': 'Novo post'},
  offlineSync: true,
);

Para upload, envie isMultipart: true e use VoltFile nos campos correspondentes.

PUT / UPDATE #

O pacote expõe PutRequest.put. Ele representa a operação de update completo de um recurso REST. Não existe um método chamado update; quando a aplicação usa esse conceito, deve chamar put.

final putRequest = PutRequest<ApiConfig>();

final result = await putRequest.put(
  config,
  endpoint: '/users/42',
  data: {
    'name': 'Nome atualizado',
    'email': 'novo@email.com',
  },
  offlineSync: true,
);

PATCH #

A versão atual do volt_net não expõe uma classe ou método PatchRequest. Portanto, não documentamos um snippet que pareça suportado pela API atual. Para alterações completas, use PutRequest.put. O suporte nativo a PATCH está previsto no roadmap.

Quando o backend exigir PATCH, a implementação deverá incluir método HTTP PATCH, suporte correspondente na fila offline e testes específicos antes de ser considerada parte da API pública.

DELETE #

DELETE também pode ser enfileirado para sincronização quando a aplicação estiver offline.

final deleteRequest = DeleteRequest<ApiConfig>();

final result = await deleteRequest.delete(
  config,
  endpoint: '/posts/42',
  offlineSync: true,
);

if (result.isPending) {
  print('DELETE enfileirado para sincronização posterior.');
}

Interceptors #

Interceptors são úteis para refresh de token, cabeçalhos dinâmicos, telemetria e tratamento centralizado de erros.

class ApiInterceptor extends VoltInterceptor {
  @override
  FutureOr<http.BaseRequest> onRequest(http.BaseRequest request) async {
    request.headers['X-App-Version'] = '1.0.0';
    return request;
  }

  @override
  FutureOr<http.Response> onResponse(http.Response response) async {
    return response;
  }

  @override
  void onError(dynamic error) {
    // Encaminhe o erro para o sistema de observabilidade.
  }
}

void registerInterceptors() {
  Volt.addInterceptor(ApiInterceptor());
}

Debounce e operações em lote #

final result = await request.getWithDebounce(
  config,
  '/search',
  queryParameters: {'q': 'flutter'},
  delay: const Duration(milliseconds: 400),
);

Para operações dependentes, use resilientBatch com idempotência e rollback local.

final results = await postRequest.resilientBatch(
  [
    ({extraHeaders}) => postRequest.post(
          config,
          endpoint: '/addresses',
          data: {'city': 'São Paulo'},
          extraHeaders: extraHeaders,
        ),
    ({extraHeaders}) => postRequest.post(
          config,
          endpoint: '/orders',
          data: {'total': 100},
          extraHeaders: extraHeaders,
        ),
  ],
  idempotencyKey: 'checkout-123',
  rollbackOnFailure: true,
  onRollback: (successfulSteps) async {
    // Rever o estado local quando uma etapa posterior falhar.
  },
);

Persistência SQL customizada #

class LocalMessage extends SqlModel {
  final String content;

  LocalMessage(this.content);

  @override
  String get tableName => 'local_messages';

  @override
  Map<String, String> get tableSchema => {
        'id': 'INTEGER PRIMARY KEY AUTOINCREMENT',
        'content': 'TEXT',
      };

  @override
  Map<String, dynamic> toSqlMap() => {'content': content};
}

await CacheManager().saveModel(LocalMessage('Mensagem local'));

Roadmap #

O roadmap detalhado está em ROADMAP.md. A versão resumida é:

Status Entrega
Concluído Cache híbrido L1/L2, TTL, limpeza de entradas expiradas e network-first.
Concluído Stale-while-revalidate configurável, promoção automática para L1 e callback onUpdate.
Concluído Logging, CURL, interceptors, parsing em isolate, offline sync e resilient batch.
Planejado API nativa de PATCH com suporte à fila offline.
Planejado Persistência alternativa para Web sem SQLite.
Planejado Suporte GraphQL opcional.

Licença #

Desenvolvido por Felippe Pinheiro de Almeida sob a licença MIT.

2
likes
145
points
120
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Motor de requisições HTTP de alta performance com Cache Híbrido (L1/L2), SQLite e Processamento em Isolates.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

connectivity_plus, crypto, flutter, http, json_annotation, meta, mocktail, path, sqflite, uuid

More

Packages that depend on volt_net