flutter_lifecycle_guard 1.0.0-dev.1
flutter_lifecycle_guard: ^1.0.0-dev.1 copied to clipboard
Reliable background execution for Flutter. Automatically manages foreground services, WorkManager, and iOS background tasks with built-in OEM battery saver evasion.
import 'package:flutter/material.dart';
import 'package:flutter_lifecycle_guard/flutter_lifecycle_guard.dart';
import 'verification_harness_page.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await LifecycleGuard.initialize(
config: LifecycleGuardConfig(
androidNotificationChannelId: 'my_app_bg',
androidNotificationChannelName: 'Background Sync',
enableOemGuidance: true,
autoRequestPermissions: true,
enableDetailedLogging: true,
),
);
runApp(const MyApp());
}
// IMPORTANT: This must be a top-level function for background execution
@pragma('vm:entry-point')
Future<void> syncWithServer(ProgressCallback updateProgress) async {
final items = await getUnsyncedItems();
for (var i = 0; i < items.length; i++) {
await uploadItem(items[i]);
updateProgress((i + 1) / items.length);
}
}
// Top-level heartbeat function
@pragma('vm:entry-point')
Future<void> periodicSync() async {
final items = await getUnsyncedItems();
if (items.isNotEmpty) {
for (final item in items) {
await uploadItem(item);
}
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(home: const HomePage());
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
GuardTaskHandle? _currentUpload;
String _status = 'Idle';
double _progress = 0;
@override
void initState() {
super.initState();
_setupHeartbeat();
_checkOemGuidance();
}
Future<void> _setupHeartbeat() async {
await LifecycleGuard.instance.scheduleHeartbeat(
HeartbeatConfig(
id: 'periodic_sync',
interval: const Duration(minutes: 30),
work: periodicSync,
constraints: TaskConstraints(requiresNetwork: true),
),
);
}
Future<void> _checkOemGuidance() async {
// Wait for the widget to be built
await Future.delayed(const Duration(seconds: 2));
final guide = LifecycleGuard.instance.oemGuide;
if (guide != null && guide.severity.index >= OemSeverity.high.index) {
if (mounted) {
await LifecycleGuard.instance.showBatteryOptimizationSettings(
context: context,
showExplanationFirst: true,
);
}
}
}
Future<void> _startUpload() async {
final handle = await LifecycleGuard.instance.execute(
TaskDescriptor(
id: 'upload_photos_${DateTime.now().millisecondsSinceEpoch}',
urgency: TaskUrgency.immediate,
work: syncWithServer,
notification: TaskNotification(
title: 'Uploading photos',
body: 'Syncing your photos to the cloud',
),
constraints: TaskConstraints(requiresNetwork: true),
serviceType: ForegroundServiceType.dataSync,
retryPolicy: RetryPolicy(maxRetries: 3),
onKilled: () {
// Save progress for retry
saveUploadCheckpoint();
},
),
);
setState(() => _currentUpload = handle);
handle.statusStream.listen((status) {
if (mounted) {
setState(() {
_status = status.message;
_progress = status.progress ?? 0;
});
}
if (status.state == TaskState.completed) {
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Upload complete!')));
}
}
});
}
Future<void> _cancelUpload() async {
await _currentUpload?.cancel();
setState(() {
_currentUpload = null;
_status = 'Cancelled';
_progress = 0;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('LifecycleGuard Demo')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Text(
'Status: $_status',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
LinearProgressIndicator(value: _progress),
const SizedBox(height: 4),
Text('${(_progress * 100).toStringAsFixed(0)}%'),
],
),
),
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: _currentUpload == null ? _startUpload : null,
icon: const Icon(Icons.cloud_upload),
label: const Text('Start Background Upload'),
),
const SizedBox(height: 8),
OutlinedButton.icon(
onPressed: _currentUpload != null ? _cancelUpload : null,
icon: const Icon(Icons.cancel),
label: const Text('Cancel Upload'),
),
const SizedBox(height: 24),
const Divider(),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const VerificationHarnessPage(),
),
);
},
icon: const Icon(Icons.fact_check),
label: const Text('Open QA Verification Harness'),
),
const SizedBox(height: 16),
Text('OEM Info', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
Builder(
builder: (context) {
final guide = LifecycleGuard.instance.oemGuide;
if (guide == null) {
return const Text('No OEM-specific restrictions detected.');
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Manufacturer: ${guide.manufacturer}'),
Text('Severity: ${guide.severity.name}'),
Text(guide.description),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () {
LifecycleGuard.instance.showBatteryOptimizationSettings(
context: context,
);
},
child: const Text('Open Battery Settings'),
),
],
);
},
),
],
),
),
);
}
}
// Stub functions for the example
Future<List<String>> getUnsyncedItems() async => ['item1', 'item2', 'item3'];
Future<void> uploadItem(String item) async =>
await Future.delayed(const Duration(seconds: 2));
void saveUploadCheckpoint() {}