microg/GmsCore · error · IllegalStateException

looper is null and the calling thread has not called Looper.

Error message

looper is null and the calling thread has not called Looper.prepare()

What it means

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.

Source

Thrown at play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderClientImpl.java:81

    }

    @NonNull
    @Override
    public Task<Void> removeLocationUpdates(@NonNull LocationListener listener) {
        return scheduleTask((VoidReturningGoogleApiCall<LocationClientImpl>) (client) -> client.removeLocationUpdates(listener));
    }

    @NonNull
    @Override
    public Task<Void> removeLocationUpdates(@NonNull PendingIntent pendingIntent) {
        return scheduleTask((VoidReturningGoogleApiCall<LocationClientImpl>) (client) -> client.removeLocationUpdates(pendingIntent));
    }

    @NonNull
    @Override
    public Task<Void> requestLocationUpdates(@NonNull LocationRequest request, @NonNull LocationListener listener, @Nullable Looper looper) {
        Looper currentLooper = looper == null ? Looper.myLooper() : looper;
        if (currentLooper == null) throw new IllegalStateException("looper is null and the calling thread has not called Looper.prepare()");
        return scheduleTask((VoidReturningGoogleApiCall<LocationClientImpl>) (client) -> client.requestLocationUpdates(request, listener, currentLooper));
    }

    @NonNull
    @Override
    public Task<Void> requestLocationUpdates(@NonNull LocationRequest request, @NonNull Executor executor, @NonNull LocationCallback callback) {
        return scheduleTask((VoidReturningGoogleApiCall<LocationClientImpl>) (client) -> client.requestLocationUpdates(request, executor, callback));
    }

    @NonNull
    @Override
    public Task<Void> requestLocationUpdates(@NonNull LocationRequest request, @NonNull Executor executor, @NonNull LocationListener listener) {
        return scheduleTask((VoidReturningGoogleApiCall<LocationClientImpl>) (client) -> client.requestLocationUpdates(request, executor, listener));
    }

    @NonNull
    @Override
    public Task<Void> requestLocationUpdates(@NonNull LocationRequest request, @NonNull LocationCallback callback, Looper looper) {

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Pass Looper.getMainLooper() explicitly so updates are delivered on the main thread
  2. Call Looper.prepare() on the background thread before requesting updates, or use a HandlerThread and pass its Looper
  3. Use the Executor-based overload requestLocationUpdates(request, executor, callback) which does not require a Looper

Example fix

// before
thread {
    client.requestLocationUpdates(request, callback, null)
}
// after
thread {
    client.requestLocationUpdates(request, callback, Looper.getMainLooper())
}
Defensive patterns

Strategy: validation

Validate before calling

Looper looper = looperArg != null ? looperArg : Looper.myLooper();
if (looper == null) looper = Looper.getMainLooper();
client.requestLocationUpdates(request, listener, looper);

Type guard

static boolean hasLooper() { return Looper.myLooper() != null; }

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/ed4c4fb952a6c185. Report an issue: GitHub.