Search code examples
androidfunctionkotlinvariables

Not able to return variable from inside a function


So i'm trying to return a value from a function to use inside the mainActivity class, but I get an error when I try to return the variable stating that the 'variable must be initialized' even though I have given a value to it. Any thoughts on this?

private fun getCLocation() : LocationClass {

        var loc: LocationClass

        if(checkPermission()){

            if(isLocationEnabled()){

                locationVariable.lastLocation.addOnCompleteListener(this) { task->

                    val location:Location? = task.result

                    if(location == null){
                        Toast.makeText(this,"NULL",Toast.LENGTH_LONG).show()
                    }
                    else{
                        loc = LocationClass(location.latitude.toString(),location.longitude.toString())
                    }
                }

            } else {
                // location not enabled,open settings
                val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
                startActivity(intent)
            }

        } else {
            //Permission Not Enabled
            requestPermission()
        }

        return loc
    }

Error is: Variable 'loc' must be initialized


Solution

  • The two problems with your code:

    1. The user may not have granted the permission yet, so it would enter the else block, where the function requests a permission, but doesn't do anything to set the initial location value. The function couldn't possibly return a valid location in this circumstance.
    2. Getting location updates is asynchronous. Even if the permission is already granted, your OnCompleteListener will not be called until some time in the future, after this function has already returned. You can read more explanations of what asynchronous APIs are here.

    Here's a basic strategy for what to do. The function that gets a location takes a callback parameter instead of returning the LocationClass directly since it is asynchronous. You need a higher-level logic that can be retried after location or permissions become available.

    private var triedPromptingLocationSetting = false
    private var locationWorkflowInitiated = false // SET THIS BACK TO FALSE IN onViewDestroyed() IF IN FRAGMENT
    
    private fun fetchCLocation(onReceived: (LocationClass?)->Unit) {
        if (!checkPermission() || isLocationEnabled()) {
            Log.e("Do not call getCLocation() before permission is granted and location is turned on! Ignoring.")
            return
        }
        locationVariable.lastLocation.addOnCompleteListener(this) { task->
            val location:Location? = task.result
            if (location == null) {
                onReceived(null)
            }
            else {
                var loc = LocationClass(location.latitude.toString(),location.longitude.toString())
                onReceived(loc)
            }
        }
    }
    
    // This is where the logic is for whatever you wanted to do with this location.
    // In this example, it's assumed it would be called in onResume()
    private fun doMyLocationWorkflow() {
        if (!checkPermission()) {
            requestPermission()
            return
        }
        if (!isLocationEnabled()) {
            if (triedPromptingLocationSetting) {
                // can't infinitely loop back to settings. 
                showSomeUiRequestingUserToManuallyEnableLocation()
            } else {
                triedPromptingLocationSetting = true
                val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
                startActivity(intent)
            }
            return
        }
    
        locationWorkflowInitiated = true
    
        fetchCLocation { location ->
            if (location == null) {
                Toast.makeText(this,"NULL",Toast.LENGTH_LONG).show()
                //...
                return@fetchCLocation 
            }
            // Do something with the LocationClass
        }
    }
    
    override fun onResume() {
        super.onResume()
        if (!locationWorkflowInitiated) {
            doMyLocationWorkflow()
        }
    }
    
    private val locationPermissionRequest = registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ) { permissions ->
            when {
                permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> {
                    doMyLocationWorkflow() // go back to original workflow
                }
                permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> {
                    doMyLocationWorkflow() // go back to original workflow
                } else -> {
                    showSomeUiTellingUserPermissionMustBeGranted()
                }
            }
        }
    
    private fun requestPermission() {
        locationPermissionRequest.launch(arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION))
    }