CodingPoding
CodingPoding

Reputation: 33

Android Studio - Kotlin - How to make the reference to the service null?

I'm trying to adapt Google's LocationsUpdatesForegroundService example into Kotlin to use in my app. Now, everything is going fine, until I need to make a reference to a service equal to null. That doesn't 'cause any problems within the Java code it originates from but, when I try to implement it in Kotlin, even if use null!!, I get a KotlinNullPointerException when I try to run the app and the app crashes. I'm not quite sure how to avoid this or set it in a different way. I've spent a few hours on this and sometime browsing StackOverFlow without really being able to find a solution for it. If anyone could help me, it'd be greatly appreciated. I've enclosed the link to the code I'm going off of here:

https://github.com/android/location-samples/blob/master/LocationUpdatesForegroundService/app/src/main/java/com/google/android/gms/location/sample/locationupdatesforegroundservice/MainActivity.java#L127

...as well as the necessary code I'm using below.

Relevant code from my Main Activity:

private var lservice : LocService = null!! // A reference to the service to get location updates
private var bound = false // Tracks the bound state of the service

// Monitors the state of the connection to the service.
private val mServiceConnection = object:ServiceConnection {
    override fun onServiceConnected(name:ComponentName, service: IBinder) {
        val binder : LocService.LocalBinder = service as LocService.LocalBinder
        lservice = binder.getService()
        bound = true
    }

    override fun onServiceDisconnected(name: ComponentName) {
        lservice = null!!
        bound = false
    }
}

My service class, which may or may not be necessary for helping to debug this error:

class LocService : Service() {

private val PACKAGE_NAME = "com.example.localization"

private val TAG = LocService::class.java!!.getSimpleName()

val ACTION_BROADCAST = PACKAGE_NAME + ".broadcast"

private val EXTRA_STARTED_FROM_NOTIFICATION = PACKAGE_NAME + ".started_from_notification"

// To return a current instance of the service
private val binder = LocalBinder()

// To check if the bounded activity has actually gone away
// and not unbound as part of an orientation change
private var changingConfig = false

private lateinit var fusedLocClient: FusedLocationProviderClient // For FusedLocationProvider API
private lateinit var locRequest : LocationRequest // Parameters for FusedLocationProvider
// Callback for changes in location
private lateinit var locCallback: LocationCallback
private lateinit var serviceHandler : Handler

private lateinit var notificationManager : NotificationManager // Notification Manager
private lateinit var loc : Location // The current location

// The identifier for the notification displayed for the foreground service
private val NOTIFICATION_ID = 12345678

// Set up when the service is created
override fun onCreate()
{
    // An instance of Fused Location Provider Client
    fusedLocClient = LocationServices.getFusedLocationProviderClient(this)

    // Obtains location callback
    locCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult?) {
            super.onLocationResult(locationResult)
            loc = locationResult!!.getLastLocation() // Obtains last location

            // Send location information to any broadcast receivers
            val intention = Intent(ACTION_BROADCAST)
            intention.putExtra("Coordinates", locationResult!!.getLastLocation())
            intention.putExtra("Address", getAddress(locationResult))
            intention.putExtra("Time", SimpleDateFormat("MM/dd/yyyy 'at' HH:mm").format(Date()))
            LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intention)

            // Change notification content if the service is running in the foreground
            if (serviceIsRunningInForeground(this@LocService))
            {
                notificationManager.notify(NOTIFICATION_ID, getNotification())
            }
        }
    }

    // Create location request and get the last location
    getLastLocation()
    buildLocReq()

    // Creates a HandlerThread, which is an extension of Thread and works
    // with a Looper, meaning it's meant to handle multiple jobs in the background
    // thread. The Looper is what keeps the thread alive. Notification Manager
    // is there to notify the user of the notification service
    val handlerThread = HandlerThread(TAG)
    handlerThread.start()
    serviceHandler = Handler(handlerThread.getLooper())
    notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
}

// Called whenever the client starts the service using startService()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    val startedFromNotification = intent!!.getBooleanExtra(EXTRA_STARTED_FROM_NOTIFICATION, false)

    return START_NOT_STICKY // Don't recreate the service after it's killed
}

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    changingConfig = true
}

// Called when the client comes to the foreground and binds
// with this service. The service will stop being a foreground
// service when that happens
override fun onBind(intent: Intent): IBinder {
    stopForeground(true)
    changingConfig = false
    return binder
}

// Called when the client returns to the foreground
// and binds once again with this service. The service will
// stop being a foreground service when that happens
override fun onRebind(intent: Intent?) {
    stopForeground(true)
    changingConfig = false
    super.onRebind(intent)
}

// Called when the client unbinds with the service. If it's called
// with a configuration change, do nothing. Otherwise, make the service
// a foreground service
override fun onUnbind(intent: Intent?): Boolean {
    if (!changingConfig && requestingLocationUpdates(this))
    {
        startForeground(NOTIFICATION_ID, getNotification())
    }

    return true
}

// Called when service is destroyed
override fun onDestroy() {
    serviceHandler.removeCallbacksAndMessages(null)
}

inner class LocalBinder : Binder()
{
    fun getService() : LocService
    {
        return this@LocService
    }
}

