I'm trying to track location in the background using flutter and to do so I'm using the background_locator plugin. I've declared a class variable of File type to save the log in the background. The global variable is built at the very beginning of the class.
Issue: While invoking the callback method, the global variable built is becoming null. So though I could see the location log in my console, I couldn't write it to the file as the object is null.
Tries:
- I've tried with the exact example provided in their documentation.
- I've declared it as non static property and tried to access with the class object.
- Tried it out declaring it as static property as well.
- Tried building file object with the same path every time needed but it is throwing following issue.
No implementation found for method getApplicationDocumentsDirectory on channel plugins.flutter.io/path_provider
Here is my complete source code for reference.
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'dart:math';
import 'dart:ui';
import 'package:background_locator/background_locator.dart';
import 'package:background_locator/location_dto.dart';
import 'package:background_locator/settings/android_settings.dart';
import 'package:background_locator/settings/ios_settings.dart';
import 'package:background_locator/settings/locator_settings.dart';
import 'package:flutter/material.dart';
import 'package:location_permissions/location_permissions.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart' as ph;
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ReceivePort port = ReceivePort();
String logStr = '';
bool isRunning = false;
LocationDto? lastLocation;
bool permissionsGranted = false;
static const String isolateName = 'LocatorIsolate';
static int _count = -1;
static File? finalFile;
void requestPermission() async {
var storageStatus = await ph.Permission.storage.status;
if (!storageStatus.isGranted) {
await ph.Permission.storage.request();
}
if (storageStatus.isGranted) {
permissionsGranted = true;
setPrerequisites();
}
setState(() {});
}
static Future<void> init(Map<dynamic, dynamic> params) async {
//TODO change logs
print("***********Init callback handler");
if (params.containsKey('countInit')) {
dynamic tmpCount = params['countInit'];
if (tmpCount is double) {
_count = tmpCount.toInt();
} else if (tmpCount is String) {
_count = int.parse(tmpCount);
} else if (tmpCount is int) {
_count = tmpCount;
} else {
_count = -2;
}
} else {
_count = 0;
}
print("$_count");
await setLogLabel("start");
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(null);
}
static Future<void> disposeLocationService() async {
await setLogLabel("end");
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(null);
}
static Future<void> callback(LocationDto locationDto) async {
await setLogPosition(_count, locationDto);
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(locationDto);
_count++;
}
static Future<void> setLogLabel(String label) async {
final date = DateTime.now();
await _MyAppState().writeToLogFile(
'------------\n$label: ${formatDateLog(date)}\n------------\n');
}
static Future<void> setLogPosition(int count, LocationDto data) async {
final date = DateTime.now();
await _MyAppState().writeToLogFile(
'$count : ${formatDateLog(date)} --> ${formatLog(data)} --- isMocked: ${data.isMocked}\n');
}
static double dp(double val, int places) {
num mod = pow(10.0, places);
return ((val * mod).round().toDouble() / mod);
}
static String formatDateLog(DateTime date) {
return date.hour.toString() +
":" +
date.minute.toString() +
":" +
date.second.toString();
}
static String formatLog(LocationDto locationDto) {
return dp(locationDto.latitude, 4).toString() +
" " +
dp(locationDto.longitude, 4).toString();
}
@override
void initState() {
super.initState();
if (permissionsGranted) {
setPrerequisites();
} else {
requestPermission();
}
}
void setPrerequisites() async {
finalFile = await _getTempLogFile();
if (IsolateNameServer.lookupPortByName(isolateName) != null) {
IsolateNameServer.removePortNameMapping(isolateName);
}
IsolateNameServer.registerPortWithName(port.sendPort, isolateName);
port.listen(
(dynamic data) async {
await updateUI(data);
},
);
initPlatformState();
setState(() {});
}
Future<void> updateUI(LocationDto data) async {
final log = await readLogFile();
await _updateNotificationText(data);
setState(() {
if (data != null) {
lastLocation = data;
}
logStr = log;
});
}
Future<void> _updateNotificationText(LocationDto data) async {
if (data == null) {
return;
}
await BackgroundLocator.updateNotificationText(
title: "new location received",
msg: "${DateTime.now()}",
bigMsg: "${data.latitude}, ${data.longitude}");
}
Future<void> initPlatformState() async {
print('Initializing...');
await BackgroundLocator.initialize();
logStr = await readLogFile();
print('Initialization done');
final _isRunning = await BackgroundLocator.isServiceRunning();
setState(() {
isRunning = _isRunning;
});
print('Running ${isRunning.toString()}');
}
@override
Widget build(BuildContext context) {
final start = SizedBox(
width: double.maxFinite,
child: ElevatedButton(
child: const Text('Start'),
onPressed: () {
_onStart();
},
),
);
final stop = SizedBox(
width: double.maxFinite,
child: ElevatedButton(
child: Text('Stop'),
onPressed: () {
onStop();
},
),
);
final clear = SizedBox(
width: double.maxFinite,
child: ElevatedButton(
child: Text('Clear Log'),
onPressed: () {
clearLogFile();
setState(() {
logStr = '';
});
},
),
);
String msgStatus = "-";
if (isRunning != null) {
if (isRunning) {
msgStatus = 'Is running';
} else {
msgStatus = 'Is not running';
}
}
final status = Text("Status: $msgStatus");
final log = Text(
logStr,
);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter background Locator'),
),
body: Container(
width: double.maxFinite,
padding: const EdgeInsets.all(22),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[start, stop, clear, status, log],
),
),
),
),
);
}
void onStop() async {
await BackgroundLocator.unRegisterLocationUpdate();
final _isRunning = await BackgroundLocator.isServiceRunning();
setState(() {
isRunning = _isRunning;
});
}
void _onStart() async {
if (await _checkLocationPermission()) {
await _startLocator();
final _isRunning = await BackgroundLocator.isServiceRunning();
setState(() {
isRunning = _isRunning;
lastLocation = null;
});
} else {
// show error
}
}
static Future<void> initCallback(Map<dynamic, dynamic> params) async {
await init(params);
}
static Future<void> disposeCallback() async {
await disposeLocationService();
}
Future<void> locationServicecallback(LocationDto locationDto) async {
await callback(locationDto);
}
static Future<void> notificationCallback() async {
print('***notificationCallback');
}
Future<void> writeToLogFile(String log) async {
await finalFile!.writeAsString(log, mode: FileMode.append);
}
Future<String> readLogFile() async {
return finalFile!.readAsString();
}
static Future<File?> _getTempLogFile() async {
File file =
File('${(await getApplicationDocumentsDirectory()).path}/log.txt');
if (file.existsSync()) {
return file;
} else {
file = await file.create(recursive: true);
}
return file;
}
Future<void> clearLogFile() async {
await finalFile!.writeAsString('');
}
Future<bool> _checkLocationPermission() async {
final access = await LocationPermissions().checkPermissionStatus();
switch (access) {
case PermissionStatus.unknown:
case PermissionStatus.denied:
case PermissionStatus.restricted:
final permission = await LocationPermissions().requestPermissions(
permissionLevel: LocationPermissionLevel.locationAlways,
);
if (permission == PermissionStatus.granted) {
return true;
} else {
return false;
}
case PermissionStatus.granted:
return true;
default:
return false;
}
}
Future<void> _startLocator() async {
Map<String, dynamic> data = {'countInit': 1};
return await BackgroundLocator.registerLocationUpdate(
callback,
initCallback: initCallback,
initDataCallback: data,
disposeCallback: disposeCallback,
iosSettings: const IOSSettings(
accuracy: LocationAccuracy.NAVIGATION, distanceFilter: 0),
autoStop: false,
androidSettings: const AndroidSettings(
accuracy: LocationAccuracy.NAVIGATION,
interval: 5,
distanceFilter: 0,
client: LocationClient.google,
androidNotificationSettings: AndroidNotificationSettings(
notificationChannelName: 'Location tracking',
notificationTitle: 'Start Location Tracking',
notificationMsg: 'Track location in background',
notificationBigMsg:
'Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running.',
notificationIconColor: Colors.grey,
notificationTapCallback: notificationCallback,
),
),
);
}
}
Any help or working example would be highly helpful, thanks in advance.