Home > Net >  Is there a way to convince the compiler that a @NonNull variable is actually Nullable in Kotlin?
Is there a way to convince the compiler that a @NonNull variable is actually Nullable in Kotlin?

Time:09-22

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? ?

CodePudding user response:

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)
    }
}

CodePudding user response:

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)
    }
}
  • Related