Remember the exercise we did in a previous lesson, with the "plus" and "minus" button incrementing or decrementing a counter on the screen? Maybe your solution looked something like this:

int number = 0;

public void plusButtonClick(View v) {
    number = number + 1;

    TextView textview = (TextView) findViewById(R.id.greetingTextview);
    String message = "The number is currently " + number;
    textview.setText(message);
}

public void minusButtonClick(View v) {
    number = number - 1;

    TextView textview = (TextView) findViewById(R.id.greetingTextview);
    String message = "The number is currently " + number;
    textview.setText(message);
}

Notice how much repetition there is? The only unique thing each button does is change the value of number. They both perform identical steps to display the information on the screen.

Can you think of why this kind of repetition is bad? Try to think of some reasons before reading ahead.

Here's a few big ones:

  • Repeating yourself is a waste of effort. This might seem like laziness, but is an entirely valid reason, especially when your codebase gets larger.
  • There might be slight differences between each repetition that are hard to catch.
  • It's hard to change your code. Say you want the message to read, "Your number" rather than "The number", you're going to have to change it in every place you repeated that line of code. If you find an error in one line of code, its repetitions might still have that error if you forget to propagate the change to all of them. This makes your app inconsistent and unstable.

We've learnt about functions, so we have a neat way to clean this up.

int number = 0;

public void plusButtonClick(View v) {
    number = number + 1;

    updateUI();
}

public void minusButtonClick(View v) {
    number = number - 1;

    updateUI();
}

public void updateUI() {
    TextView textview = (TextView) findViewById(R.id.greetingTextview);
    String message = "The number is currently " + number;
    textview.setText(message);    
}

This is much cleaner. If we add more buttons and features to our app, we can just modify the updateUI() function, and use it. This is a very common pattern you might want to use. Define a common function that updates the UI based on instance variables. All your user inputs can modify those instance variables and call this "update" function. This makes it really easy to add new features, as well as add new elements to the user interface.

Now that we've learnt about functions, let's dig into some of the functions we've already seen and understand them a bit more. Let's start by looking at the method we create for our button onClick.

public void minusButtonClick(View v) {
    Log.d("MainActivity", "hello!");
}

We are now able to understand the pieces of this function. The return value is void. It has one input parameter, a View named v. What do you think this input parameter is?

Remember what this method is for. This is a method that gets called by Android when a Button is clicked in your layout. So this View parameter is the Button (which is a type of View) that called this method. This method signature is required if you want to use it as an onClick method. For example, try removing the View parameter:

public void minusButtonClick() {
    Log.d("MainActivity", "hello!");
}

If you run the app and click the button, your app will crash with the following error: "Could not find method buttonClick(View)". Notice that it is specifically looking for a function of that name which has a View parameter.

This View parameter lets us figure out which View is calling the function. So for example, you can have two different Views call the same onClick function. We can use the View's properties to learn more about it. For example:

public void buttonClick(View v) {
    Log.d("MainActivity", "Is this my button? " + (v.getId() == R.id.button));
}

The getId() function returns the ID of the view, and we can compare it to an ID we want. Based on this, you can use if-else statements to perform different behaviors for different buttons.

You might have noticed while typing the above code that many properties and methods show up in the autocomplete for v. We will learn more about properties when we learn about classes. For now, have a look at this page to get an idea of wide array of properties a View has: https://developer.android.com/reference/android/view/View.html.

Let's loop back to MainActivity and learn a bit more about something we've been ignoring till now.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Your MainActivity should look something like this. onCreate looks a lot like functions we've seen before. There are a few differences: @Override and protected. Again, we will learn about this when we explore classes. For now let's learn about what onCreate is used for.

Each screen in your app is represented by what's called an Activity. Right now our app has just one screen, and hence just one activity, called MainActivity. When MainActivity has started, this onCreate function is called by the Android operating system. So, this is a great place to do any sort of initial setup we want to do.

Let's look at the lines inside the function. The first line is something that's going to have to wait till we learn about classes. The second line calls the setContentView function. This function looks for a layout file and puts it on screen. Notice that R.layout.activity_main looks similar to activity_main.xml. They refer to the same thing. In the future, you could have multiple layouts, and the setContentView function lets you indicate which layout to put up on screen right now.

If you remove that line and run the app, you will see that the screen is left blank.

The onCreate function is a great place to do any initialization we need to do. In fact, the best practice is initializing all of our View references here. For example, this is what our code might look like till now:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void hiButtonClick(View v) {
        TextView textview = (TextView) findViewById(R.id.greetingTextview);
        textview.setText("Hi!");
    }

    public void byeButtonClick(View v) {
        TextView textview = (TextView) findViewById(R.id.greetingTextview);
        textview.setText("Bye!");
    }
}

The code that creates the TextView reference is repetitive. More importantly, each time you create a new variable, it takes up some amount of memory, and it takes some time to run. Running findViewById repeatedly is not a good idea.

Here is the best practice on how to do this. Note the inline comments:

public class MainActivity extends AppCompatActivity {

    TextView textview;     // declaration as an instance variable

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textview = (TextView) findViewById(R.id.greetingTextview);    // initialization
    }

    public void hiButtonClick(View v) {
        textview.setText("Hi!");              // usage
    }

    public void byeButtonClick(View v) {
        textview.setText("Bye!");
    }
}

Declaring our views as instance variables lets all of our functions access them as needed. We only call findViewById once for each new view. This reduces repetition, and makes our code easy to read and understand.

You might notice that lots of code you find online has a similar structure to it. As this is a best practice, make it a point to organize your code in this way in the future as well.

results matching ""

    No results matching ""