Android:Exercises Android Basics

From Juneday education
Jump to: navigation, search

Introduction

In the exercises below you will

  • add Views to your Activity using Design view
  • add Views to your Activity using XML
  • use the Android Logging class
  • invoke a method when clicking a Button
  • do common mistakes and, of course, solve them
  • learn how to use the Android Monitor

Preparation

Start up Android Studio and create a new project as you did in Android:Setting up your development environment - why not use the Throwaway project we created there?

Exercises using Design view

Create a new button

In Android Studion, open the file res/layout/activity_main.xml. You can view this file in two ways:

  1. as text
  2. as a graphical preview

We're going to start of by looking at the graphical view, so chose Design. You should now be able to drag-n-drop a Button to your Activity. It does not matter where you place the button as long as you can see it.

Build and execute your app with the new button.

Add an action to the view

Select the button in the design view.

  1. Change the text of the Button to "Update".
  2. find an entry saying onClick. Enter the name onUpdateClick a method you'd like to get invoked (called) when clicking the button.

Build and execute your app. The button should now be seen with the text "Update" instead of "Button."

Click the Button. Uh oh, the app crashes.

Crash - read the signs

In Android Studio, open the Android Monitor' (bottom row). Scroll up to find the cause of the crash.

Process: se.juneday.throwaway.throwaway, PID: 11820
java.lang.IllegalStateException: Could not find method onUpdateClick(View) in a parent or ancestor Context for android:onClick attribute defined on view class android.support.v7.widget.AppCompatButton with id 'button'
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.resolveMethod(AppCompatViewInflater.java:327)
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:284)
at android.view.View.performClick(View.java:5702)
at android.widget.TextView.performClick(TextView.java:10888)
at android.view.View$PerformClick.run(View.java:22541)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7229)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

This really should give you a hint: Could not find method onUpdateClick(View). Not so surprising, we added a method that we wanted Android to invoke when pressing the Button. Android tries to do that. Don't get mad about your app crashing when you forget to add the method - it's better to have a crash when developing the code than your customer discover an odd behaviour.

Note: why learning how to crash an app? When developing Java code in general, and Android in particular, we will get some exceptions casing a crash. We believe it is better to have seen the most common problems and having practiced solving these than to spending time on using search engines to find what's wrong with your app. With this exercise we also teach you to use the Android Monitor. ... if you want to become a developer it is important to be familiar with common "errors" and familar with existng tools.

Add the method

Open the file MainActivity.java. Add an instance method to your class (MainActivity) like this:

    public void onUpdateClick(View v) {        
    }

You will see a warning that the symbol View can't be resolved. You need to add an import statement. You can do this manually by adding:

import android.view.View;

or by clicking Alt+Enter when your cursor is located on the text View.

Build and run your app.

Do something in your method

Let's start of by logging a simple debug message.

  1. Add this tag to you class
    private static final String TAG = "MyActivity";
    
  2. Add this log message
Log.v(TAG, "View: " + v);

You need to import the Log class, either:

import android.util.Log;

or (as before) click Alt+Enter.

Build and run the app. Click the Button and check what is printed in the Android Monitor. You should be able to see:

