Showing posts with label Android. Show all posts
Showing posts with label Android. Show all posts

Wednesday, August 19, 2015

"Variable is accessed within inner class. Needs to be declared final."

Problem:

"Variable is accessed within inner class. Needs to be declared final." or a similar error occurs.

Solution:

Declare the variable final, or make it an instance variable.

Explanation:

Java doesn't want developers to change local variables from within an inner class or an anonymous inner class.

Inner Classes and Local Variables

Any variable defined in a method and accessed by an anonymous inner class must be final. Or, as Oracle says:
"An anonymous class cannot access local variables in its enclosing scope that are not declared as final or effectively final"
Note: "effectively final" is something new introduced in Java SE 8. It is defined as a variable or parameter that is not declared as final, whose value is never changed after it is initialized.

But why make it so inner classes can't modify variables belonging to their outer scope?
The reason is that the inner class "captures" the variable. To understand why this matters, we need to understand the implications of how captured variables work. If you are familiar with closures, that is exactly what's going on here.
The inner class is a closure. It copies the variable from it's enclosing scope to a new variable, and brings just that copy inside the inner class. Anything it does to that copy is independent from the variable in the enclosing scope. So if the variable changes in the inner class, and then it is used later in the enclosing scope, the changes made in the inner class did not persist in the enclosing scope.

Basically, what happens in the inner class stays in the inner class.
Below is a proper scenario.

 
public class RadiusStuff { //This is the outer class
    public void start(Stage stage) { //This method is the enclosing context
    Button submit = new Button("Submit");
    final string radius = "10";
    submit.setOnAction(new EventHandler<ActionEvent>() {
        //This is the inner class, specifically an anonymous class.
        @Override
        public void handle(ActionEvent e) {
            submit.setText(radius);
        }
    });
  }
}

This functionality is by design.

Java wants the developer to use the final keyword on any variables that are going to be modified in the inner class. This prevents us from thinking the things we change in the inner class will persist in the enclosing scope.
In a way, adding the final keyword does not change the behavior of the code. Think about it. With or without the final keyword, any changes to the variable in the inner class won't persist, so why make any changes at all? Java forces developers to use final in this scenario just to emphasize that we shouldn't be modifying a local variable in an inner class.

But what if, in the closure, we are only reading from the variable and not writing to it? Does it still need to be declared final?
If you are using Java 7 or below, the answer is "yes".
In Java 8, if we are only accessing but not changing the variable, the variable is "effectively final" and does not need to be declared final.

The problem with this is that if we ever want to change the variable we won't be able to. In the below code I show an example of this case by assigning a new value to our "radius" variable, but it will not work.
 
public class RadiusStuff { //This is the outer class
    public void start(Stage stage) { //This method is the enclosing context
    Button submit = new Button("Submit");
    final string radius = "10";
    submit.setOnAction(new EventHandler<ActionEvent>() {
        //This is the inner class, specifically an anonymous class.
        @Override
        public void handle(ActionEvent e) {
            submit.setText(radius);
        }
    });
    radius = "15"; //This will throw compile-time error.
  }
}

Instance Variables

I mentioned under the "Solution" section that we could also just make the variable an instance variable. Does this really work? Why?
Referring back to Oracle's documentation we see that our inner class "cannot access local variables in its enclosing scope that are not declared as final..."
So why would an instance variable be an exception? Note the words "local variable". This only applies to variables declared within the method that the inner class is in, also known as the "enclosing scope".
So does this mean an anonymous inner class can change an instance variable, and have those changes persist outside of the inner class?
Yes.

 
public class RadiusStuff { //This is the outer class
    string radius = "10"; //Instance variable, no need to be final.

    public void start(Stage stage) { //This method is the enclosing context
    Button submit = new Button("Submit");
    
    submit.setOnAction(new EventHandler<ActionEvent>() {
        //This is the inner class, specifically an anonymous class.
        @Override
        public void handle(ActionEvent e) {
            submit.setText(radius);
        }
    });
    string radius = "15"; //This is allowed now.
  }
}

Other notes:

  • Variables defined in interfaces are implicitly final, even if they don't have the final keyword.
  • For variables that reference objects, the properties of the object can be modified, even if the variable is final. However, you cannot change which object the variable refers to.
  • You can find excellent information on closures here: C# in depth
  • The behavior mentioned in my post is similar between Java and C#, but there are some differences, so be careful. Perhaps I'll discuss those in a future post.

