1

In Android, there is a android.location.LocationListener class, and the Android Operating System updates locations using the class as below:

android.location.LocationListener

It says that Location is @NonNull, but in reality the OS does return null sometimes, which crashes the app.

object : LocationListener {
    override fun onLocationChanged(location: Location) {
        doSomethingWithLocation(location) // App crashes here, because location is null
    }
}

Is there some way to tell the compiler that Location is actually Location? ?

Jacques.S
  • 3,262
  • 2
  • 21
  • 27
  • why don't just declare it nullable then? ```override fun onLocationChanged(location: Location?)``` – IR42 Sep 20 '21 at 09:50
  • I've never received a null location for this case. Are you sure that the code that's using the location is correct? – danypata Sep 20 '21 at 10:04
  • Trying to change the function signature by declaring it nullable isn't allowed by the compiler. Yes I am sure it is correct, this only happens on very old devices running very old android versions. – Jacques.S Sep 20 '21 at 10:13
  • so why can't you use a null-safe call? – mightyWOZ Sep 20 '21 at 10:22
  • My question is whether or not there is a way to convince the compiler that the NonNull value is actually nullable. Using a null-safe call does not convince the compiler that the NonNull value is actually nullable. – Jacques.S Sep 20 '21 at 10:36

2 Answers2

2

I don't believe there is way to discard the nullability information coming from java. in your case this is a problem because as pointed out by @RobCo, kotlin will automatically insert non-null assertion for location and the assertion will fail.

So it seems the only solution you are left with is to use a java wrapper type which will only provide you with non-null location values, which you can use in your kotlin code. this wrapper could look like

import androidx.core.util.Consumer;

public class LocationHandler{
    LocationHandler(Consumer<Location> delegate){
        this.delegate = delegate;
    }

    private Consumer<Location> delegate;

    private LocationListener _listener = new LocationListener() {
        @Override
        public void onLocationChanged(@NonNull Location location) {
            // Only forward non-null location values
            if(location != null){ delegate.accept(location); }
            else {
                Log.d("LocationHandler", "Received null Location");
            }
        }
    };

    public LocationListener getListener(){
        return _listener;
    }
}

Now in your kotlin code you can use it as

class MainActivity : AppCompatActivity() {

    private val locationDelegate = Consumer<Location> {
        // Do something with non-null location
    }
    
    override fun somFun {
        val locationHandler = LocationHandler(locationDelegate)
        val locMan = getSystemService(Context.LOCATION_SERVICE) as LocationManager
        locMan.requestLocationUpdates(
                LocationManager.GPS_PROVIDER, 
                0L, 
                0F, 
                locationHandler.listener)
    }
}
mightyWOZ
  • 7,946
  • 3
  • 29
  • 46
  • The compiler thinks this is a mistake and this does not answer my question. – Jacques.S Sep 20 '21 at 10:16
  • That warning is valid for compiler's prespective, since you are using null safe call with a non-nullable type. but you don't have any choice either use `location: Location?` or use a null safe call with warning – mightyWOZ Sep 20 '21 at 10:20
  • I agree with @mightyWOZ - i don't understand the reason behind downvote, but this is the correct way to make it null-safe, if java api is throwing nullable object, even though if its marked as non-nullable. – Ritt Sep 20 '21 at 10:22
  • You can write a quick test-case and check how its behaving. – Ritt Sep 20 '21 at 10:23
  • 1
    This might not work though as Kotlin normally adds a `Intrinsics.checkParameterIsNotNull` call which would still cause a crash. There is a compiler flag (`-Xno-param-assertions`) to disabled this behaviour. – RobCo Sep 20 '21 at 10:34
  • Thanks for pointing that out, I think assertions should not be disabled. instead a java wrapper type can be introduced to filter out the null values. I will update the answer. – mightyWOZ Sep 20 '21 at 11:33
1

I found two workarounds for this.

First, if the interface contains only a single abstract method, we can use SAM conversion and override the type of the parameter to Location? like this:

val listener = LocationListener { location: Location? ->
    doSomethingWithLocation(location)
}

What is interesting, we can override the parameter if using SAM conversion, but we can't if using full syntax of subtyping. It seems LocationListener is SAM, so this solution could work in your case.

If the first solution is not possible, we can override the interface in Java:

public interface MyLocationListener extends LocationListener {
    @Override
    void onLocationChanged(@Nullable Location location);
}
val listener = object : MyLocationListener {
    override fun onLocationChanged(location: Location?) {
        doSomethingWithLocation(location)
    }
}
broot
  • 21,588
  • 3
  • 30
  • 35
  • In my case the SAM conversion isn't possible, but using Java to change the Nullable method signature does the trick. I wish there was a way to force this in Kotlin, but your solution works, thanks. – Jacques.S Sep 20 '21 at 12:24