{"record":{"id":"ed4c4fb952a6c185","repo":"microg/GmsCore","slug":"looper-is-null-and-the-calling-thread-has-not-call","errorCode":null,"errorMessage":"looper is null and the calling thread has not called Looper.prepare()","messagePattern":"looper is null and the calling thread has not called Looper\\.prepare\\(\\)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderClientImpl.java","lineNumber":81,"sourceCode":"    }\n\n    @NonNull\n    @Override\n    public Task<Void> removeLocationUpdates(@NonNull LocationListener listener) {\n        return scheduleTask((VoidReturningGoogleApiCall<LocationClientImpl>) (client) -> client.removeLocationUpdates(listener));\n    }\n\n    @NonNull\n    @Override\n    public Task<Void> removeLocationUpdates(@NonNull PendingIntent pendingIntent) {\n        return scheduleTask((VoidReturningGoogleApiCall<LocationClientImpl>) (client) -> client.removeLocationUpdates(pendingIntent));\n    }\n\n    @NonNull\n    @Override\n    public Task<Void> requestLocationUpdates(@NonNull LocationRequest request, @NonNull LocationListener listener, @Nullable Looper looper) {\n        Looper currentLooper = looper == null ? Looper.myLooper() : looper;\n        if (currentLooper == null) throw new IllegalStateException(\"looper is null and the calling thread has not called Looper.prepare()\");\n        return scheduleTask((VoidReturningGoogleApiCall<LocationClientImpl>) (client) -> client.requestLocationUpdates(request, listener, currentLooper));\n    }\n\n    @NonNull\n    @Override\n    public Task<Void> requestLocationUpdates(@NonNull LocationRequest request, @NonNull Executor executor, @NonNull LocationCallback callback) {\n        return scheduleTask((VoidReturningGoogleApiCall<LocationClientImpl>) (client) -> client.requestLocationUpdates(request, executor, callback));\n    }\n\n    @NonNull\n    @Override\n    public Task<Void> requestLocationUpdates(@NonNull LocationRequest request, @NonNull Executor executor, @NonNull LocationListener listener) {\n        return scheduleTask((VoidReturningGoogleApiCall<LocationClientImpl>) (client) -> client.requestLocationUpdates(request, executor, listener));\n    }\n\n    @NonNull\n    @Override\n    public Task<Void> requestLocationUpdates(@NonNull LocationRequest request, @NonNull LocationCallback callback, Looper looper) {","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderClientImpl.java#L63-L99","documentation":"microG's FusedLocationProviderClientImpl throws this IllegalStateException when the Looper used to deliver location updates cannot be resolved. If the caller passes a null Looper, the library falls back to Looper.myLooper(); on a thread that never called Looper.prepare() there is no per-thread Looper, so delivery of updates would be impossible and the library fails fast.","triggerScenarios":"Calling requestLocationUpdates(LocationRequest, LocationListener) or requestLocationUpdates(LocationRequest, LocationCallback) with a null Looper from a thread that has not called Looper.prepare() (e.g. a plain background Thread, Executor thread, or Kotlin coroutine on Dispatchers.Default).","commonSituations":"Requesting location updates from a worker thread without an associated Looper; passing an uninitialized Looper variable; migrating from Google Play services where a null Looper was tolerated by using the main looper implicitly.","solutions":["Pass Looper.getMainLooper() explicitly so updates are delivered on the main thread","Call Looper.prepare() on the background thread before requesting updates, or use a HandlerThread and pass its Looper","Use the Executor-based overload requestLocationUpdates(request, executor, callback) which does not require a Looper"],"exampleFix":"// before\nthread {\n    client.requestLocationUpdates(request, callback, null)\n}\n// after\nthread {\n    client.requestLocationUpdates(request, callback, Looper.getMainLooper())\n}","handlingStrategy":"validation","validationCode":"Looper looper = looperArg != null ? looperArg : Looper.myLooper();\nif (looper == null) looper = Looper.getMainLooper();\nclient.requestLocationUpdates(request, listener, looper);","typeGuard":"static boolean hasLooper() { return Looper.myLooper() != null; }","tryCatchPattern":null,"preventionTips":["Always pass an explicit Looper (usually Looper.getMainLooper()) to requestLocationUpdates","Prefer the Executor-based overload on threads without a Looper","Never assume the calling thread has a Looper; background/Executor threads do not"],"tags":["android","looper","threading","location"],"backgroundTag":"missing-required-argument","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}