Wednesday, June 3, 2015

Adding and removing GeoFences to a Google Map

Problem:

There is no native way to add, keep track of, or erase GeoFences that are added to a Google Map.

Solution:

After a GeoFence is successfully added to the GeoFencing API, use its radius, latitude, and longitude to construct a Circle object. Add this Circle to a Stack of Circles, and also add it to the Google Map object. Pop the circle off the stack and remove it from the Map when removing the GeoFence.

Explanation:

This assumes you have a GeoFencing API already set up and a Google Map set up.
If you followed the Android GeoFence tutorial you should end up with something like this:

LocationServices.GeofencingApi.addGeofences(
    mGoogleApiClient,
    getGeofencingRequest(),
    getGeofencePendingIntent()
).setResultCallback(this);

In my code I call this every time a GeoFence is added. If that completes successfully, you can now add your GeoFence to the map. You'll need to know the fence's ID, latitude, longitude, and radius, so make sure to store those somewhere. Then, you're going to want to call your method that will do the work for making the circle with the map. I called mine createCircle() and I call it like this. Latlng is a LatLng type, and rad is a float.

createCircle(new CircleOptions()
    .center(latlng)
    .radius(rad)
    .strokeColor(Color.BLACK));

Here is the method that does the work:

private void createCircle(CircleOptions circleOptions){
    if(mMap != null) {
        Circle newCircle = mMap.addCircle(circleOptions);
        if(newCircle != null) {
            circleStack.push(newCircle);
        } else {
            Log.d(TAG, "newCircle is null");
        }
    } else {
        Log.d(TAG, "mMap is null");
    }
}

mMap is an instance variable of the Google Map that was created earlier in our code. Make sure you only call this after the Map is ready. Remember that Google Maps has the onMapReady() callback method, and this should be in your code already if you implemented OnMapReadyCallback.

circleStack is a stack that I instantiated like this:

private Stack<Circle> circleStack = new Stack<Circle>();

Ta-da! Now we have a circle on our map exactly where the GeoFence is!
And the Stack keeps track of the Circles for when we want to remove them.

Removing Circles and GeoFences:

The Android GeoFence tutorial only shows you how to remove all the geoFences for a pending intent. But there is another removeGeofences() method that takes a different argument and allows you to remove a list of fences based on their IDs. But how do you remove just a single GeoFence? I solved this by adding a button that would remove the most recently added GeoFence from the Geofencing Api and from the Google Map.

To remove the GeoFence from the API, just get the most recently added GeoFence from your ArrayList of GeoFences, which if you followed the Android GeoFence tutorial is called mGeofenceList. The most recently added fence will be at the highest index of your mGeofenceList. So get the fence from the highest index, and then get its ID and save it in a variable. You'll also want to remove from the mGeofenceList.
Then, add that ID to a List<String>. Yes, it seems a bit ridiculous to add a single entry to a List, but we need to use a List with the removeGeofences method. Now we are ready to call GeofencingApi.removeGeofences(), passing it your mGoogleApiClient (the same one from above) and your List of a single GeoFence ID to remove.

Finally, remove the Circle from the map. This is where the stack comes in. Pop the circle from the stack and store it in a Circle variable, and then call remove() on that Circle. Note that pop returns the object that it popped off the top of the stack.

Now your GeoFences are removed from the API, so they won't do anything if you enter, exit, or dwell within them. They are also removed from the map so no one will think a GeoFence exists where it doesn't.

public void removeGeofencesClick(View view) {
    if(mGeofenceList.size() > 0 && mGoogleApiClient.isConnected()) {
        int mGeofenceListLastIndex = mGeofenceList.size() - 1;
        Geofence fenceToRemove = mGeofenceList.get(mGeofenceListLastIndex);
        String geoFenceIdToRemove = fenceToRemove.getRequestId();
        mGeofenceList.remove(mGeofenceListLastIndex);

        //This will only ever hold 1 fence at a time, but removeGeofences()
        //takes a List so that's why we make a List.
        List<String> geoFenceToRemove = new ArrayList<>();
        geoFenceToRemove.add(geoFenceIdToRemove);
        LocationServices.GeofencingApi.removeGeofences(mGoogleApiClient,
            geoFenceToRemove).setResultCallback(this);
        //This removes the last added GeoFence from the Map,
        // but not the GeoFence collection.
        if(!circleStack.empty()){
            Circle circleToRemove = (Circle)circleStack.pop();
            circleToRemove.remove(); //remove it from the Map
        }
    }
}

