flutter_background_service icon indicating copy to clipboard operation
flutter_background_service copied to clipboard

Kill background services when closes app completely

Open alejogonza opened this issue 4 months ago • 0 comments

I need my service to keep sending events in the background, but when it closes completely it also closes the services, I have done everything, but I can't get it, can you give me any idea of what is wrong here?

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import 'dart:convert';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

final logger = Logger('LocationApp');

Future<void> initializeService() async {
  final service = FlutterBackgroundService();

  // Verifica si el servicio ya está en ejecución
  if (await service.isRunning()) {
    return;
  }

  // Inicializa el plugin de notificaciones
  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  // Configura el canal de notificación para Android
  const AndroidNotificationChannel channel = AndroidNotificationChannel(
    'my_foreground',
    'Rastreando ubicación',
    description: 'Notificaciones del servicio de rastreo de ubicación',
    importance: Importance.high,
  );

  // Crea el canal de notificación
  await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>()
      ?.createNotificationChannel(channel);

  await service.configure(
    androidConfiguration: AndroidConfiguration(
      onStart: onStart,
      autoStart: false,
      isForegroundMode: true,
      notificationChannelId: 'my_foreground',
      initialNotificationTitle: 'Rastreando ubicación',
      initialNotificationContent: 'Servicio en ejecución',
      foregroundServiceNotificationId: 888,
    ),
    iosConfiguration: IosConfiguration(
      autoStart: false,
      onForeground: onStart,
      onBackground: onIosBackground,
    ),
  );
}

@pragma('vm:entry-point')
Future<bool> onIosBackground(ServiceInstance service) async {
  return true;
}

@pragma('vm:entry-point')
void onStart(ServiceInstance service) async {
  if (service is AndroidServiceInstance) {
    service.on('setAsForeground').listen((event) {
      service.setAsForegroundService();
    });

    service.on('setAsBackground').listen((event) {
      service.setAsBackgroundService();
    });
  }

  service.on('stopService').listen((event) {
    service.stopSelf();
  });
  Timer.periodic(const Duration(seconds: 10), (timer) async {
    if (service is AndroidServiceInstance) {
      if (await service.isForegroundService()) {
        service.setForegroundNotificationInfo(
          title: "Rastreando ubicación",
          content: "Enviando ubicación cada 10 segundos",
        );
      }
    }

    await sendLocationToApi();

    service.invoke('update');
  });
}

Future<void> sendLocationToApi() async {
  try {
    Position position = await Geolocator.getCurrentPosition(
      locationSettings: const LocationSettings(
        accuracy: LocationAccuracy.high,
        timeLimit: Duration(seconds: 5),
      ),
    );

    final url = Uri.parse('https://api.com/test');
    final response = await http.post(
      url,
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({
        'latitude': position.latitude,
        'longitude': position.longitude,
        'timestamp': position.timestamp.toIso8601String(),
      }),
    );

    logger
        .info('Respuesta de la API: ${response.statusCode} - ${response.body}');

    if (response.statusCode != 200) {
      logger.warning('Error al enviar ubicación: ${response.body}');
    } else {
      logger.info('Ubicación enviada con éxito: ${response.body}');
    }
  } catch (e) {
    logger.severe('Error al obtener o enviar ubicación: $e');
  }
}

Future<bool> requestLocationPermissions() async {
  var locationStatus = await Permission.locationWhenInUse.request();
  if (locationStatus.isGranted) {
    var backgroundStatus = await Permission.locationAlways.request();
    return backgroundStatus.isGranted;
  }
  return false;
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  _setupLogging();

  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();
  await flutterLocalNotificationsPlugin.initialize(
    const InitializationSettings(
      android: AndroidInitializationSettings('@mipmap/ic_launcher'),
    ),
  );

  await Geolocator.requestPermission();

  if (await requestLocationPermissions()) {
    await initializeService();
    runApp(const MyApp());
  } else {
    runApp(const MyAppWithoutPermissions());
  }
}

void _setupLogging() {
  Logger.root.level = Level.ALL;
  Logger.root.onRecord.listen((record) {
    print('${record.level.name}: ${record.time}: ${record.message}');
  });
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Ubicación en Segundo Plano',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const LocationScreen(),
    );
  }
}

class MyAppWithoutPermissions extends StatelessWidget {
  const MyAppWithoutPermissions({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Permisos Denegados',
      theme: ThemeData(primarySwatch: Colors.red),
      home: const Scaffold(
        body: Center(
          child: Text('Se requieren permisos de ubicación para usar esta app.'),
        ),
      ),
    );
  }
}

class LocationScreen extends StatefulWidget {
  const LocationScreen({super.key});

  @override
  LocationScreenState createState() => LocationScreenState();
}

class LocationScreenState extends State<LocationScreen>
    with WidgetsBindingObserver {
  bool _isServiceRunning = false;
  final FlutterBackgroundService _service = FlutterBackgroundService();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _checkServiceStatus();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    if (state == AppLifecycleState.detached) {
      print('App en estado detached');
      await _stopService();
      print('App cerrada');
      _service.invoke('kill');
    }
  }

  Future<void> _checkServiceStatus() async {
    bool isRunning = await _service.isRunning();
    setState(() {
      _isServiceRunning = isRunning;
    });
  }

  Future<void> _toggleService() async {
    if (_isServiceRunning) {
      await _stopService();
    } else {
      await _startService();
    }
    await _checkServiceStatus();
  }

  Future<void> _startService() async {
    await _service.startService();
  }

  Future<void> _stopService() async {
    _service.invoke("stopService");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Ubicación en Segundo Plano')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(_isServiceRunning
                ? 'El servicio está activo y enviando ubicación'
                : 'El servicio está detenido'),
            ElevatedButton(
              onPressed: _toggleService,
              child: Text(
                  _isServiceRunning ? 'Detener servicio' : 'Iniciar servicio'),
            ),
          ],
        ),
      ),
    );
  }
}

alejogonza avatar Oct 06 '24 22:10 alejogonza