Closed omejiasq closed 6 days ago
Can I update the data in the smartphone_blocked field of the extras object at the time of sending the location to the server so that it sends this correct value perhaps by adding something like this inside the onLocation function?
No. Config.extras (and any Config option) are changed only by using .setConfig (see api docs). The updated extras will be applied only to the next recorded location.
by the time .onLocation has been fired, the http request has likely already been initiated.
you could, of course, force a “next” location to be recorded by calling .getCurrentPosition.
Thank you! I have read many documents of the great number of functions that the API has with good documentation, but this is something very complex because sometimes I do not find an example of how to call the getCurrentPosition function that you tell me inside the onLocation function. I have also seen the source code examples of the application that you have and I do not see one that calls this getCurrentPosition function inside onLocation. I put this code inside onLocation: bg.Location location = await bg.BackgroundGeolocation.getCurrentPosition( extras: { //"userId": userId, "smartphone_blocked": _smartphoneBlocked, //"tipos_viajes": tiposViajes, //"company": company, // "ayuda": ayuda, "band": "1" }); But it automatically sends locations every second to backend and then blocks the smartphone. Also, after removing this code that I mentioned, and based on what you mentioned and reading the API, I put this code inside the onLocation function to see if it updated the parameter that I mentioned: await bg.BackgroundGeolocation.setConfig(bg.Config( autoSync: true, extras: {"smartphone_blocked": _smartphoneBlocked, "band": "1"}, //url: "http://tracker.transistorsoft.com/locations/transistor-flutter-test", //params: deviceParams )); And this does nothing. Would you be so kind as to please tell me how I can call this that you say .getCurrentPosition?
When you call .getCurrentPosition, it does not return the first location it receives. It fetches (default) 3 locations, letting the GPS radio "heat up". Each of those locations is fired to the .onLocation
event having location.sample
set to true
. Location "samples" or NOT sent to the HTTP service.
See getCurrentPosition options.samples
to control the number of samples recorded.
The options.extras
provided to .getCurrentPosition
are NOT permanently applied to Config.extras
. These are applied only for this .getCurrentPosition
request.
await bg.BackgroundGeolocation.setConfig(bg.Config(
"extras": {
"foo": "bar"
}
));
await bg.BackgroundGeolocation.getCurrentPosition({
"samples": 1
});
I understand what you're saying and I've already looked at the getCurrentPosition documentation again and I understand that I only need to set the samples attribute as you tell me and you gave me the example. I also set the smartphone state variable inside the extras object of the setConfig function to change the correct smartphone state and now the onLocation function looks like this:
static void _onLocation(bg.Location location0) async {
await checkScreenLocked(); // Ensure state synchronization
//location.extras['smartphone_blocked'] = _smartphoneBlocked;
print('[location] before field blocked: $location0');
await bg.BackgroundGeolocation.setConfig(bg.Config(
//autoSync: true,
extras: {"smartphone_blocked": _smartphoneBlocked, "band": "1"},
));
await bg.BackgroundGeolocation.getCurrentPosition(
samples: 1,
);
//print('[location] after field blocked: $location');
}
But I don't understand then what the getCurrentPosition function is for in this code. Excuse me for my questions, I have read the API quite a bit, tried many examples but I do not understand this process. My change that you recommend should I make in the onLocation function using these 2 functions that I mentioned? What else should I do?
Do NOT call .getCurrentPosition within .onLocation!! That will create an infinite loop!
So, where can i call getCurrenPosition how you told me: "force a “next” location to be recorded by calling .getCurrentPosition"? Beside of call getCurrentPosition, i suppose, do i need to send this data returned by getCurrentPosition, set my variables in extra field with setConfig, or which are the general steps for "force" a "next" location?
If it were me, I’d initiate a repeating timer to check the value at some frequency. When the value changes, only then would I do my thing with .setConfig / .getCurrentPosition
Much easier to understand with this explanation of yours my friend! So finally, inside the timer I imagine with a timer that runs every minute "won't this consume important battery of the smartphone?", I would call the getCurrentPosition function, and with the data of this position I compare against the previous position and with that if the state of the smartphone changes, then I use setConfig to change the value of the state of the smartphone? if so I thank you immensely for your help and I will not bother you anymore.
And only run the timer while your app is in the foreground. Actually, why aren’t you just setting up an app lifecycle listener to do this when the app goes to background?
Hi, I did this what you told me:"setting up an app lifecycle listener to do this when the app goes to background", and this seems update good and this seems to update the state of the _smartphoneBlocked variable fine, but this value never changes when sent by your plugin to my server, it always sends me a :0. For example this was my listener:
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
isStateSynced = false; // Force new synchronization on lifecycle changes
if (state == AppLifecycleState.paused || state == AppLifecycleState.resumed) {
checkScreenLocked();
}
}
The code I'm trying for this service is the following:
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg;
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:app_movil_protegos2/api.dart';
class LocationService with WidgetsBindingObserver {
static const platform = MethodChannel('co.itfusion.protegos/screen_state');
static String _smartphoneBlocked = '1'; // Iniciar como bloqueado por defecto
static bool isObserverAdded = false; // Bandera para evitar múltiples observadores
static bool isStateSynced = false; // Bandera para sincronizar estado de pantalla
/// Inicializar el servicio y configurar los listeners
static Future<void> initPlatformState(BuildContext context) async {
try {
if (!isObserverAdded) {
WidgetsBinding.instance.addObserver(LocationService());
isObserverAdded = true;
}
SharedPreferences prefs = await SharedPreferences.getInstance();
String? userId = prefs.getString(SharedPrefsKeys.userId) ?? '';
String? tiposViajes = prefs.getString(SharedPrefsKeys.tiposViajes) ?? '';
String? company = prefs.getString(SharedPrefsKeys.company) ?? '';
int? ayuda = prefs.getInt(SharedPrefsKeys.ayuda);
String? seguimientoMetros = prefs.getString(SharedPrefsKeys.seguimientoMetros);
double? seguimientoMetrosDouble =
seguimientoMetros != null ? double.tryParse(seguimientoMetros) : null;
if (userId.isEmpty) {
Navigator.pushNamed(context, "/login");
return;
}
if (seguimientoMetrosDouble == null || seguimientoMetrosDouble <= 0) {
print("Error: 'seguimiento_metros' no válido. Usando valor predeterminado.");
seguimientoMetrosDouble = 10.0;
}
await checkScreenLocked();
_configureBackgroundGeolocation(
userId, tiposViajes, company, ayuda, seguimientoMetrosDouble,
);
platform.setMethodCallHandler((MethodCall call) async {
switch (call.method) {
case 'screenLocked':
_updateSmartphoneBlockedState('1', 'Teléfono bloqueado');
break;
case 'screenUnlocked':
_updateSmartphoneBlockedState('0', 'Teléfono desbloqueado');
break;
default:
print("Método desconocido: ${call.method}");
}
});
} catch (e) {
print("Error inicializando el estado de la plataforma: $e");
}
}
/// Listener del ciclo de vida de la app
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
isStateSynced = false; // Forzar nueva sincronización en cambios de ciclo de vida
if (state == AppLifecycleState.paused || state == AppLifecycleState.resumed) {
checkScreenLocked();
}
}
/// Verifica el estado inicial de la pantalla
static Future<void> checkScreenLocked() async {
if (isStateSynced) return;
try {
final bool result = await platform.invokeMethod('isScreenLocked');
_smartphoneBlocked = result ? '1' : '0';
isStateSynced = true;
print(result
? 'Teléfono bloqueado valor 1 _smartphoneBlocked: $_smartphoneBlocked'
: 'Teléfono desbloqueado valor 0 _smartphoneBlocked: $_smartphoneBlocked');
} on PlatformException catch (e) {
print("Error al obtener el estado de pantalla: ${e.message}");
_smartphoneBlocked = '0'; // Fallback en caso de error
}
}
/// Configuración del plugin BackgroundGeolocation
static void _configureBackgroundGeolocation(
String userId,
String? tiposViajes,
String? company,
int? ayuda,
double? seguimientoMetrosDouble,
) {
bg.BackgroundGeolocation.onLocation(_onLocation, _onLocationError);
bg.BackgroundGeolocation.onMotionChange(_onMotionChange);
bg.BackgroundGeolocation.onActivityChange(_onActivityChange);
bg.BackgroundGeolocation.onProviderChange(_onProviderChange);
bg.BackgroundGeolocation.onConnectivityChange(_onConnectivityChange);
bg.BackgroundGeolocation.onHttp(_onHttp);
bg.BackgroundGeolocation.ready(bg.Config(
reset: true,
debug: true,
logLevel: bg.Config.LOG_LEVEL_VERBOSE,
desiredAccuracy: bg.Config.DESIRED_ACCURACY_HIGH,
distanceFilter: 10,
backgroundPermissionRationale: bg.PermissionRationale(
title:
"Allow {applicationName} to access this device's location even when the app is closed or not in use.",
message:
"This app collects location data to enable recording your trips to work and calculate distance-travelled.",
positiveAction: 'Change to "{backgroundPermissionOptionLabel}"',
negativeAction: 'Cancel',
),
url: API_URL + "device/locations2",
extras: {
"userId": userId,
"smartphone_blocked": _smartphoneBlocked,
"tipos_viajes": tiposViajes,
"company": company,
"ayuda": ayuda,
"band": "0"
},
stopOnTerminate: false,
startOnBoot: true,
enableHeadless: true,
)).then((bg.State state) {
print("[ready] ${state.toMap()}");
}).catchError((error) {
print('[ready] ERROR: $error');
});
}
/// Actualiza el estado del bloqueo de pantalla
static void _updateSmartphoneBlockedState(String state, String message) {
_smartphoneBlocked = state;
print('Actualización estado automático mensaje: $message Estado: $_smartphoneBlocked');
}
/// Eventos del plugin BackgroundGeolocation
static void _onLocation(bg.Location location) async {
await checkScreenLocked(); // Asegurar sincronización del estado
print('[location] before field blocked: $location');
}
static void _onLocationError(bg.LocationError error) {
print('[location] ERROR - $error');
}
static void _onMotionChange(bg.Location location) {
print('[motionchange] - $location');
}
static void _onActivityChange(bg.ActivityChangeEvent event) {
print('[activitychange] - $event');
}
static void _onHttp(bg.HttpEvent event) {
print('[${bg.Event.HTTP}] - $event');
}
static void _onProviderChange(bg.ProviderChangeEvent event) {
print('[providerchange] - $event');
}
static void _onConnectivityChange(bg.ConnectivityChangeEvent event) {
print('[connectivitychange] - $event');
}
}
/// Constantes para claves de SharedPreferences
class SharedPrefsKeys {
static const String userId = "userId";
static const String tiposViajes = "tipos_viajes";
static const String company = "company";
static const String ayuda = "ayuda";
static const String seguimientoMetros = "seguimiento_metros";
}
Bbut I don't know how to update the _smartphoneBlocked field inside your plugin, maybe I should use the config something like this inside an event like onMotionChange? something maybe like this?
bg.BackgroundGeolocation.setConfig(bg.Config(
extras: {
"userId": userId,
"smartphone_blocked": _smartphoneBlocked,
"tipos_viajes": tiposViajes,
"company": company,
"ayuda": ayuda,
"band": "0",
},
));
But I don't know how to update the _smartphoneBlocked
use .setConfig
to change ANY config option, including Config.extras
.
Every location recorded AFTER .setConfig
completes, will have those new extras
stamped upon it. It's really simple.
you should be calling .setConfig
in your checkScreenLocked
method (and only when the value actually changes).
thanks a lot Chris!!! awesome plugin and help from you!
Your Environment
flutter doctor
):✓] Flutter (Channel stable, 3.24.2, on Mac OS X 10.15.7 19H2026 darwin-x64, locale es-CO) [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.1) [!] Xcode - develop for iOS and macOS (Xcode 12.4) ✗ Flutter requires Xcode 14 or higher. Download the latest version or update via the Mac App Store. ! CocoaPods 1.12.1 out of date (1.13.0 is recommended). CocoaPods is a package manager for iOS or macOS platform code. Without CocoaPods, plugins will not work on iOS or macOS. For more info, see https://flutter.dev/to/platform-plugins To update CocoaPods, see https://guides.cocoapods.org/using/getting-started.html#updating-cocoapods [✓] Chrome - develop for the web [✓] Android Studio (version 2020.3) [✓] VS Code (version 1.95.3) [✓] Connected device (2 available) ! Device 192.168.1.7:5555 is offline. [✓] Network resourcesclass LocationService { static const platform = MethodChannel('co.itfusion.protegos/screen_state'); static String _smartphoneBlocked = '1'; // Iniciar como bloqueado por defecto
static Future initPlatformState(BuildContext context) async {
try {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? userId = prefs.getString("userId") ?? '';
String? tiposViajes = prefs.getString("tipos_viajes") ?? '';
String? company = prefs.getString("company") ?? '';
int? ayuda = prefs.getInt("ayuda");
String? seguimientoMetros = prefs.getString("seguimiento_metros");
double? seguimientoMetrosDouble = seguimientoMetros != null ? double.tryParse(seguimientoMetros) : null;
}
static Future checkScreenLocked() async {
try {
final bool result = await platform.invokeMethod('isScreenLocked');
_smartphoneBlocked = result ? '1' : '0';
print(result ? 'Teléfono bloqueado valor 1 _smartphoneBlocked:' : 'Teléfono desbloqueado valor 0 _smartphoneBlocked:');
print(_smartphoneBlocked);
//SharedPreferences prefs = await SharedPreferences.getInstance();
//prefs.setString("smartphoneBlocked", _smartphoneBlocked);
} on PlatformException catch (e) {
print("Error al obtener el estado de pantalla: ${e.message}");
}
}
static void _configureBackgroundGeolocation( String userId, String? tiposViajes, String? company, int? ayuda, double? seguimientoMetrosDouble) { bg.BackgroundGeolocation.onLocation(_onLocation, _onLocationError); bg.BackgroundGeolocation.onMotionChange(_onMotionChange); bg.BackgroundGeolocation.onActivityChange(_onActivityChange); bg.BackgroundGeolocation.onProviderChange(_onProviderChange); bg.BackgroundGeolocation.onConnectivityChange(_onConnectivityChange); bg.BackgroundGeolocation.onHttp(_onHttp);
}
static void _updateSmartphoneBlockedState(String state, String message) { _smartphoneBlocked = state; print(message); }
static void _onLocation(bg.Location location) async { await checkScreenLocked(); // Asegurar sincronización del estado print('Estado después de checkScreenLocked: $_smartphoneBlocked'); print('[location] antes update campo blocked - $location'); // Actualizar el campo smartphone_blocked en la estructura de location //location.extras['smartphone_blocked'] = _smartphoneBlocked; String extras_smartphone = _smartphoneBlocked; location.extras;
}
static void _onLocationError(bg.LocationError error) { print('[location] ERROR - $error'); }
static void _onMotionChange(bg.Location location) { print('[motionchange] - $location'); }
static void _onActivityChange(bg.ActivityChangeEvent event) { print('[activitychange] - $event'); }
static void _onHttp(bg.HttpEvent event) { print('[${bg.Event.HTTP}] - $event'); }
static void _onProviderChange(bg.ProviderChangeEvent event) { print('[providerchange] - $event'); }
static void _onConnectivityChange(bg.ConnectivityChangeEvent event) { print('[connectivitychange] - $event'); } }