gotify_flutter 1.1.1
gotify_flutter: ^1.1.1 copied to clipboard
Android Flutter client for Gotify push notifications — WebSocket receive, REST send, local alerts, and background delivery. No Firebase required.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:gotify_flutter/gotify_flutter.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
GotifyBackgroundService.initCommunicationPort();
runApp(const GotifyExampleApp());
}
class GotifyExampleApp extends StatelessWidget {
const GotifyExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'gotify_flutter example',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF0B6E4F)),
useMaterial3: true,
),
home: const _RootPage(),
);
}
}
class _RootPage extends StatefulWidget {
const _RootPage();
@override
State<_RootPage> createState() => _RootPageState();
}
class _RootPageState extends State<_RootPage> {
final _gotify = GotifyPushClient();
bool _checking = true;
bool _loggedIn = false;
@override
void initState() {
super.initState();
_restore();
}
Future<void> _restore() async {
final session = await _gotify.restoreSession();
if (!mounted) return;
setState(() {
_loggedIn = session != null;
_checking = false;
});
}
@override
void dispose() {
_gotify.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_checking) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
if (!_loggedIn) {
return LoginPage(
gotify: _gotify,
onLoggedIn: () => setState(() => _loggedIn = true),
);
}
return DemoPage(
gotify: _gotify,
onLoggedOut: () => setState(() => _loggedIn = false),
);
}
}
class LoginPage extends StatefulWidget {
const LoginPage({super.key, required this.gotify, required this.onLoggedIn});
final GotifyPushClient gotify;
final VoidCallback onLoggedIn;
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _urlController =
TextEditingController(text: 'https://gotify.example.com');
final _userController = TextEditingController(text: 'admin');
final _passController = TextEditingController();
final _clientController = TextEditingController(text: 'gotify_flutter example');
bool _busy = false;
String? _error;
Future<void> _login() async {
setState(() {
_busy = true;
_error = null;
});
try {
await widget.gotify.login(
baseUrl: _urlController.text.trim(),
username: _userController.text.trim(),
password: _passController.text,
clientName: _clientController.text.trim().isEmpty
? 'gotify_flutter example'
: _clientController.text.trim(),
);
await widget.gotify.ensureSendApplication(name: 'gotify_flutter Example');
widget.onLoggedIn();
} catch (e) {
if (mounted) setState(() => _error = '$e');
} finally {
if (mounted) setState(() => _busy = false);
}
}
@override
void dispose() {
_urlController.dispose();
_userController.dispose();
_passController.dispose();
_clientController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Connect to Gotify')),
body: ListView(
padding: const EdgeInsets.all(24),
children: [
TextField(
controller: _urlController,
decoration: const InputDecoration(
labelText: 'Server URL',
hintText: 'https://gotify.example.com',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.url,
autocorrect: false,
),
const SizedBox(height: 12),
TextField(
controller: _userController,
decoration: const InputDecoration(
labelText: 'Username',
border: OutlineInputBorder(),
),
autocorrect: false,
),
const SizedBox(height: 12),
TextField(
controller: _passController,
decoration: const InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(),
),
obscureText: true,
),
const SizedBox(height: 12),
TextField(
controller: _clientController,
decoration: const InputDecoration(
labelText: 'Client name',
border: OutlineInputBorder(),
),
),
if (_error != null) ...[
const SizedBox(height: 16),
Text(_error!, style: TextStyle(color: Theme.of(context).colorScheme.error)),
],
const SizedBox(height: 24),
FilledButton(
onPressed: _busy ? null : _login,
child: Text(_busy ? 'Connecting…' : 'Connect'),
),
],
),
);
}
}
class DemoPage extends StatefulWidget {
const DemoPage({super.key, required this.gotify, required this.onLoggedOut});
final GotifyPushClient gotify;
final VoidCallback onLoggedOut;
@override
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
final _messages = <GotifyMessage>[];
GotifyStreamState _state = GotifyStreamState.disconnected;
String? _error;
bool _ready = false;
@override
void initState() {
super.initState();
_boot();
}
Future<void> _boot() async {
try {
widget.gotify.onConnectionState.listen((s) {
if (mounted) setState(() => _state = s);
});
widget.gotify.onMessage.listen((m) {
if (!mounted) return;
setState(() => _messages.insert(0, m));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${m.displayTitle}: ${m.message}'),
behavior: SnackBarBehavior.floating,
action: SnackBarAction(
label: 'OK',
onPressed: () {},
),
),
);
});
// Required for alerts after Home / swipe-away. Android will show a quiet
// ongoing status row (customizable title/text — cannot be fully removed).
await widget.gotify.startListening(
keepAliveInBackground: true,
keepAliveNotificationTitle: 'gotify_flutter example',
keepAliveNotificationText: 'Running in background',
);
if (mounted) setState(() => _ready = true);
} catch (e) {
if (mounted) setState(() => _error = '$e');
}
}
Future<void> _send() async {
await widget.gotify.sendMessage(
title: 'Demo',
message: 'Hello from gotify_flutter example',
priority: 5,
);
}
Future<void> _logout() async {
await widget.gotify.logout();
widget.onLoggedOut();
}
@override
Widget build(BuildContext context) {
final connected = _state == GotifyStreamState.connected;
return Scaffold(
appBar: AppBar(
title: const Text('gotify_flutter'),
actions: [
Padding(
padding: const EdgeInsets.only(right: 8),
child: Center(
child: Text(
connected ? 'Connected' : _state.name,
style: TextStyle(color: connected ? Colors.greenAccent : null),
),
),
),
IconButton(
tooltip: 'Logout',
onPressed: _logout,
icon: const Icon(Icons.logout),
),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: _ready && _error == null ? _send : null,
icon: const Icon(Icons.send),
label: const Text('Send demo'),
),
body: _error != null
? Center(child: Text(_error!, textAlign: TextAlign.center))
: !_ready
? const Center(child: CircularProgressIndicator())
: _messages.isEmpty
? const Center(
child: Text(
'Listening… send a demo notification\n(in-app snackbar + system alert)',
textAlign: TextAlign.center,
),
)
: ListView.separated(
itemCount: _messages.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (_, i) => ListTile(
title: Text(_messages[i].displayTitle),
subtitle: Text(_messages[i].message),
),
),
);
}
}