Chapter 12. The Stack, the Heap, and the Garbage Collector

By the end of this chapter, the missing link between Java and our XML layouts will be fully revealed, leaving us with the power to add all kinds of widgets to our apps as we have done before. However, this time, we will be able to control them through our Java code.

In this chapter, we will get to take control of some fairly simple UI elements, such as Button and TextView, and, in the next chapter, we will take things further and manipulate a whole range of UI elements.

To enable us to understand what is happening, we need to find out a bit more about the memory in an Android device and two areas of it – the Stack and the Heap.

In this chapter, we will learn about the following topics:

  • Android UI elements are classes
  • Garbage collection
  • Our UI is on the Heap
  • Special types of class, including Inner and Anonymous

Back to that news flash.

All the Android UI elements are classes too

When our app is run and the setContentView method is called from onCreate, the layout is inflated from XML UI classes and loaded into memory as usable objects. They are stored in a part of the DVM's memory, called the Heap.

Re-introducing references

But where are all these UI objects/classes? We certainly can't see them in our code. And how on earth do we get our hands on them?

The DVM inside every Android device takes care of memory allocation to our apps. In addition, it stores different types of variables in different places.

Variables that we declare and initialize in methods are stored on an area of memory known as the Stack. We can stick to our existing warehouse analogy when talking about the Stack – almost. We already know how we can manipulate variables on the Stack with straightforward expressions. So, let's talk about the Heap and what is stored there.

Note

Important fact: All objects of classes are reference type variables and are just references to the actual objects that are stored on the Heap – they are not the actual objects.

Think of the Heap as a separate area of the same warehouse. The Heap has lots of floor space for odd shaped objects, racks for smaller objects, lots of long rows with smaller sized cubby holes, and so on. This is where objects are stored. The problem is that we have no direct access to the Heap. Think of it as a restricted access part of the warehouse. You can't actually go there, but you can refer to what is stored there. Let's look at what a reference variable actually is.

It is a variable that we refer to and use via a reference. A reference can be loosely, but usefully, defined as an address or location. The reference (address or location) of the object is on the Stack.

So, when we use the dot operator, we are asking Dalvik to perform a task at a specific location – a location that is stored in the reference.

Tip

Reference variables are just that – a reference. They are used as a way to access and manipulate the object (variables and methods), but they are not the actual variable itself.

Why oh why would we ever want a system like this? Just give me my objects on the Stack already. Here is why.

A quick break to throw out the trash

This is what the whole Stack and Heap thing does for us.

As we know, the DVM keeps track of all our objects for us and stores them in a devoted area of our warehouse called the Heap. Regularly, while our app is running, the DVM will scan the Stack, the regular racks of our warehouse, and match up references to objects that are on the Heap, and any objects it finds without a matching reference, it destroys – or, in Java terminology, it garbage collects.

Think of a very discerning refuse vehicle driving through the middle of our Heap, scanning objects to match up to references (on the Stack). No reference means it is garbage now.

If an object has no reference variable, we can't possibly do anything with it anyway because we have no way to access it/refer to it. This system of garbage collection helps our apps run more efficiently by freeing up unused memory.

If this task was left to us, our apps would be much more complicated to code.

So, variables declared in a method are local, on the Stack, and only visible within the method where they were declared. A member variable (in an object) is on the Heap and can be referenced from anywhere where there is a reference to it and the access specification (encapsulation) allows.

Seven facts about the Stack and the Heap

Let's take a quick look at what we learned about Stack and Heap:

  1. You don't delete objects, but the VM sends the garbage collector when it thinks it is appropriate. This is usually when there is no active reference to the object.
  2. Local variables and methods are on the Stack and the local variables are local to the specific method within which they were declared.
  3. Instance/class variables are on the Heap (with their objects), but the reference to the object (its address) is a local variable on the Stack.
  4. We control what goes onto the Stack. We can use the objects on the Heap, but only by referencing them.
  5. The Heap is kept clear and up-to-date by the garbage collector.
  6. An object is garbage collected when there is no longer a valid reference to it. So, when a reference variable is removed from the Stack, then its related object becomes viable for garbage collection. And when the DVM decides the time is right (usually very promptly), it will free up the RAM memory to avoid running out.
  7. If we try to reference an object that doesn't exist, we will get a NullPointerException and the app will crash.

Let's move on and see exactly what this information buys us in terms of taking control of our UI.

So how does this Heap thing help me?

