webpack build throwing `Unable to instantiate service com.transistorsoft.backgroundgeolocation.HeadlessJobService`
Closed this issue · 29 comments
Your Environment
- Plugin version: 1.9.0-beta.4
- Android
- OS version: 7.0
- Device manufacturer / model: AVD emulator
- Nativescript version (
tns info
):
nativescript: 4.2.3
tns-core-modules: 4.2.0
tns-android: 4.2.0
nativescript-dev-webpack: 0.16.0
webpack: 4.6.0
- Plugin config
const config = {
debug: false,
startOnBoot: false,
// eternal location tracking
stopOnTerminate: false,
enableHeadless: true,
// accuracy and interval
desiredAccuracy: 0,
stationaryRadius: 10,
distanceFilter: 100,
activityRecognitionInterval: 10000,
// http
url: this.httpService.getUrl('/location/current'),
autoSync: true,
method: 'PATCH',
headers: this.httpService.getHeaders({asObject: true}),
httpRootProperty: '.',
locationTemplate: '{ "lat":<%= latitude %>, "lng":<%= longitude %> }',
// iOS
preventSuspend: true,
// android
foregroundService: true,
notificationTitle: '...'
};
Expected Behavior
Headless mode works when built with webpack
Actual Behavior
Raises error below and die when built with webpack
(works fine when I repeat the same step with non-webpack built app)
System.err: java.lang.RuntimeException: Unable to instantiate service com.transistorsoft.backgroundgeolocation.HeadlessJobService: java.lang.ClassNotFoundException: Didn't find class "com.transistorsoft.backgroundgeolocation.HeadlessJobService" on path: DexPathList[[zip file "/data/app/com.myapp/base.apk"],nativeLibraryDirectories=[/data/app/com.myapp/lib/x86, /data/app/com.myapp/base.apk!/lib/x86, /system/lib, /vendor/lib]]
System.err: at android.app.ActivityThread.handleCreateService(ActivityThread.java:3147)
System.err: at android.app.ActivityThread.-wrap5(ActivityThread.java)
System.err: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1550)
System.err: at android.os.Handler.dispatchMessage(Handler.java:102)
System.err: at android.os.Looper.loop(Looper.java:154)
System.err: at android.app.ActivityThread.main(ActivityThread.java:6077)
System.err: at java.lang.reflect.Method.invoke(Native Method)
System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
System.err: Caused by: java.lang.ClassNotFoundException: Didn't find class "com.transistorsoft.backgroundgeolocation.HeadlessJobService" on path: DexPathList[[zip file "/data/app/com.myapp/base.apk"],nativeLibraryDirectories=[/data/app/com.myapp/lib/x86, /data/app/com.myapp/base.apk!/lib/x86, /system/lib, /vendor/lib]]
System.err: at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
System.err: at java.lang.ClassLoader.loadClass(ClassLoader.java:380)
System.err: at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
System.err: at android.app.ActivityThread.handleCreateService(ActivityThread.java:3144)
System.err: ... 8 more
Steps to Reproduce
- run app with
tns run android --bundle
- open the app
- kill the app from task list
Debug logs
the gist is the error message above.
also confirmed the same on a real android device as well
The minimum gist of the code is like the following:
@Injectable()
export class GeolocationService {
...
onLocation = (location) => {
console.log('location!!');
}
headlessTask(event, completionHandler) {
switch(event.name) {
case 'location':
BackgroundGeolocation.changePace(true);
this.onLocation(event);
break;
}
completionHandler();
}
public startService()
BackgroundGeolocation.watchPosition(() => { .... })
BackgroundGeolocation.on('location', this.onLocation);
BackgroundGeolocation.on('heartbeat', this.onHeartBeat);
BackgroundGeolocation.on('http', this.onHttp);
if (isAndroid) {
BackgroundGeolocation.registerHeadlessTask(this.headlessTask.bind(this));
}
}
}
// the root component of the app
@Component({
selector: 'root',
moduleId: module.id,
templateUrl: 'root.component.html'
})
export class RootComponent {
constructor(private geolocationService: GeolocationService) {}
ngOnInit(){
const config = {...}
BackgroundGeolocation.ready(config, ({enabled}) => {
if (!enabled) {
BackgroundGeolocation.start(this.onStartDone.bind(this));
} else {
this.onStartDone();
}
}
}
onStartDone() {
this.geolocationService.startService()
}
}
please tell me if you need anymore information, thanks
Please give a look at this, we're stuck here
Ok made a bare minimum reproducible repo here.
https://github.com/Liooo/geo-loc-test
bootstraped from the template tns-template-blank-ng
, with the minimum geolocation headless task. The app dies with the same error when killing the activity.
I understand you're busy, but please at least post a comment or show whatever reaction, this is not some contribution based open source software but we pay not small amount of money for this. Thanks.
The plugin uses Nativescript's Static Binding Generator for the Headless classes
This instructs NativeScript to auto-generate corresponding native Java classes:
HeadlessBroadcastReceiver.java
HeadlessJobService.java
.
$ tns build android
When built with tns build android --bundle
, Nativescript fails to auto-generate these classes. I have no experience using Webpack with Nativescript. It's Nativescript's responsibility to auto-generate these classes.
@Liooo as a solution craete manual import to the worker file used in the plugin.
// e.g in your app.component.ts
import "nativescript-background-geolocation-lt/HeadlessJobService";
@transistorsoft to resolve the issue for all users the best approach would be to include explicit imports of the background services files within your source code.
// e.g. in the background-geolocation.ts
import "nativescript-background-geolocation-lt/HeadlessJobService";
Thanks for the tip, @NickIliev
@Liooo Try latest 1.9.0
.
@christocracy Thank you, I'm checking it out.
The same code now dies like this.
and the logcat says someting like
12-20 09:18:06.188 15794 15794 E AndroidRuntime: java.lang.RuntimeException: Unable to instantiate service com.transistorsoft.backgroundgeolocation.HeadlessJobService: java.lang.NullPointerException: Attempt to read from field 'int com.tns.Runtime.currentObjectId' on a null object reference
12-20 09:18:06.188 15794 15794 E AndroidRuntime: at android.app.ActivityThread.handleCreateService(ActivityThread.java:3147)
12-20 09:18:06.188 15794 15794 E AndroidRuntime: at android.app.ActivityThread.-wrap5(ActivityThread.java)
12-20 09:18:06.188 15794 15794 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1550)
12-20 09:18:06.188 15794 15794 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:102)
12-20 09:18:06.188 15794 15794 E AndroidRuntime: at android.os.Looper.loop(Looper.java:154)
12-20 09:18:06.188 15794 15794 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6077)
12-20 09:18:06.188 15794 15794 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
12-20 09:18:06.188 15794 15794 E AndroidRuntime: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
12-20 09:18:06.188 15794 15794 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
12-20 09:18:06.188 15794 15794 E AndroidRuntime: Caused by: java.lang.NullPointerException: Attempt to read from field 'int com.tns.Runtime.currentObjectId' on a null object reference
12-20 09:18:06.188 15794 15794 E AndroidRuntime: at com.tns.Runtime.initInstance(Runtime.java:624)
12-20 09:18:06.188 15794 15794 E AndroidRuntime: at com.transistorsoft.backgroundgeolocation.HeadlessJobService.<init>(HeadlessJobService.java:12)
12-20 09:18:06.188 15794 15794 E AndroidRuntime: at java.lang.Class.newInstance(Native Method)
12-20 09:18:06.188 15794 15794 E AndroidRuntime: at android.app.ActivityThread.handleCreateService(ActivityThread.java:3144)
12-20 09:18:06.188 15794 15794 E AndroidRuntime: ... 8 more
12-20 09:18:06.189 1627 3010 W ActivityManager: Force finishing activity engineer.liooo.test/com.tns.ErrorReportActivity
I hate NativeScript.
lol, don't say that we all just trying to build something for somebody
@NickIliev Do you think you can figure out how to fix?
@Liooo, @NickIliev has provided all that's required in pointing out the problem. You need not cc him on this anymore.
Okay then will be waiting for your work, thanks man
In the meantime, show me your tns info
It says my transistor soft subscription expires in 5days or something, so can you finish the fix by then or please at least give me the bug fixed version if it takes longer.
your test app work for me.
$ tns info
✔ Getting NativeScript components versions information...
✔ Component nativescript has 5.1.0 version and is up to date.
✔ Component tns-core-modules has 5.1.1 version and is up to date.
✔ Component tns-android has 5.1.0 version and is up to date.
✔ Component tns-ios has 5.1.0 version and is up to date.
$ tns run android --bundle
Refreshing application on device FA6970312056...
Successfully synced application engineer.liooo.test on device FA6970312056.
JS: Angular is running in the development mode. Call enableProdMode() to enable the production mode.
JS: [BackgroundGeolocation] onActivityDestroyedEvent - com.intentfilter.androidpermissions.PermissionsActivity@6edbabb
JS: location
JS: location
JS: location
You need to kill the app from the task list and get the headless mode to kick in.
we should see console.log("headless")
if it's working
any updates?
@christocracy
Happy new year.
How's this thing going.
Your latest issue is actually in your application code here.
You cannot registerHeadlessTask
in your application components. The Headless Mode instructions explicitly tell you to install this in the root app.ts
file. Since you're using Angular, I moved it to main.ts
and it works. Application components are not rendered in the headless application instance.
// this import should be first in order to load some required settings (like globals and reflect-metadata)
import { platformNativeScriptDynamic } from "nativescript-angular/platform";
+import { BackgroundGeolocation } from "nativescript-background-geolocation-lt";
import { AppModule } from "./app/app.module";
+BackgroundGeolocation.registerHeadlessTask((event, completionHandler) => {
+ console.log('[HeadlessTask] ', event.name, event);
+ completionHandler();
+});
platformNativeScriptDynamic().bootstrapModule(AppModule);
However, there is one minor bug fix I added to the plugin, adding a missing return
statement when the headlessTask
failed to be registered (due to the method being placed in the wrong file, as in your test app). Published as 1.9.1
.
Oh thanks for that, I'll try!
You cannot registerHeadlessTask in your application components.
Then what if the library users want to call Angular service inside headlessTask which probably is often the case?
Then what if the library users want to call Angular service inside headlessTask which probably is often the case?
If you have some methods stored in an external file that you wish to execute in the headless context, simply import
them. How you bring some methods into the context where the headless-task is registered is not the plugin's concern.
Angular's got all these ecosystem with DI and service instantiation and stuff so it doesn't work just by importing the class. But as long as location tracking headless part is working I can find some other workaround so it's ok. Thanks!
If there's a method in some Service that you can't manage to make accessible to your app in the headless context, simply extract some particular method into a simple external library file and import that into both the Service and Headless contexts. Javascript 101.
yea that's what I'll do thanks.
But if the user has whatever large angular codebase it's normal to call a service from another service for example, and it's not easy nor good for the code maintainability to extract only headless mode accessed logic out of them. So definitely better if it work in angular ecosystem
that's between you and angular. You'll have to explore and see what parts of an Angular app are booted within the Headless context.