Tuesday, June 2, 2015

Geofence Transition Bitwise Logic


Problem:

You want to set multiple Geofence transitions either programmatically or through user input, but you can only call setTransitionTypes() once.

Solution:

Use bitwise OR on each transition type to get an int that can represent every combination of transitions.
public void createGeoFence(float rad, double lat, double lng,
    boolean enterChecked, boolean exitChecked, boolean dwellChecked) {
    //Set transitions based on what the user selects.
    int transitions = 0;
    if(enterChecked){
        transitions = Geofence.GEOFENCE_TRANSITION_ENTER;
    }
    if(exitChecked){
        transitions = transitions | Geofence.GEOFENCE_TRANSITION_EXIT;
    }
    if(dwellChecked){
        transitions = transitions | Geofence.GEOFENCE_TRANSITION_DWELL;
    }

    mGeofenceList.add(new Geofence.Builder()
        .setRequestId(fenceId)
        .setCircularRegion(lat, lng, rad)
        .setExpirationDuration(86400000) //24 hours
        .setTransitionTypes(transitions)
        .setNotificationResponsiveness(5000) //5 seconds
        .build());
}

The above sample method gets passed everything you need to construct a GeoFence. The booleans are the results of checking if the check boxes are checked, they will be true for each box that is checked, and false otherwise. I included a screenshot of the checkboxes for a visual aid.

Note that ints are primitive and can't be null, so if they are not assigned a value they are 0. Further, a transitionType of 0 will throw an error. Make sure to handle this. In another part of my code(not shown) I simply made it so that if the Dwell, Enter, and Exit controls were all unchecked the user would not be allowed to create the GeoFence and would be informed of their error.

Explanation:

Google has a pretty clever way for setting the transitions for a geofence. They use bitwise logic. So we should run with that. The pipe '|' operator is a bitwise OR.

Maybe you want to programmatically set your transitions on GeoFences, but you are having issues with the fact that you can only call setTransitionTypes() once for each GeoFence Builder. For me, I wanted to take user input, and add a transition type for each transition type the user selects. Instinctually you may want to examine each user input (checkboxes in this case) and then call setTransitionsTypes() for each one that the user enabled. But you can't. You can only call setTransitionTypes once, and you can only give it one argument.

So you must build your transition types before calling setTranstionTypes(). One simple and readable way to do this is a bitwise OR.
According to the Android API:
GEOFENCE_TRANSITION_ENTER has a value of 1 = 001 binary
GEOFENCE_TRANSITION_EXIT has a value of 2 = 010 binary
GEOFENCE_TRANSITION_DWELL has a value of 4 = 100 binary

Why these values? Look at the binary logic. Adding any combination of values will produce a unique result.
If we do an OR on ENTER and EXIT we get
001
010
-----
011
Which equals 3.

If we do EXIT and DWELL we get
010
100
----
110
Which equals 6.

So through bitwise OR you will always get a unique int between 1 and 7, inclusive. This explains why DWELL doesn't have a value of 3. Because that would mean there are multiple ways to get a value of 3. 

Let's pretend for a minute that DWELL = 011.
If you set that as the transition type, it's not clear if you want a DWELL, or if you want ENTER and EXIT transitions; these all evaluate to 3. And what if you wanted to set your transition types to DWELL and ENTER? There would be a bitwise OR between DWELL and ENTER, which would result in a value of 3 yet again, and the GeoFence wouldn't know if you wanted transitions on dwell, or dwell and enter.
Bitwise logic below, pretending DWELL is 011 and ENTER is 001 (which it really is).
001
011
----
011 

Now lets go back to real life where DWELL = 100 binary. How many different combinations of GeoFence transitions can you have?
That would be (2^3) -1 = 7. The minus 1 is because we can't send a value of 0 to setTransitionTypes without it throwing an error.

So what if Google wanted to add another transition type to the GeoFence? What's the lowest positive value they could use to not interfere with their schema?
It would just be the next highest power of 2, which is 8, also known as 1000 binary.
The number of combinations this would allow is (2^4) -1 = 15.

You may have noticed that in the entire scenario discussed in the post you could just add the values together and you will come up with the same int. The reason I chose the bitwise OR was because I liked the readability of it, and I liked keeping with the same logic Google is using. Adding up 3 ints and passing them to setTransitionTypes may appear to be slightly simpler code, but it makes very little sense if you don't already understand how setTransitionTypes works.