Any UI element that has its id attribute set in the XML layout can have its reference retrieved from the Heap using the findViewById method, which is part of the Activity/AppCompatActivity class. As it is part of the class that we extend in all our apps, we have access to it, just like the following code shows:

myButton = (Button) findViewById(R.id.myButton);

The preceding code assumes that myButton has been declared previously to an appropriate type, in this case, Button. The code also assumes that within the XML layout is a Button with an id attribute set to myButton.

Notice that findViewById is also polymorphic. We know this because we use a cast, (Button), to be explicit about making the returned object a Button from its View parent type, just like we did with our object of type Elephant with the feed method in the last chapter.

This is quite exciting because it implies we can grab a reference to a whole bunch of stuff from our layout. We can then start using all the methods that these objects have. Here are some examples of the methods we can use for Button objects:

myButton.setText
myButton.setHeight
myButton.setOnCLickListener
myButton.setVisibility

Note

The Button class alone has around 50 methods!

If you think that after eleven chapters, we are finally going to start doing some neat stuff with Android, you would be right!

Using Buttons and TextView widgets from our layout

To follow along with this project, create a new Android Studio project, call it Java Meet UI, choose the Empty Activity template, and leave all the other options at their defaults. As usual, you can find the Java code and the XML layout code in the Chapter 12/Java Meet UI folder.

First, let's build a simple UI by observing the following steps:

  1. In the editor window of Android Studio, switch to activity_main.xml and make sure that you are on the Design tab.
  2. Delete the auto-generated TextView, the one that reads "Hello world!".
  3. Add a TextView widget to the top center of the layout.
  4. Set its text property to 0, its id property to txtValue, and its textSize to 40sp. Pay careful attention to the case of the id. It has an uppercase V.
  5. Now, drag and drop six buttons on the layout so that it looks a bit like the following diagram. The exact layout isn't important:
    Using Buttons and TextView widgets from our layout
  6. When the layout is how you want it, click the Infer Constraints button to constrain all the UI items.
  7. Double left-click on each button in turn (left to right, and then top to bottom) and set the text and id properties, as shown in the following table:

    The text property

    The id property

    add

    btnAdd

    take

    btnTake

    grow

    btnGrow

    shrink

    btnShrink

    hide

    btnHide

    reset

    btnReset

When you're done, your layout should look like the following diagram:

Using Buttons and TextView widgets from our layout

The precise position and text on the buttons is not very important, but the values given to the id properties must be the same. The reason for this is that we will be using these ids to get a reference to the buttons and the TextView in this layout from our Java code.

Switch to the MainActivity.java tab in the editor and we will write the code.

Amend the following line:

