Skip to content

Networking ​

The network plugin is the app's shared HTTP client: every feature calls vyuh.network instead of creating its own.

ContractNetworkPlugin (implements Client from package:http)
Default implementationHttpNetworkPlugin
Accessorvyuh.network
PluginDescriptor slotnetwork
ImplementationsHttpNetworkPlugin, or your own

Because NetworkPlugin implements the standard http Client, you can pass vyuh.network to any code or package that expects a Client.

Set up ​

Every app gets an HttpNetworkPlugin with default settings. To change them, fill the network slot:

dart
import 'package:retry/retry.dart';
import 'package:vyuh_core/vyuh_core.dart';

void main() {
  runApp(
    plugins: PluginDescriptor(
      network: HttpNetworkPlugin(
        timeout: const Duration(seconds: 10),
        retryOptions: const RetryOptions(maxAttempts: 5),
      ),
    ),
    features: () => [/* ... */],
  );
}

HttpNetworkPlugin takes these optional arguments:

ArgumentDefaultWhat it does
timeout30 secondsHow long each attempt may take before it fails with a TimeoutException
retryOptionsRetryOptions(maxAttempts: 3, delayFactor: Duration(milliseconds: 200))How often and how quickly to retry, from the retry package
clienta new ClientUse this http Client instead of creating one
webIncludeCredentialstrueOn the web, send cookies with cross-origin requests (see below)

HttpNetworkConfig is deprecated. HttpNetworkPlugin does not use it, so pass timeout and retryOptions instead.

Cookies on the web ​

Web apps that sign in with HTTP-only session cookies need the browser to send those cookies on cross-origin requests. When the app runs on the web and webIncludeCredentials is true (the default), HttpNetworkPlugin creates a BrowserClient with withCredentials = true. Your server must answer with matching CORS headers (Access-Control-Allow-Credentials: true and an explicit origin).

Turn it off if your API is public and must not receive cookies. On mobile and desktop the setting has no effect.

Use it ​

Call vyuh.network from a feature, usually inside a small service class:

dart
import 'dart:convert';

import 'package:vyuh_core/vyuh_core.dart';

class OrdersApi {
  final _base = Uri.parse('https://api.example.com');

  Future<List<dynamic>> orders() async {
    final response = await vyuh.network.get(
      _base.resolve('/orders'),
      headers: {'Accept': 'application/json'},
    );

    if (response.statusCode != 200) {
      throw StateError('Could not load orders (${response.statusCode})');
    }
    return jsonDecode(response.body) as List<dynamic>;
  }

  Future<void> placeOrder(Map<String, Object?> order) async {
    final response = await vyuh.network.post(
      _base.resolve('/orders'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(order),
    );

    if (response.statusCode >= 400) {
      throw StateError('Order failed (${response.statusCode})');
    }
  }
}

The available methods are the http Client ones: get, head, post, put, patch, delete, read, readBytes, send and close. They return the http types Response, StreamedResponse, String and Uint8List; import package:http/http.dart when you need those types by name.

The plugin does not check status codes or decode bodies. A 404 or 500 is a normal Response; deciding what it means is up to your service.

Behaviour and lifecycle ​

  • Preloaded. NetworkPlugin is a preloaded plugin, so it starts before the platform and every other plugin can use it in init.
  • Start-up. init() creates the client: a BrowserClient with credentials on the web (when webIncludeCredentials is on), otherwise a standard Client. If you passed client, that one is used and nothing is created.
  • Shutdown. dispose() closes the client. close() does nothing: every feature shares the client, so only the platform closes it, when it disposes the plugin.
  • Restart. The plugin keeps running across platform restarts. If it fails to start, the app shows its startup failure screen, and Restart tries it again.
  • Timeouts and retries. get, head, post, put, patch, delete, read and readBytes each run with the timeout and are retried, but only for a TimeoutException or a socket error (a failure to reach the server). An HTTP error status is never retried. If every attempt fails, the last error is thrown.
  • send is not retried. send gets the same timeout, but a streamed request can only be sent once, so it is never retried.
  • No auth headers. The plugin adds no headers of its own. Add Authorization yourself, for example from vyuh.auth.token. Nothing calls vyuh.auth.refreshSession() on a 401 for you; call it and retry the request yourself.

Testing ​

Pass a mock Client to HttpNetworkPlugin. Here with mocktail:

dart
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart';
import 'package:mocktail/mocktail.dart';
import 'package:vyuh_core/vyuh_core.dart';
import 'package:vyuh_test/vyuh_test.dart';

class MockClient extends Mock implements Client {}

void main() {
  late MockClient client;

  setUp(() {
    client = MockClient();
    registerFallbackValue(Uri.parse('https://example.com'));
  });

  tearDown(() async {
    if (VyuhBinding.instance.initialized) await vyuh.dispose();
  });

  testWidgets('loads orders', (tester) async {
    when(() => client.get(any(), headers: any(named: 'headers')))
        .thenAnswer((_) async => Response('[]', 200));

    runApp(
      plugins: PluginDescriptor(network: HttpNetworkPlugin(client: client)),
      features: () => [],
    );
    await vyuh.getReady(tester);

    expect(await OrdersApi().orders(), isEmpty);
  });
}

MockClient from package:http/testing.dart works just as well.

Write your own ​

Extend NetworkPlugin to add behaviour every request should share, such as an auth header or logging. NetworkPlugin declares get, head, post, put, delete and patch; as a Client you also implement read, readBytes, send and close, plus init and dispose.

The simplest approach wraps HttpNetworkPlugin's client argument with your own http BaseClient:

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

class AuthClient extends BaseClient {
  final _inner = Client();

  @override
  Future<StreamedResponse> send(BaseRequest request) {
    final token = vyuh.auth.token;
    if (token.isNotEmpty) request.headers['Authorization'] = 'Bearer $token';
    return _inner.send(request);
  }

  @override
  void close() => _inner.close();
}

final network = HttpNetworkPlugin(client: AuthClient());

This keeps the timeouts and retries. vyuh.network.close() does nothing; the platform closes your client when it disposes the plugin. When you pass client, the web BrowserClient is not created for you, so wrap a BrowserClient yourself on the web if you need cookies. For a completely different transport, extend NetworkPlugin directly. See writing a plugin.

API ​