flutter_traccar_api 0.1.0
flutter_traccar_api: ^0.1.0 copied to clipboard
A comprehensive Dart package for integrating with Traccar GPS tracking server. Provides authentication, device management, position tracking, real-time WebSocket updates, and reporting capabilities.
import 'package:flutter/material.dart';
import 'package:flutter_traccar_api/flutter_traccar_api.dart';
import 'realtime_dashboard.dart';
void main() {
runApp(const TraccarApiExampleApp());
}
class TraccarApiExampleApp extends StatelessWidget {
const TraccarApiExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Traccar API Example',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const LoginScreen(),
);
}
}
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
final _serverUrlController = TextEditingController(text: 'https://demo.traccar.org');
final _api = FlutterTraccarApi();
bool _isLoading = false;
@override
void initState() {
super.initState();
_initializeApi();
}
Future<void> _initializeApi() async {
await _api.initialize();
// Check for cached credentials
if (await _api.hasCachedCredentials()) {
final success = await _api.refreshAuth();
if (success && mounted) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const DashboardScreen()),
);
}
}
}
Future<void> _login() async {
if (!_formKey.currentState!.validate()) return;
setState(() => _isLoading = true);
try {
final success = await _api.login(
username: _usernameController.text,
password: _passwordController.text,
serverUrl: _serverUrlController.text,
);
if (success && mounted) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const DashboardScreen()),
);
} else {
_showError('Login failed. Please check your credentials.');
}
} catch (e) {
_showError('Error: $e');
} finally {
setState(() => _isLoading = false);
}
}
void _showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message), backgroundColor: Colors.red),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Traccar API Login'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.location_on, size: 80, color: Colors.blue),
const SizedBox(height: 32),
TextFormField(
controller: _serverUrlController,
decoration: const InputDecoration(
labelText: 'Server URL',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.cloud),
),
validator: (value) => value?.isEmpty == true ? 'Required' : null,
),
const SizedBox(height: 16),
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(
labelText: 'Username',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.person),
),
validator: (value) => value?.isEmpty == true ? 'Required' : null,
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
validator: (value) => value?.isEmpty == true ? 'Required' : null,
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isLoading ? null : _login,
child: _isLoading
? const CircularProgressIndicator()
: const Text('Login'),
),
),
],
),
),
),
);
}
@override
void dispose() {
_usernameController.dispose();
_passwordController.dispose();
_serverUrlController.dispose();
super.dispose();
}
}
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});
@override
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
final _api = FlutterTraccarApi();
List<Device> _devices = [];
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadDevices();
}
Future<void> _loadDevices() async {
try {
final devices = await _api.getDevices();
if (mounted) {
setState(() {
_devices = devices;
_isLoading = false;
});
}
} catch (e) {
if (mounted) {
setState(() => _isLoading = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error loading devices: $e')),
);
}
}
}
Future<void> _logout() async {
await _api.logout();
if (mounted) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const LoginScreen()),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Dashboard - ${_api.currentUsername ?? 'User'}'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
actions: [
IconButton(
icon: const Icon(Icons.timeline),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => RealtimeDashboard(api: _api),
),
);
},
tooltip: 'Real-time Dashboard',
),
IconButton(
icon: const Icon(Icons.logout),
onPressed: _logout,
),
],
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: _devices.isEmpty
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.devices, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('No devices found'),
],
),
)
: ListView.builder(
itemCount: _devices.length,
itemBuilder: (context, index) {
final device = _devices[index];
return Card(
margin: const EdgeInsets.all(8.0),
child: ListTile(
leading: CircleAvatar(
backgroundColor: device.status == 'online'
? Colors.green
: Colors.grey,
child: const Icon(Icons.device_hub, color: Colors.white),
),
title: Text(device.name ?? 'Unknown Device'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Status: ${device.status ?? 'Unknown'}'),
if (device.lastUpdate != null)
Text('Last Update: ${device.lastUpdate}'),
],
),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {
// Navigate to device details
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DeviceDetailScreen(device: device),
),
);
},
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _loadDevices,
child: const Icon(Icons.refresh),
),
);
}
}
class DeviceDetailScreen extends StatelessWidget {
final Device device;
const DeviceDetailScreen({super.key, required this.device});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(device.name ?? 'Unknown Device'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Device Information',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 16),
_buildInfoRow('Name', device.name ?? 'Unknown'),
_buildInfoRow('Status', device.status ?? 'Unknown'),
_buildInfoRow('Unique ID', device.uniqueId ?? 'Unknown'),
if (device.lastUpdate != null)
_buildInfoRow('Last Update', device.lastUpdate.toString()),
],
),
),
),
],
),
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 100,
child: Text(
'$label:',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
Expanded(child: Text(value)),
],
),
);
}
}