hosteday_flutter 2.0.0
hosteday_flutter: ^2.0.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 #
A Flutter SDK for connecting applications to HosteDay APIs, authentication, session storage, user management, custom backend endpoints, and realtime services.
hosteday_flutter gives Flutter developers a simple Firebase-inspired API for:
- Initializing a HosteDay project.
- Signing users in and out.
- Creating accounts.
- Persisting authenticated sessions.
- Sending password reset emails.
- Sending email verification emails.
- Reading and updating the current user.
- Calling custom backend APIs.
- Sending authenticated API requests.
- Connecting to HosteDay realtime channels.
- Listening to public, private, presence, and encrypted realtime events.
- Publishing realtime events through HosteDay.

HosteDay platform #
This package is designed to work with the HosteDay platform.
HosteDay is a backend and API platform that lets developers create isolated APIs for their projects without manually configuring Docker, databases, routing, HTTPS, authentication, or infrastructure. Each project can get its own generated API, isolated runtime environment, database, HTTPS-enabled subdomain, and ready-to-use backend endpoints.
To use this Flutter SDK, you first need to create a project from the HosteDay dashboard. After creating the project, HosteDay provides the values required by the Flutter app:
- Project domain, such as
your-project.hosteday.com. - Project API key, used by the SDK as
HosteDayOptionKeys.projectApiKey. - Realtime app key, used by the SDK as
HosteDayOptionKeys.realtimeAppKey. - Realtime host, used by the SDK as
HosteDayOptionKeys.realtimeHost.
A typical workflow is:
- Create a new project from the HosteDay dashboard.
- Define your database tables and fields, such as
posts,orders, orproducts. - Let HosteDay generate the API endpoints for your project.
- Copy the project domain and project API key from the dashboard.
- Enable realtime for the project when needed, then copy the realtime app key and realtime host.
- Use those values when initializing
hosteday_flutter.
Example:
await
HosteDay.initializeApp
(
options: const <String, Object?>{
HosteDayOptionKeys.projectDomain: 'your-project.hosteday.com',
HosteDayOptionKeys.projectApiKey: 'YOUR_PROJECT_API_KEY',
// Required only when using HosteDay realtime.
HosteDayOptionKeys.realtimeAppKey: 'YOUR_REALTIME_APP_KEY',
HosteDayOptionKeys.realtimeHost: 'ws3.hosteday.com',
},
authStorage
:
HosteDaySharedPreferencesAuthStorage
(
)
,
);
The project API key identifies your HosteDay project. It is not the signed-in user's access token.
After a user signs in, HosteDay Auth manages the user access token automatically. You only need to
set withAuth: true when calling protected project endpoints:
final response = await
HosteDay.client.get
('/api/posts
'
,withAuth:
true
,
);
Realtime also uses the authenticated user token automatically when subscribing to private or presence channels.
Installation #
Add the package to your Flutter project:
flutter pub add hosteday_flutter
Import it:
import 'package:hosteday_flutter/hosteday_flutter.dart';
Quick start #
Initialize HosteDay before running your Flutter app.
import 'package:flutter/material.dart';
import 'package:hosteday_flutter/hosteday_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await HosteDay.initializeApp(
options: const <String, Object?>{
HosteDayOptionKeys.projectDomain: 'your-project.hosteday.com',
// Project API key.
//
// This is not the signed-in user's access token.
HosteDayOptionKeys.projectApiKey: 'YOUR_PROJECT_API_KEY',
},
// Stores the user session locally using shared_preferences.
authStorage: HosteDaySharedPreferencesAuthStorage(),
);
runApp(const App());
}
Important naming notes #
Project API key #
Use:
HosteDayOptionKeys.projectApiKey
This identifies the HosteDay project.
It is not the authenticated user's access token.
The SDK sends it automatically as:
X-Api-Token: YOUR_PROJECT_API_KEY
User access token #
The user access token is created after sign in.
You do not pass it manually.
The SDK stores it and uses it automatically when you call:
withAuth: true
Example:
final response = await
HosteDay.client.get
('/api/posts
'
,withAuth:
true
,
);
Realtime app key #
Use:
HosteDayOptionKeys.realtimeAppKey
This key is provided by HosteDay for realtime connections.
It does not mean the developer needs a Pusher account.
HosteDay uses a Pusher-compatible realtime protocol internally.
Basic initialization #
await
HosteDay.initializeApp
(
options: const <String, Object?>{
HosteDayOptionKeys.projectDomain: 'your-project.hosteday.com',
HosteDayOptionKeys.projectApiKey: 'YOUR_PROJECT_API_KEY',
},
authStorage
:
HosteDaySharedPreferencesAuthStorage
(
)
,
);
Initialization with realtime #
await
HosteDay.initializeApp
(
options: const <String, Object?>{
HosteDayOptionKeys.projectDomain: 'your-project.hosteday.com',
HosteDayOptionKeys.projectApiKey: 'YOUR_PROJECT_API_KEY',
HosteDayOptionKeys.realtimeAppKey: 'YOUR_REALTIME_APP_KEY',
HosteDayOptionKeys.realtimeHost: 'ws3.hosteday.com',
HosteDayOptionKeys.realtimeScheme: 'wss',
HosteDayOptionKeys.realtimePort: 443,
},
authStorage
:
HosteDaySharedPreferencesAuthStorage
(
)
,
);
Realtime is not connected automatically unless you request it:
await
HosteDay.connectRealtime
();
Or initialize and connect immediately:
await
HosteDay.initializeApp
(
options: const <String, Object?>{
HosteDayOptionKeys.projectDomain: 'your-project.hosteday.com',
HosteDayOptionKeys.projectApiKey: 'YOUR_PROJECT_API_KEY',
HosteDayOptionKeys.realtimeAppKey: 'YOUR_REALTIME_APP_KEY',
HosteDayOptionKeys.realtimeHost: 'ws3.hosteday.com',
},
authStorage: HosteDaySharedPreferencesAuthStorage(),
connectRealtime: true,
);
Using environment variables #
Recommended for examples and local development:
abstract final class ExampleEnvironment {
const ExampleEnvironment._();
static const String projectDomain = String.fromEnvironment(
'HOSTEDAY_PROJECT_DOMAIN',
defaultValue: 'project.hosteday.com',
);
static const String projectApiKey = String.fromEnvironment(
'HOSTEDAY_PROJECT_API_KEY',
defaultValue: 'YOUR_PROJECT_API_KEY',
);
static const String realtimeAppKey = String.fromEnvironment(
'HOSTEDAY_REALTIME_APP_KEY',
defaultValue: 'YOUR_REALTIME_APP_KEY',
);
static const String realtimeHost = String.fromEnvironment(
'HOSTEDAY_REALTIME_HOST',
defaultValue: 'ws3.hosteday.com',
);
static const String realtimeScheme = String.fromEnvironment(
'HOSTEDAY_REALTIME_SCHEME',
defaultValue: 'wss',
);
static const int realtimePort = int.fromEnvironment(
'HOSTEDAY_REALTIME_PORT',
defaultValue: 443,
);
}
Run the app:
flutter run \
--dart-define=HOSTEDAY_PROJECT_DOMAIN=your-project.hosteday.com \
--dart-define=HOSTEDAY_PROJECT_API_KEY=your_project_api_key \
--dart-define=HOSTEDAY_REALTIME_APP_KEY=your_realtime_app_key \
--dart-define=HOSTEDAY_REALTIME_HOST=ws3.hosteday.com
The first argument of String.fromEnvironment must be the environment variable name, not the actual
value.
Correct:
String.fromEnvironment
('HOSTEDAY_PROJECT_DOMAIN
'
,defaultValue:
'
your-project.hosteday.com
'
,
);
Incorrect:
String.fromEnvironment
('https://your-project.hosteday.com
'
,defaultValue:
'
project.hosteday.com
'
,
);
Main SDK entry point #
The official SDK entry point is:
HosteDay
Available global accessors:
HosteDay.client;HosteDay.auth;HosteDay.config;HosteDay.http;HosteDay.realtime;HosteDay
.isInitialized;
Example:
final client = HosteDay.client;
final auth = HosteDay.auth;
final config = HosteDay.config;
Auth state gate #
Use authStateChanges() to switch between signed-in and signed-out screens.
class AuthGate extends StatelessWidget {
const AuthGate({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder<HosteDayUser?>(
stream: HosteDay.auth.authStateChanges(),
initialData: HosteDay.auth.currentUser,
builder: (context, snapshot) {
final user = snapshot.data;
if (user == null) {
return const SignInPage();
}
return HomePage(user: user);
},
);
}
}
Authentication #
Sign in with email and password #
try {
final credential = await HosteDay.auth.signInWithEmailAndPassword(
email: 'user@example.com',
password: 'password123',
);
final user = credential.user;
final session = credential.session;
print(user.id);
print(user.email);
print(session.accessToken);
} on HosteDayException catch (error) {
print(error.displayMessage);
}
After successful sign in, HosteDay automatically:
- Saves the session.
- Saves the access token.
- Updates
currentUser. - Emits auth state changes.
- Uses the token for protected API requests.
- Uses the token for private and presence realtime channel authorization.
Register a new user #
try {
final credential = await HosteDay.auth.createUserWithEmailAndPassword(
email: 'new-user@example.com',
password: 'password123',
additionalData: <String, dynamic>{
'name': 'Mustafa',
},
);
print(credential.user.id);
print(credential.user.displayName);
} on HosteDayException catch (error) {
print(error.displayMessage);
}
You can pass extra fields through additionalData:
await
HosteDay.auth.createUserWithEmailAndPassword
(
email: 'new-user@example.com',
password: 'password123',
additionalData: <String, dynamic>{
'name': 'Mustafa',
'phone': '+9647700000000',
},
);
The SDK sends the registration request to HosteDay's default auth endpoint.
Auth and user endpoints are owned by HosteDay and are not intended to be changed by app developers.
Current user #
final user = HosteDay.auth.currentUser;
if (
user == null) {
print('No user is signed in.');
} else {
print(user.id);
print(user.displayName);
print(user.email);
print(user.emailVerified);
print(user.photoUrl);
}
Listen to user changes #
final subscription = HosteDay.auth.userChanges().listen((user) {
if (user == null) {
print('User signed out.');
return;
}
print('Current user: ${user.email}');
});
Cancel when not needed:
await
subscription.cancel
();
Reload user from the server #
Use this after updating the profile, verifying email from a web link, or when you need fresh user data.
try {
final user = await HosteDay.auth.reload();
print(user.displayName);
print(user.emailVerified);
} on HosteDayException catch (error) {
print(error.displayMessage);
}
Update user profile #
try {
final user = await HosteDay.auth.updateProfile(
<String, dynamic>{
'name': 'Mustafa Max',
},
);
print(user.displayName);
} on HosteDayException catch (error) {
print(error.displayMessage);
}
The accepted fields depend on your HosteDay project backend.
Send email verification #
try {
await HosteDay.auth.sendEmailVerification();
print('Verification email sent.');
} on HosteDayException catch (error) {
print(error.displayMessage);
}
The Flutter app only requests the verification email.
The actual email verification is completed through the web link sent to the user.
Send password reset email #
try {
await HosteDay.auth.sendPasswordResetEmail(
email: 'user@example.com',
);
print('Password reset email sent.');
} on HosteDayException catch (error) {
print(error.displayMessage);
}
The Flutter app only requests the password reset email.
The actual password reset is completed through the web link sent to the user.
Sign out #
await
HosteDay.auth.signOut
();
When signing out, HosteDay:
- Attempts to call the remote logout endpoint.
- Clears the local session.
- Clears the local access token.
- Sets
currentUsertonull. - Emits
nullthrough auth streams. - Disconnects realtime.
The local session is cleared even if the remote logout endpoint fails.
Session storage #
Default recommended storage #
Use:
HosteDaySharedPreferencesAuthStorage
()
Example:
await
HosteDay.initializeApp
(
options: const <String, Object?>{
HosteDayOptionKeys.projectDomain: 'your-project.hosteday.com',
HosteDayOptionKeys.projectApiKey: 'YOUR_PROJECT_API_KEY',
},
authStorage
:
HosteDaySharedPreferencesAuthStorage
(
)
,
);
This stores the authenticated session using shared_preferences.
The user remains signed in after closing and reopening the app.
The package already depends on shared_preferences, so app developers do not need to install it
separately just to use HosteDay session storage.
Memory storage #
For tests or temporary sessions:
await
HosteDay.initializeApp
(
options: const <String, Object?>{
HosteDayOptionKeys.projectDomain: 'your-project.hosteday.com',
},
authStorage
:
MemoryHosteDayAuthStorage
(
)
,
);
Memory storage is cleared when the app process restarts.
Custom storage #
You can implement your own storage:
class CustomAuthStorage implements HosteDayAuthStorage {
final Map<String, String> _values = <String, String>{};
@override
Future<String?> read(String key) async {
return _values[key];
}
@override
Future<void> write(String key, String value) async {
_values[key] = value;
}
@override
Future<void> delete(String key) async {
_values.remove(key);
}
}
Use it:
await
HosteDay.initializeApp
(
options: const <String, Object?>{
HosteDayOptionKeys.projectDomain: 'your-project.hosteday.com',
},
authStorage
:
CustomAuthStorage
(
)
,
);
HTTP requests #
Use HosteDay.client for custom backend API requests.
Public GET request #
final response = await
HosteDay.client.get
('/api/posts
'
);
print(
response
);
Authenticated GET request #
final response = await
HosteDay.client.get
('/api/posts
'
,withAuth: true,
);
print(response);
This automatically sends:
Authorization: Bearer USER_ACCESS_TOKEN
GET with query parameters #
final response = await
HosteDay.client.http.get
('/api/posts
'
,withAuth: true,
queryParameters: <String, Object?>{
'page': 1,
'search': 'flutter',
},
);
print
(
response
);
Get one post #
final response = await
HosteDay.client.get
('/api/posts/1
'
,withAuth: true,
);
final post = response['data'];
print(post);
Example expected response:
{
"data": {
"id": 1,
"title": "First post",
"body": "Post body"
}
}
Create a post #
final response = await
HosteDay.client.post
('/api/posts
'
,withAuth: true,
body: <String, dynamic>{
'title': 'New post',
'body': 'Created from Flutter.',
},
);
print
(
response
);
Update a post with PUT #
final response = await
HosteDay.client.put
('/api/posts/1
'
,withAuth: true,
body: <String, dynamic>{
'title': 'Updated post title',
'body': 'Updated post body.',
},
);
print
(
response
);
Partially update a post with PATCH #
final response = await
HosteDay.client.patch
('/api/posts/1
'
,withAuth: true,
body: <String, dynamic>{
'status': 'published',
},
);
print
(
response
);
Delete a post #
await
HosteDay.client.delete
('/api/posts/1
'
,withAuth:
true
,
);
Raw request #
final response = await
HosteDay.client.request
(
method: 'POST',
path: '/api/posts',
withAuth: true,
body: <String, dynamic>{
'title': 'Created with raw request',
},
);
print
(
response
);
Request timeout #
If using the lower-level HTTP client:
final response = await
HosteDay.client.http.get
('/api/posts
'
,withAuth: true,
timeout: const Duration(seconds: 10
)
,
);
Custom model example #
You can convert API responses into your own models.
class Post {
final int id;
final String title;
final String? body;
const Post({
required this.id,
required this.title,
this.body,
});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
id: int.parse(json['id'].toString()),
title: json['title'].toString(),
body: json['body']?.toString(),
);
}
}
Load posts:
final response = await
HosteDay.client.get
('/api/posts
'
,withAuth: true,
);
final data = response['data'];
final posts = data is List
? data
.whereType<Map>()
.map((item) => Post.fromJson(Map<String, dynamic>.from(item)))
.toList()
: <Post>[];
print(posts.length);
Load one post:
final response = await
HosteDay.client.get
('/api/posts/1
'
,withAuth: true,
);
final post = Post.fromJson(
Map<String, dynamic>.from(response['data'] as Map),
);
print(post.title);
Realtime #
HosteDay realtime is available through:
HosteDay.realtime
or:
HosteDay.client.realtime
Connect realtime #
await
HosteDay.connectRealtime
();
Disconnect realtime #
await
HosteDay.disconnectRealtime
();
Check realtime status #
final initialized = HosteDay.realtime.isConnected;
print(initialized);
Inspect realtime URL #
print
(
HosteDay
.
config
.
realtimeUrl
);
Example:
wss://ws3.hosteday.com:443/app/YOUR_REALTIME_APP_KEY
Listen to a public channel #
Public channels do not require a signed-in user.
await
HosteDay.connectRealtime
();
final subscription = await
HosteDay.realtime.listenPublic
(
channel: 'posts',
event: 'PostCreated',
onEvent: (event) {
print(event.name);
print(event.channelName);
print(event.payload);
},
);
Cancel when not needed:
await
subscription.cancel
();
Listen to a private channel #
Private channels require a signed-in user.
await
HosteDay.auth.signInWithEmailAndPassword
(
email: 'user@example.com',
password: 'password123',
);
await HosteDay.connectRealtime();
final subscription = await HosteDay.realtime.listenPrivate(
channel: 'orders.1',
event: 'OrderUpdated',
onEvent: (event) {
print(event.payload);
print(event.userId);
print(event.userName);
print(event.userEmail);
},
);
The SDK automatically adds the private- prefix when needed.
Example:
channel: '
orders.1'
becomes:
private-orders.1
Listen to a presence channel #
Presence channels require a signed-in user.
await
HosteDay.connectRealtime
();
await
HosteDay.realtime.listenPresence
(
channel: 'chat.room.1',
event: 'MessageSent',
onEvent: (event) {
print(event.payload);
},
);
The SDK automatically adds the presence- prefix when needed.
Listen for presence members joining #
await
HosteDay.realtime.listenPresenceMemberAdded
(
channel: 'chat.room.1',
onEvent: (event) {
print('Member joined');
print(event.payload);
},
);
Listen for presence members leaving #
await
HosteDay.realtime.listenPresenceMemberRemoved
(
channel: 'chat.room.1',
onEvent: (event) {
print('Member left');
print(event.payload);
},
);
Listen to a private encrypted channel #
Private encrypted channels require backend support.
await
HosteDay.connectRealtime
();
await
HosteDay.realtime.listenPrivateEncrypted
(
channel: 'secure.orders.1',
event: 'SecureOrderUpdated',
onEvent: (event) {
print(event.payload);
},
);
Unified realtime listener #
Use listen(...) when the channel type is dynamic.
final subscription = await
HosteDay.realtime.listen
(
channel: 'orders.1',
event: 'OrderUpdated',
type: HosteDayChannelType.private,
onEvent: (event) {
print(event.payload);
},
);
Supported types:
HosteDayChannelType.public
HosteDayChannelType.private
HosteDayChannelType.presence
HosteDayChannelType.privateEncrypted
Unsubscribe from one channel #
await
HosteDay.realtime.unsubscribe
('orders.1
'
,type:
HosteDayChannelType
.
private
,
);
Publishing realtime events #
Publish a public event #
final response = await
HosteDay.client.publishPublicEvent
(
channel: 'posts',
event: 'PostCreated',
payload: <String, dynamic>{
'post': {
'id': 1,
'title': 'New realtime post',
},
},
);
print
(
response
);
Publish a private event #
Requires a signed-in user.
final response = await
HosteDay.client.publishPrivateEvent
(
channel: 'orders.1',
event: 'OrderUpdated',
payload: <String, dynamic>{
'order_id': 1,
'status': 'paid',
},
);
print
(
response
);
The SDK automatically adds the private- prefix when needed.
Publish a presence event #
Requires a signed-in user.
final response = await
HosteDay.client.publishPresenceEvent
(
channel: 'chat.room.1',
event: 'MemberTyping',
payload: <String, dynamic>{
'typing': true,
},
);
print
(
response
);
The SDK automatically adds the presence- prefix when needed.
Realtime event object #
Realtime callbacks receive a HosteDayRealtimeEvent.
await
HosteDay.realtime.listenPublic
(
channel: 'posts',
event: 'PostCreated',
onEvent: (event) {
print(event.name);
print(event.channelName);
print(event.payload);
print(event.data);
print(event.message);
print(event.user);
print(event.userId);
print(event.userName);
print(event.userEmail);
},
);
Access payload fields directly:
final title = event['title'];
final post = event['post'];
Check if a key exists:
if (event.containsKey('post')) {
print(event['post']);
}
Error handling #
Most API and network failures throw HosteDayException.
try {
final response = await HosteDay.client.get(
'/api/posts',
withAuth: true,
);
print(response);
} on HosteDayException catch (error) {
print(error.message);
print(error.statusCode);
print(error.displayMessage);
} catch (error) {
print(error);
}
Validation errors #
Laravel-style validation responses are available through validationErrors.
Example API response:
{
"message": "The email field is required.",
"errors": {
"email": [
"The email field is required."
],
"password": [
"The password field is required."
]
}
}
Handle it:
try {
await HosteDay.auth.signInWithEmailAndPassword(
email: email,
password: password,
);
} on HosteDayException catch (error) {
final emailError = error.firstErrorFor('email');
final passwordError = error.firstErrorFor('password');
print(emailError);
print(passwordError);
print(error.displayMessage);
}
Useful helpers:
error.hasValidationErrors;error.isValidationError;error.isUnauthenticated;error.isForbidden;error
.isNotFound;error.isServerError;error.firstValidationError;error.displayMessage;
Configuration options #
Required options #
| Option | Description |
|---|---|
HosteDayOptionKeys.projectDomain |
Your HosteDay project domain. Example: your-project.hosteday.com. |
Optional options #
| Option | Description |
|---|---|
HosteDayOptionKeys.apiBaseUrl |
Custom API base URL. Usually not needed. |
HosteDayOptionKeys.baseUrl |
Alias for apiBaseUrl. |
HosteDayOptionKeys.projectApiKey |
Project API key sent as X-Api-Token. |
HosteDayOptionKeys.projectApiKeyHeader |
Custom project API key header name. Default: X-Api-Token. |
HosteDayOptionKeys.realtimeAppKey |
HosteDay realtime app key. |
HosteDayOptionKeys.realtimeHost |
Realtime WebSocket host. |
HosteDayOptionKeys.realtimeScheme |
ws or wss. Default: wss. |
HosteDayOptionKeys.realtimePort |
Realtime port. Default: 443 for wss. |
Fixed HosteDay endpoints #
Authentication and user endpoints are fixed by HosteDay and are not configurable from
initializeApp.
The SDK manages these internally:
POST /api/auth/login
POST /api/auth/register
POST /api/auth/forgot-password
GET /api/user
PUT /api/user
POST /api/user/avatar
DELETE /api/user
POST /api/logout
POST /api/email/verification-notification
Realtime publish and authorization endpoints are also handled internally:
POST /api/realtime/events/public
POST /api/realtime/events/private
POST /api/realtime/events/presence
POST /api/broadcasting/auth-manual
Use HosteDay.client.get, post, put, patch, and delete for your own project endpoints such
as:
/api/posts
/api/orders
/api/products
Expected backend responses #
Sign in response #
Recommended response:
{
"access_token": "1|example-token",
"token_type": "Bearer",
"expires_in": null,
"user": {
"id": 1,
"name": "Mustafa",
"email": "mustafa@example.com",
"email_verified_at": "2026-07-08T10:00:00.000000Z",
"avatar_url": null
}
}
The SDK also accepts:
{
"token": "1|example-token",
"user": {
"id": 1,
"name": "Mustafa",
"email": "mustafa@example.com"
}
}
Or nested data:
{
"data": {
"access_token": "1|example-token",
"user": {
"id": 1,
"name": "Mustafa",
"email": "mustafa@example.com"
}
}
}
Supported token keys #
access_token
accessToken
token
Supported user ID keys #
id
user_id
uuid
Supported user name keys #
name
display_name
displayName
full_name
fullName
Supported avatar keys #
avatar_url
avatarUrl
avatar
photo_url
photoUrl
image
image_url
imageUrl
Email verification #
The SDK can detect verified users from:
email_verified
emailVerified
email_verified_at
emailVerifiedAt
Full quick example #
import 'package:flutter/material.dart';
import 'package:hosteday_flutter/hosteday_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await HosteDay.initializeApp(
options: const <String, Object?>{
HosteDayOptionKeys.projectDomain: 'your-project.hosteday.com',
HosteDayOptionKeys.projectApiKey: 'YOUR_PROJECT_API_KEY',
},
authStorage: HosteDaySharedPreferencesAuthStorage(),
);
runApp(const App());
}
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: StreamBuilder<HosteDayUser?>(
stream: HosteDay.auth.authStateChanges(),
initialData: HosteDay.auth.currentUser,
builder: (context, snapshot) {
final user = snapshot.data;
if (user == null) {
return const SignInPage();
}
return HomePage(user: user);
},
),
);
}
}
class SignInPage extends StatefulWidget {
const SignInPage({super.key});
@override
State<SignInPage> createState() => _SignInPageState();
}
class _SignInPageState extends State<SignInPage> {
final email = TextEditingController();
final password = TextEditingController();
bool loading = false;
@override
void dispose() {
email.dispose();
password.dispose();
super.dispose();
}
Future<void> signIn() async {
setState(() => loading = true);
try {
await HosteDay.auth.signInWithEmailAndPassword(
email: email.text.trim(),
password: password.text,
);
} on HosteDayException catch (error) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error.displayMessage)),
);
}
} finally {
if (mounted) {
setState(() => loading = false);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sign in'),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: <Widget>[
TextField(
controller: email,
decoration: const InputDecoration(
labelText: 'Email',
),
),
TextField(
controller: password,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
),
),
const SizedBox(height: 16),
FilledButton(
onPressed: loading ? null : signIn,
child: Text(loading ? 'Signing in...' : 'Sign in'),
),
],
),
),
);
}
}
class HomePage extends StatelessWidget {
final HosteDayUser user;
const HomePage({
required this.user,
super.key,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('HosteDay'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.logout),
onPressed: () {
HosteDay.auth.signOut();
},
),
],
),
body: Center(
child: Text(
'Welcome ${user.displayName ?? user.email ?? user.id}',
),
),
);
}
}
Example app #
A complete Flutter example is included in this repository:
example/
Useful files:
example/README.md
example/EXAMPLE.md
example/lib/main.dart
example/lib/app.dart
The example demonstrates:
- SDK initialization.
- Session persistence.
- Sign in.
- Registration.
- Password reset email.
- Auth gate.
- Current user display.
- Profile update.
- Email verification request.
- Posts API example.
- Realtime listening.
- Realtime publishing.
Migration notes #
Old project API token name #
Old name:
HosteDayOptionKeys.apiToken
New name:
HosteDayOptionKeys.projectApiKey
Reason:
apiToken can be confused with the signed-in user's access token.
Old realtime key name #
Old name:
HosteDayOptionKeys.pusherKey
New name:
HosteDayOptionKeys.realtimeAppKey
Reason:
HosteDay uses a Pusher-compatible protocol, but developers do not need a Pusher account.
Old global class name #
Old name:
Hosteday
New name:
HosteDay
Use HosteDay in all new code.
Security notes #
- Do not hard-code production project API keys in public repositories.
- Do not expose sensitive user access tokens manually.
- Use
withAuth: truefor protected requests. - Validate authorization on the backend.
- Validate tenant ownership on the backend.
- Use private or presence realtime channels for sensitive data.
- Do not rely on client-side checks as a security boundary.
- Revoke or invalidate user tokens server-side when needed.
License #
This project is licensed under the MIT License.