0

I have made a Location class myself with two Double val's (the longitude and latitude)

Now in an empty constructor I would like to use a FusedLocationProviderClient to get the device's current location which I then store in the two val's to represent the current location. I make one with LocationServices, then onSucces I put the variables inside the class. The problem is that I get the following error:

Captured member values initialization is forbidden due to possible reassignment

The constructor looks as following:

constructor(context: Context) {
   val client = LocationServices.getFusedLocationProviderClient(context)
    client.lastLocation.addOnSuccessListener { location: Location? ->  
        if (location != null) {
            this.latitude = location.latitude
            this.longitude = location.longitude
        }

        else {
            this.latitude = 0.0
            this.longitude = 0.0
        }
    }
}

I am a bit lost here. How do I work around this issue, so I have the user's coordinates in the variables?

Is there some sort of way for example, where I force the lastlocation to finish first, before storing the variables, so the compiler is sure it has a value which will not be reassigned as well?

1 Answers1

1

I'm assuming the latitude and longitude properties of your class are read-only vals. You cannot make them val if you are not assigning them in the constructor. In your code, you are assigning them in a callback (code that will be called later), so they are not initialized by the time the constructor returns, and you are trying to set their values when they are read-only.

You need to make them into var read-write properties. You can give them default values of 0.0 so they don't have to be nullable.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • Or maybe lateinit vars? – Animesh Sahu May 06 '20 at 13:08
  • I wouldn't personally use `lateinit` for something that is initialized in a callback (unpredictable timing). Then you cannot safely use it without using reflection to check if it's initialized. Anyway, `lateinit` can't be used for primitives. – Tenfour04 May 06 '20 at 13:09
  • Is there some sort of way, so I can wait until the waiting for location is done, before the constructor passes? I tried changing it into var, initializing it to 0.0 But this would print 0.0 when printing the object its long and latitude I made. Probably because the callback was not finished. Could I avoid this easily? – HobbyProgrammer123 May 06 '20 at 13:17
  • Its best to create a coroutine and launch it using constructor and put [suspendCoroutine](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/suspend-coroutine.html) there and store job's instance somewhere in the class's property and call join() on that whenever you require to use the variable. – Animesh Sahu May 06 '20 at 13:25
  • You should not put blocking (time-consuming) code in your constructor, or it will cause your app to hang and risks an Application Not Responding (ANR) error. (But you technically *could* do it using a FutureTask if you don't mind having a janky app that crashes with ANR.) You might consider adding a `ready` property in this class that you set to `true` in the location listener callback. Then instantiate this class in a coroutine and wait for the result using a loop like `while (!myLocationClass.ready) { delay(50) }`. – Tenfour04 May 06 '20 at 13:25
  • Or make the constructor private and take latitude and longitude as the parameters. Make a public factory `suspend` function for it. Wrap the location gathering call in a `suspendCoroutine`. – Tenfour04 May 06 '20 at 13:29
  • Would there maybe be a better way for retrieving a device location without this callback? So coordinates could be retrieved within the constructor as I consider? I understand that doing it this way is less correct as I have it now. – HobbyProgrammer123 May 06 '20 at 13:34
  • You can use `client.lastLocation` to immediately retrieve a (possibly but rarely null) Location. It returns the last known location (from some other app or system service checking the location) which might be good enough for what you're doing. If the device has been on for more than a few minutes, it almost certainly will have a location available. https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient.html#public-tasklocation-getlastlocation – Tenfour04 May 06 '20 at 14:02
  • As can be seen in the code, lastlocation is being used. But that seems to return a Task. So it needs the callback onSuccess, which causes delay and problems at the moment. Or is there a direct way to like find the Location type? – HobbyProgrammer123 May 06 '20 at 14:27
  • Oops! I haven't used this API myself so I'm not really familiar. I would expect this Task to already be complete since it's for an already known location. Can you just use `task.result` instead of a listener? – Tenfour04 May 06 '20 at 14:30
  • Trying to get the result straight away leads to the exception: java.lang.IllegalStateException: Task is not yet complete So I think this would require the success. My question is now whether I can 'wait' for the callback to finish within the constructor. Is there a way I could accomplish this? So the goal is to let this Task finish, return whether a Location or null. Then I want to do something with this. Save the Location in the class fields, or otherwise some other value. – HobbyProgrammer123 May 06 '20 at 16:04
  • Weird that they don't return an already-complete Task. But you should be able to use Android's LocationManager directly to get the instant result. Something like: `val location = (context.getSystemService(Context.LOCATION_SERVICE) as LocationManager).run { val provider = getBestProvider(Criteria(), true); getLastKnownLocation(provider) }` – Tenfour04 May 06 '20 at 19:06