public class MainActivity extends AppCompatActivity{

To the following:

public class MainActivity extends AppCompatActivity implements
   View.OnClickListener{

Tip

You will need to import the View class. Be sure to do this before continuing with the next step or you will get confusing results.

import android.view.View;

Notice that the entire line we just amended is underlined in red, showing an error. Now, because we have made MainActivity into an OnClickListener by adding it as an interface, we must implement the abstract method of OnClickListener. The method is called onClick. When we add the method, the error will be gone.

We can get Android Studio to add it for us by left-clicking anywhere on the line containing an error and then using the keyboard combination Alt + Enter. Left-click Implement methods, as shown in the following screenshot:

Using Buttons and TextView widgets from our layout

Now, left-click OK to confirm that we want Android Studio to add the onClick method. The error is gone, and we can carry on adding code. We also have an onClick method and we will soon see what we will do with that.

Now, we will declare an int variable called value and initialize it to 0. We will also declare six Button objects and a TextView object. We will give them the exact same names we gave the id property values in our UI layout. This name association is not mandatory, but it is useful to keep track of which Button in our Java code will be holding a reference to which Button from our UI.

Furthermore, we are declaring them all with the private access specification because we know they will not be needed outside of this class.

Before you go ahead and type the code, note that all these variables are members of the MainActivity class. This means that we enter all the code shown next, immediately after the class declaration that we amended in the previous step.

Making all these variables into members/fields means that they have class scope and that we can access them from anywhere within the MainActivity class. This will be essential for this project because we will need to use them all in onCreate and in our new onClick method.

Enter the following code that we have just discussed after the opening curly brace { of the MainActivity class and before the onCreate method:

// An int variable to hold a value
private int value = 0;

// A bunch of Buttons and a TextView
private Button btnAdd;
private Button btnTake;
private TextView txtValue;
private Button btnGrow;
private Button btnShrink;
private Button btnReset;
private Button btnHide;

Tip

Remember to use the ALT + Enter keyboard combination to import new classes.

import android.widget.Button;
import android.widget.TextView;

Next, we want to prepare all our variables ready for action. The best place for this to happen is in the onCreate method because we know that will be called by Android just before the app is shown to the user. This code uses the findViewById method to associate each of our Java objects with an item from our UI.

It does so by returning a reference to the object associated with the UI widget on the Heap. It "knows" which one we are after because we use the proper id as an argument. For example ...(R.id.btnAdd) will return the Button with the text ADD that we created in our layout.

Note

As a reminder, we use the odd looking = (Button) syntax because the method is polymorphic and could potentially return any object type that is a subclass of the View class. This is called casting.

Enter the following code, just after the call to setContentView in the onCreate method:

// Get a reference to all the buttons in our UI
// Match them up to all our Button objects we declared earlier
btnAdd = (Button) findViewById(R.id.btnAdd);
btnTake = (Button) findViewById(R.id.btnTake);
txtValue = (TextView) findViewById(R.id.txtValue);
btnGrow = (Button) findViewById(R.id.btnGrow);
btnShrink = (Button) findViewById(R.id.btnShrink);
btnReset = (Button) findViewById(R.id.btnReset);
btnHide = (Button) findViewById(R.id.btnHide);

Now that we have a reference to all our Button objects and our TextView, we can start using their methods. In the code that follows, we use the setOnClickListener method on each of the Button references to make Android pass any clicks from the user onto our onClick method.

This works because when we implemented the View.OnClickListener interface, our MainActivity class effectively became an OnClickListener.

So, all we have to do is call setOnClickListener on each button in turn. As a reminder, the this argument is a reference to MainActivity. So, the method call says, "hey Android, I want an OnClickListener and I want it to be the MainActivity class."

Android now knows on which class to call onClick. The following code wouldn't work if we hadn't implemented the interface first. Also, we must set up these listeners before the Activity starts, which is why we do it in onCreate.

We will add code to onClick to actually handle what happens soon.

Add the following code after the previous code, inside the onCreate method:

// Listen for all the button clicks
btnAdd.setOnClickListener(this);
btnTake.setOnClickListener(this);
txtValue.setOnClickListener(this);
btnGrow.setOnClickListener(this);
btnShrink.setOnClickListener(this);
btnReset.setOnClickListener(this);
btnHide.setOnClickListener(this);

Now, scroll down to the onClick method that Android Studio added for us after we implemented the OnClickListener interface. Add the float size; variable declaration and an empty switch block inside it so that it looks like the following code snippet. The new code to add is highlighted here:

public void onClick(View view)

      // A local variable to use later
      float size;

      switch(view.getId()){
      
      
      }
}

Remember that switch will check for a case to match the condition inside the switch statement.

In the previous code, the switch condition is view.getId(). Let's step through and explain this. The view variable is a reference to an object of the View type that was passed into the onClick method by Android:

public void onClick(View view)

View is the parent class for Button, TextView, and more. So, perhaps, as we might expect, calling v.getId() will return the id attribute of the UI widget that has been clicked and triggered the call to onClick in the first place.

All we need to do then is provide a case statement (and appropriate action) for each of the Button references we want to respond to.

The following code we will see is the first three case statements. They handle R.id.btnAdd, R.id.btnTake, and R.id.btnReset.

The code in the R.id.btnAdd case simply increments the value variable, and then it does something new.

It calls the setText method on the txtValue object. Here is the argument –(""+ value). This argument uses an empty string and adds (concatenates) whatever value is stored in value to it. This has the effect of causing our TextView txtValue to display whatever value is stored in value.

The TAKE button (R.id.btnTake) does exactly the same, only it subtracts one from value instead of adding one.

The third case statement handles the RESET button and sets value to zero, again updating the text attribute of txtValue.

Then, at the end of each case, there is a break statement. At this point, the switch block is exited, the onClick method returns, and life goes back to normal, until the user's next click.

Enter the following code that we have just discussed inside the switch block after the opening curly brace {:

case R.id.btnAdd:
   value ++;
   txtValue.setText(""+ value);

   break;

case R.id.btnTake:
   value--;
   txtValue.setText(""+ value);

   break;

case R.id.btnReset:
   value = 0;
   txtValue.setText(""+ value);

   break;

The next two case statements handle the SHRINK and GROW buttons from our UI. We can confirm this from the id's R.id.btnGrow and R.id.btnShrink methods. What is new and more interesting are the two new methods that are used.

The getTextScaleX method returns the horizontal scale of the text within the object it is used on. We can see that the object it is used on is our TextView txtValue. The size = at the start of the line of code assigns that returned value to our float variable size.

The next line of code in each case statement changes the horizontal scale of the text using setTextScaleX. When the GROW button is pressed, the scale is set to size + 1, and when the SHRINK button is pressed, the scale is set to size - 1.

The overall effect is to allow these two buttons to grow and shrink the text in txtValue by a scale of 1 on each click.

Enter the following two case statements that we have just discussed, below the previous code:

case R.id.btnGrow:
   size = txtValue.getTextScaleX();
   txtValue.setTextScaleX(size + 1);

   break;

case R.id.btnShrink:
   size = txtValue.getTextScaleX();
   txtValue.setTextScaleX(size - 1);

   break;

In our final case statement that we will code next, we have an if-else block. The condition takes a little bit of explaining, so here is advance sight of it:

if(txtValue.getVisibility() == View.VISIBLE)

The condition to be evaluated is txtValue.getVisibility() == View.VISIBLE. The first part of that before the == operator returns the visibility attribute of our txtValue TextView. The return value will be one of three possible constant values as defined in the View class. They are View.VISIBLE, View.INVISIBLE, and View.GONE.

If the TextView is visible to the user on the UI, the method returns View.VISIBLE, the condition is evaluated as true, and the if block is executed.

Inside the if block, we use the setVisibility method on txtValue and make it invisible to the user with the View.INVISIBLE argument.

In addition to this, we change the text on the btnHide to SHOW using the setText method.

After the if block has executed, txtValue is invisible, and we have a button on our UI that says SHOW. When the user clicks on it in this state, the if statement will be false and the else block will execute. In the else block, we reverse the situation. We set txtValue back to View.VISIBLE and the text property on btnHide back to HIDE.

If this is in any way unclear, just enter the code, run the app, and revisit this final piece of code and explanation once you have seen it in action:

case R.id.btnHide:
   if(txtValue.getVisibility() == View.VISIBLE)
   {
         // Currently visible so hide it
         txtValue.setVisibility(View.INVISIBLE);

         // Change text on the button
         btnHide.setText("SHOW");

   }else{
         // Currently hidden so show it
         txtValue.setVisibility(View.VISIBLE);

         // Change text on the button
         btnHide.setText("HIDE");
   }


   break;

We have the UI and the code in place, so it is time to run the app and try out all the buttons. Notice that the ADD and TAKE buttons change the value of value by one in either direction and then display the result in the TextView. In the following diagram, I have clicked the ADD button three times:

Using Buttons and TextView widgets from our layout

Notice that the SHRINK and GROW buttons increase the width of the text, and RESET sets the value variable to 0 and displays it on the TextView. In the following diagram, I have clicked the GROW button eight times:

Using Buttons and TextView widgets from our layout

Finally, the HIDE button not only hides the TextView, but changes its own text to SHOW and will indeed re-show the TextView if tapped again.

Tip

I will not bother you by showing you an image of something that is hidden. Make sure to try the app in Android Studio as well as following along with the book.

Notice that there was no need for Log or Toast in this app as we are finally manipulating the UI by using our Java code.

The Inner and Anonymous classes

Before we go ahead to the next chapter and build apps with loads of different widgets that will put into practice and reinforce everything we have learned in this chapter, we will have a very brief introduction to Anonymous and Inner classes.

When we implemented our Basic classes demo app in Chapter 10, Object-Oriented programming, we declared and implemented the class in a separate file to our MainActivity class. That file had to have the same name as the class. This is the way to create a regular class.

We can also declare and implement classes within a class. The only question remaining, of course, is why would we do this?

When we implement an inner class, the inner class can access the member variables of the enclosing class and the enclosing class can access the members of the inner class.

This often makes the structure of our code more straightforward. Therefore, inner classes are sometimes the way to go.

In addition, we can also declare and implement an entire class within a method of one of our classes. When we do so, we use a slightly different syntax and do not use a name with the class. This is an anonymous class.

We will see both inner and anonymous classes in action throughout the rest of this book and we will thoroughly discuss them when we use them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.138.123