hosteday_flutter 2.3.0
hosteday_flutter: ^2.3.0 copied to clipboard
A lightweight Flutter SDK for connecting apps with Hosteday APIs, including authentication, user requests, custom endpoints, and realtime support.
hosteday_flutter #
Flutter SDK for Hosteday REST, authentication, public URLs, and optional Realtime.
Installation #
Add the package to your Flutter application:
dependencies:
hosteday_flutter: ^2.3.0
import 'package:hosteday_flutter/hosteday_flutter.dart';
Initialize Hosteday #
Only the project domain is required. No network request is made by the default initialization.
await Hosteday.initializeApp(options: {
HostedayOptionKeys.projectDomain: 'max.hosteday.com',
});
Optional values may be null. http://localhost:8787 preserves HTTP and the port.
Call await Hosteday.dispose() before switching projects.
Generated app runtime config #
Declare the asset in the application's pubspec.yaml:
flutter:
assets:
- assets/config/hosteday.json
{
"project_domain": "max.hosteday.com",
"project_api_key": null,
"realtime_app_key": null,
"realtime_host": null
}
WidgetsFlutterBinding.ensureInitialized();
await Hosteday.initializeFromAsset();
initializeFromAsset(overrides: {...}) supports explicit option overrides.
The engine only needs to supply the asset and ordinary SDK resource calls.
REST API #
Relative resource paths automatically receive /api. products, /products,
and /api/products all target https://max.hosteday.com/api/products.
Read a page or a record #
final page = await Hosteday.client.index('products',
search: 'phone', page: 1, withAuth: false,
);
final products = page.items; // List<Map<String, dynamic>>
print(page.currentPage);
print(page.perPage);
print(page.hasNextPage);
print(page.hasPreviousPage);
final product = await Hosteday.client.show('products', id: 1, withAuth: false);
print(product['name']);
index unwraps data.data into HostedayPage.items; show unwraps data
into a Map<String, dynamic>. Neither requires a resource-specific key.
Both default to withAuth: false and accept headers, timeout, query parameters,
and relation scoping. index also supports filters.
Raw GET (unchanged) #
final response = await Hosteday.client.get('products');
final recordResponse = await Hosteday.client.get('products', id: 1);
get returns the complete response envelope, including success, message,
and data, for applications that need raw responses.
POST #
final created = await Hosteday.client.post('/products',
body: {'name': 'Phone', 'price': 250},
withAuth: true,
);
PUT #
await Hosteday.client.put('/products', id: 15,
body: {'name': 'Phone', 'price': 240},
);
PATCH #
await Hosteday.client.patch('/products/15', body: {'price': 230});
DELETE #
await Hosteday.client.delete('/products', id: 15);
id is optional for PUT/PATCH/DELETE when it is already part of the path.
When supplied, it is appended as one encoded segment. The high-level client's
mutation methods retain withAuth: true as their default; GET and request
default to false. The low-level http methods retain their default of false.
Host and URLs #
final host = Hosteday.client.host; // https://max.hosteday.com
final api = Hosteday.client.apiBaseUrl; // https://max.hosteday.com/api
Advanced API routing:
await Hosteday.initializeApp(options: {
HostedayOptionKeys.projectDomain: 'max.hosteday.com',
HostedayOptionKeys.apiBaseUrl: 'https://gateway.example.com/v2',
});
// /products -> https://gateway.example.com/v2/products
// host and public links still use https://max.hosteday.com
An override containing only an origin gets /api; an override with a path uses
that exact prefix. base_url remains an alias. Full REST URLs must use the
configured API origin, protecting project and user credentials from accidental
forwarding. Full media URLs may use other origins.
Static Pages #
final info = Hosteday.client.staticPages['info'];
final privacy = Hosteday.client.staticPages['/privacy/'];
// https://max.hosteday.com/info
// https://max.hosteday.com/privacy
Dart reserves static, so the property is named staticPages. It accepts one
non-empty slug and rejects traversal, query strings, fragments, and foreign URLs.
It builds a link without fetching the page or checking the platform's page quota.
Storage & Media #
final imageUrl = Hosteday.client.storageUrl('products/image.png');
// https://max.hosteday.com/storage/products/image.png
final cdn = Hosteday.client.storageUrl('https://cdn.example.com/image.png');
// unchanged
An existing /storage/ prefix is not duplicated. Handle absent API image values
before calling storageUrl:
final imagePath = product['image'] as String?;
if (imagePath != null && imagePath.isNotEmpty) {
Image.network(Hosteday.client.storageUrl(imagePath));
}
Authentication #
final credential = await Hosteday.auth.signInWithEmailAndPassword(
email: 'customer@example.com', password: password,
);
final orders = await Hosteday.client.get('/orders', withAuth: true);
await Hosteday.auth.signOut();
await Hosteday.auth.createUserWithEmailAndPassword(
email: email, password: password,
additionalData: {'name': 'Customer'},
);
await Hosteday.auth.sendPasswordResetEmail(email: email);
await Hosteday.auth.reload();
await Hosteday.auth.sendEmailVerification();
Observe Hosteday.auth.authStateChanges() and read Hosteday.auth.currentUser.
For profile, avatar, password reset, session, and channel details see the
API reference.
Default auth storage is in memory. To persist sessions:
await Hosteday.initializeApp(
options: {HostedayOptionKeys.projectDomain: 'max.hosteday.com'},
authStorage: HostedaySharedPreferencesAuthStorage(),
);
SharedPreferences is not encrypted storage. You can implement
HostedayAuthStorage for your platform's secure storage. For an external user
session, supply a HostedayTokenProvider; the SDK's active session takes priority.
Project API Key #
await Hosteday.initializeApp(options: {
HostedayOptionKeys.projectDomain: 'max.hosteday.com',
HostedayOptionKeys.projectApiKey: projectApiKey,
});
The existing platform header is preserved: X-Api-Token. Null, empty, and
whitespace-only keys send no header. apiTokenHeader supports an explicit
server-specific override. This key is separate from Authorization: Bearer ...,
which is added only for withAuth: true (or an explicitly supplied header).
Client configuration is visible in a compiled application; use client-scoped keys.
Query Parameters & Search #
final products = await Hosteday.client.get('/products',
search: 'phone',
queryParameters: {'category_id': 5, 'page': 2},
filters: {'status': ['active', 'pending']},
);
Existing URL query values are preserved unless overridden. Null values are omitted. Nested maps and lists use Laravel bracket syntax. Filters apply to index GET requests only. Relation scoping remains available:
await Hosteday.client.get('/products', id: 15,
relationField: 'category_id', relationValue: 5,
);
Pagination / Responses #
final page = await Hosteday.client.index('products', page: 1, search: 'phone');
if (page.hasNextPage) {
final next = await Hosteday.client.index('products',
page: page.currentPage + 1, search: 'phone',
);
print(next.items);
}
if (page.hasPreviousPage) {
final previous = await Hosteday.client.index('products',
page: page.currentPage - 1, search: 'phone',
);
print(previous.items);
}
Keep the same search, filters, and relation options when requesting another page.
page must be positive and overrides queryParameters['page'] when supplied.
nextPageUrl and previousPageUrl are server metadata only. Requests use the
configured API domain, never the HTTP links returned in pagination metadata.
from and to may be null; total and lastPage are nullable and are never
inferred from record counts. Empty lists are valid. No automatic local sorting,
filtering, field coercion, or fetching of additional pages occurs.
index and show require success: true and the documented envelope shape.
Malformed responses and success: false throw HostedayException, retaining
server messages, validation details, and the original response. HTTP errors
continue to use the existing request-layer error handling.
get retains the server JSON structure without these envelope checks. Its
existing behavior still wraps a top-level list as {'data': list} and returns
{} for an empty body. The API explorer uses raw get to display full responses;
the posts repository uses index and show.
Error Handling #
try {
await Hosteday.client.index('products',
search: 'phone', page: 1, timeout: const Duration(seconds: 15));
} on HostedayException catch (error) {
final status = error.statusCode;
final developerMessage = error.message;
final userMessage = error.displayMessage;
final originalCause = error.error;
final emailError = error.firstErrorFor('email');
}
Network failures and timeouts have distinct messages. Invalid JSON retains the
HTTP status and original body in response['raw_body']. HTTP errors retain the
server message, status, and validation errors, including 429, 430–433, and 5xx.
No undocumented meaning is assigned to Hosteday-specific status codes.
For compatibility, missing/invalid configuration throws ArgumentError, and
access before initialization or repeated initialization throws StateError, with
specific corrective messages. Runtime asset read/JSON errors use HostedayException.
Realtime #
REST works without Realtime configuration. To connect, supply both settings:
await Hosteday.initializeApp(options: {
HostedayOptionKeys.projectDomain: 'max.hosteday.com',
HostedayOptionKeys.realtimeAppKey: realtimeAppKey,
HostedayOptionKeys.realtimeHost: realtimeHost,
});
if (Hosteday.config.hasRealtime) {
await Hosteday.connectRealtime();
}
Listen using listenPublic, listenPrivate, or listenPresence as shown in the
Realtime example.
Private and presence subscriptions require user authentication. Publishing follows
the same distinction:
await Hosteday.client.publishPublicEvent(
channel: 'products', event: 'updated', payload: {'id': 15},
);
await Hosteday.client.publishPrivateEvent(
channel: 'orders', event: 'updated', payload: {'id': 42},
);
await Hosteday.disconnectRealtime();
Complete Example #
This minimal app displays the real server response:
import 'package:flutter/material.dart';
import 'package:hosteday_flutter/hosteday_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hosteday.initializeApp(options: {
HostedayOptionKeys.projectDomain: 'max.hosteday.com',
});
final request = Hosteday.client.get('/products');
runApp(MaterialApp(home: Scaffold(body: SafeArea(
child: FutureBuilder<Map<String, dynamic>>(
future: request,
builder: (context, snapshot) {
if (snapshot.hasError) return Text(snapshot.error.toString());
if (!snapshot.hasData) return const CircularProgressIndicator();
return SingleChildScrollView(child: Text(snapshot.data.toString()));
},
),
))));
}
The interactive example includes all HTTP verbs, search,
query parameters, real response rows and images, URL helpers, account flows,
and optional Realtime. On Web, /categories is sent to /api/categories;
CORS must be configured by the server.
Migration and verification #
Old HosteDay... spellings remain deprecated compatibility APIs. New code uses
Hosteday.... See migration notes for URL behavior changes,
architecture, and verification status.
flutter pub get
dart format lib test example/lib example/test
flutter analyze
flutter test
cd example
flutter pub get
flutter analyze
flutter test
This is a Flutter package: run its flutter_test suite with flutter test, not
standalone dart test.