03-05 23:20:58.551 17179-17179/se.juneday.throwaway.throwaway V/MyActivity: View: android.support.v7.widget.AppCompatButton{743dac4 VFED..C.. ...P.... 0,0-352,192 #7f0b005c app:id/button}

where the interesting part really is:

V/MyActivity

which is the log tag, and

View: android.support.v7.widget.AppCompatButton

which is the text we wanted to log.

Note: the view you're being passed as a parameter is that Button itself.

Changing views?

Since the view we're being passed is the Button. Can we change the text of the button? Well, of course we can. And we shall. We need to add code to the onUpdateClick method.

The following will not compile:

v.setText("Wow");

since there is no method called setText in View. We need to cast the view to a Button.

((Button)v).setText("Wow!");

The method should now look something like this:

    public void onUpdateClick(View v) {
        Log.v(TAG, "View: " + v);
        ((Button)v).setText("Wow!");
    }

We can do this safely since this method only will be invoked when pressing the Button.

Note: If you're planning on reusing the method you might have to take another approach. But we recommend separate methods for actions rather than a command method with tons of if-statements.

Build and run your app. When pressing the Button you shall see that the text in the Button shall change to Wow.

Exercises using XML

Add one more button

In Android Studion, open the file res/layout/activity_main.xml again. we previously said that you can view this file in two ways:

  1. as text
  2. as a graphical preview

We've tried out the latter so let's check out the first.

In the XML file you can see your button defined, something like this:

   <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onUpdateClick"
        android:text="Update"
        tools:layout_editor_absoluteX="61dp"
        tools:layout_editor_absoluteY="114dp" />

Add a new Button definition below the previous one - in other words add one more button, this time using XML. Add the following code

   <Button

and press enter. Chose "wrap_content". Add the text:

        android:text="Don't push it!"

Build and run the app. Uh oh, most likely you will not be able to see the Button. This has to do with the Layout. For now, simply tell Android to put the new button underneath the previous one:

        app:layout_constraintTop_toBottomOf="@id/button"

Let's write the entire button definition to make it easier to follow:

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Don't push it!"
        app:layout_constraintTop_toBottomOf="@id/button"/>

Build and run the app.

Act on pressing the new button

Look at the previos button's definition:

        android:onClick="onUpdateClick"

The line above was added when you added onUpdateClick in the Design view. It should be farly obvious how to add another method to handle when the new button is pressed.

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Don't push it!"
        android:onClick="onStupidClick"
        app:layout_constraintTop_toBottomOf="@id/button"/>

Build and run the app. As you may have already figured out the app will crash when pressing the button. So let's be pro-active and add, to Mainactivity.java, the method Android will invoke, onStupidClick:

    public void onStupidClick(View v){
        Log.v(TAG, "Stupid click, view: " + v);
    }

Build and run the app. When clicking the button you should see something like this:

Stupid click, view: android.support.v7.widget.AppCompatButton{ee7812e VFED..C.. ...P.... 0,192-494,384}

Change someone else's view

Let's change the text in the first button when pressing the second button. In order to do this we need to get a reference to the updateButton object. You can do this by calling the method findViewById.

Note: when finding views, using findViewById, and casting the resulting View to a specific type, e g Button it is of course important that the view you look up actuallt is of the same type as the type you're casting to.

Suggested solution

Expand using link to the right to see the full content.

Looking up the Button is done like this:

findViewById(R.id.button);

The method returns a View, which does not have a method to alter the text. Such a method can be found in Button, which actually is the type of the Button object. So we need to tyoecast the View returned:

        Button updateButton = (Button) findViewById(R.id.button);

and now we're ready to alter the text.

        updateButton.setText("Uh oh");

Here's the entire method:

    public void onStupidClick(View v){
        Button updateButton = (Button) findViewById(R.id.button);
        updateButton.setText("Uh oh");
    }

Faulty typecast

Let's add yet another View to your app.

Add a Spinner

Add a Spinner to the Activity. Place the spinner below the second button.

Build and run the app.

Log the spinner

When clicking the "Don't push it" print out the spinner using Log.

Build and run the app.

Update the spinner

When pressing the button, the Spinner's text should also be updated - which means you need to use setText if possible (nudge nudge).

Wait for an error message from the compiler - typically some red text. Since the Spinner has nomethod called setText we can't invoke it.

Typecast a Spinner to a Button

Try casting the Spinner to a Button and then change the text

Build the app. Nice, it works (well, actually it compiles). Now, run the app.

Your app will crash. What is the error message (look in the Android Monitor).


Suggested solution

Expand using link to the right to see the full content.

Add a Spinner

First of all, when putting the Spinner below the second button ("Don't push it") we need to be able to find that button using findViewById. So we need to add an id to that button. Add an id to the second Button:

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/stupidButton"
        android:text="Don't push it!"
        android:onClick="onStupidClick"
        app:layout_constraintTop_toBottomOf="@id/button"/>

Ok, we can now add the Spinner. Start, assuming you're editing XML rather than using the Design view, by writing

    <Spinner

and press enter and chose wrap_content for both android:layout_width and android:layout_height.

    <Spinner
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
    </Spinner>

Now, it's time to add a constraint to place the Spinner:

        app:layout_constraintTop_toBottomOf="@id/stupidButton"

Log the spinner

Ok, so let's find the Spinner in the method onStupidClick. We find it using findViewById. Keep the existing code in the method:

        Spinner spinner = (Spinner) findViewById(R.id.spinner);

and add coresponding code the Log:

        Log.d(TAG, "spinner: " + spinner);

When running the app you should see something like:

D/MyActivity: spinner: android.support.v7.widget.AppCompatSpinner{9328806 V.ED..C.. ........ 0,384-192,480 #7f0b005e app:id/spinner}

in the Android Monitor. Great. Now, change the text using setText.

Update the spinner

Add code to set the text

        Spinner spinner = (Spinner) findViewById(R.id.spinner);
        spinner.setText("Uh oh");

This will not compile since the spinner has no method called setText. .... too bad. But kind of expected... You can't always get what you want. Let's cast differently (actually in a really bad way which compiles but crashes in runtime).

Typecast a Spinner to a Button

Replace the following code in the onStupidClick method:

        Spinner spinner = (Spinner) findViewById(R.id.spinner);
        Log.d(TAG, "spinner: " + spinner);
        spinner.setText("Uh oh");

with

        Spinner spinner = (Spinner) findViewById(R.id.spinner);
        Log.d(TAG, "spinner: " + spinner);
//        spinner.setText("Uh oh");
        Button b = (Button) findViewById(R.id.spinner);
        b.setText("Just made typecast a Spinner to a Button");

Running the app causes a crash and an error message (check the Android Monitor):

03-06 10:19:23.581 9080-9080/se.juneday.throwaway.throwaway E/AndroidRuntime: FATAL EXCEPTION: main
                                                                              Process: se.juneday.throwaway.throwaway, PID: 9080
                                                                              java.lang.IllegalStateException: Could not execute method for android:onClick
                                                                                  at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293)
                                                                                  at android.view.View.performClick(View.java:5702)
                                                                                  at android.widget.TextView.performClick(TextView.java:10888)
                                                                                  at android.view.View$PerformClick.run(View.java:22541)
                                                                                  at android.os.Handler.handleCallback(Handler.java:739)
                                                                                  at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                                  at android.os.Looper.loop(Looper.java:158)
                                                                                  at android.app.ActivityThread.main(ActivityThread.java:7229)
                                                                                  at java.lang.reflect.Method.invoke(Native Method)
                                                                                  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
                                                                                  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
                                                                               Caused by: java.lang.reflect.InvocationTargetException
                                                                                  at java.lang.reflect.Method.invoke(Native Method)
                                                                                  at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
                                                                                  at android.view.View.performClick(View.java:5702) 
                                                                                  at android.widget.TextView.performClick(TextView.java:10888) 
                                                                                  at android.view.View$PerformClick.run(View.java:22541) 
                                                                                  at android.os.Handler.handleCallback(Handler.java:739) 
                                                                                  at android.os.Handler.dispatchMessage(Handler.java:95) 
                                                                                  at android.os.Looper.loop(Looper.java:158) 
                                                                                  at android.app.ActivityThread.main(ActivityThread.java:7229) 
                                                                                  at java.lang.reflect.Method.invoke(Native Method) 
                                                                                  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230) 
                                                                                  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120) 
                                                                               Caused by: java.lang.ClassCastException: android.support.v7.widget.AppCompatSpinner cannot be cast to android.widget.Button
                                                                                  at se.juneday.throwaway.throwaway.MainActivity.onStupidClick(MainActivity.java:34)
                                                                                  at java.lang.reflect.Method.invoke(Native Method) 
                                                                                  at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288) 
                                                                                  at android.view.View.performClick(View.java:5702) 
                                                                                  at android.widget.TextView.performClick(TextView.java:10888) 
                                                                                  at android.view.View$PerformClick.run(View.java:22541) 
                                                                                  at android.os.Handler.handleCallback(Handler.java:739) 
                                                                                  at android.os.Handler.dispatchMessage(Handler.java:95) 
                                                                                  at android.os.Looper.loop(Looper.java:158) 
                                                                                  at android.app.ActivityThread.main(ActivityThread.java:7229) 
                                                                                  at java.lang.reflect.Method.invoke(Native Method) 
                                                                                  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230) 
                                                                                  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120) 

where the interesting parts are:

 Caused by: java.lang.ClassCastException: android.support.v7.widget.AppCompatSpinner cannot be cast to android.widget.Button                                                                         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120) 

Lessons learned? Think about what you're doing?

Links

Source code

External links

Books this chapter is a part of

Android - the practical way

Book TOC | previous chapter | next chapter