// For obtaining location request
private fun buildLocReq()
{
    // Create a location request to store parameters for the requests
    locRequest = LocationRequest.create()

    // Sets priority, interval, and --smallest displacement-- for requests
    locRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
    locRequest.interval = 5000
    // locRequest.smallestDisplacement = 10f
}

private fun getLastLocation() {
    try
    {
        fusedLocClient.getLastLocation()
            .addOnCompleteListener(object:OnCompleteListener<Location>
            {
                override fun onComplete(@NonNull task:Task<Location>) {
                    if (task.isSuccessful() && task.getResult() != null)
                    {
                        loc = task.getResult() as Location
                    }
                    else
                    {
                        Log.w(TAG, "Failed to get location.")
                    }
                }
            })
    }
    catch (unlikely:SecurityException) {
        Log.e(TAG, "Lost location permission." + unlikely)
    }
}

fun requestLocationUpdates()
{
    setRequestingLocationUpdates(this, true)
    startService(Intent(getApplicationContext(), LocService::class.java))

    try
    {
        fusedLocClient.requestLocationUpdates(locRequest, locCallback, Looper.myLooper())
    } catch (unlikely:SecurityException)
    {
        setRequestingLocationUpdates(this, false)
        Log.e(TAG, "Lost location permission. Couldn't request updates. " + unlikely)
    }
}

// Obtain address via GeoCoder class
private fun getAddress(locResult: LocationResult?): String {
    var address = ""
    var geoCoder = Geocoder(this, Locale.getDefault())

    var loc1 = locResult!!.locations.get(locResult.locations.size-1)

    try {
        var addresses:ArrayList<Address> = geoCoder.getFromLocation(loc1.latitude, loc1.longitude, 1) as ArrayList<Address>
        address = addresses.get(0).getAddressLine(0)
    } catch (e: IOException) {
        e.printStackTrace()
    }

    return address
}

private fun getNotification(): Notification {
    val intent = Intent(this, LocService::class.java)

    val text = getLocationText(loc)

    val builder = NotificationCompat.Builder(this)
        .setContentText(text)
        .setOngoing(true)
        .setPriority(Notification.PRIORITY_HIGH)
        .setTicker(text)
        .setWhen(System.currentTimeMillis())

    return builder.build()
}

// Checks to see if the service is running in the foreground or not
fun serviceIsRunningInForeground(context: Context) : Boolean
{
    val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager

    for (service in manager.getRunningServices(Integer.MAX_VALUE))
    {
        if (javaClass.getName().equals(service.service.getClassName()))
        {
            if (service.foreground)
            {
                return true
            }
        }
    }

    return false
}

val KEY_REQUESTING_LOCATION_UPDATES = "requesting_locaction_updates"

// Returns true if the requesting location updates, else false
fun requestingLocationUpdates(context: Context): Boolean {
    return PreferenceManager.getDefaultSharedPreferences(context)
        .getBoolean(KEY_REQUESTING_LOCATION_UPDATES, false)
}

// Stores the location updates state in SharedPreferences
fun setRequestingLocationUpdates(context: Context, requestingLocationUpdates: Boolean)
{
    PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(KEY_REQUESTING_LOCATION_UPDATES, requestingLocationUpdates).apply()
}

// Returns the coordinates as a string for the notifications
fun getLocationText(loc: Location) : String
{
    if (loc == null) {
        return "Unknown Location"
    } else {
        return "Latitude: " + loc.longitude.toString() + " | Longitude: " + loc.longitude.toString()
    }
}
}

Here's the error:

11-01 00:27:36.923 15995-15995/com.example.localization E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.localization, PID: 15995
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.example.localization/com.example.localization.MainActivity}: kotlin.KotlinNullPointerException
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2327)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
    at android.app.ActivityThread.-wrap11(ActivityThread.java)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:148)
    at android.app.ActivityThread.main(ActivityThread.java:5417)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
 Caused by: kotlin.KotlinNullPointerException
    at com.example.localization.MainActivity.<init>(MainActivity.kt:40)
    at java.lang.Class.newInstance(Native Method)
    at android.app.Instrumentation.newActivity(Instrumentation.java:1067)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2317)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476) 
    at android.app.ActivityThread.-wrap11(ActivityThread.java) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) 
    at android.os.Handler.dispatchMessage(Handler.java:102) 
    at android.os.Looper.loop(Looper.java:148) 
    at android.app.ActivityThread.main(ActivityThread.java:5417) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

Upvotes: 0

Views: 1090

Answers (1)

gidds
gidds

Reputation: 18577

You declare lservice as:

private var lservice: LocService

That means it's not nullable; Kotlin won't allow you to set it to null.

(In particular, note that null!! will always throw an exception: the !! operator tells the compiler to treat an expression as non-null, or to throw an exception if it is.  And since null obviously is null, you're guaranteed an exception!)

If you want to allow the service to be null, you'll have to declare it as:

private var lservice: LocService?

The ? in the type means that it's nullable.  As a result, you'll be able to set it to null without any exception.  However, you'll need to check whether it's null when you use it, to prevent a NullPointerException there.

Nullability is pretty basic to Kotlin. It's all explained in the Kotlin language docs.

Upvotes: 1

Related Questions