Passing Data Between Activities

Now that you have a QuizActivity and a CheatActivity, you can think about passing data between them. Figure 5.9 shows what data you will pass between the two activities.

Figure 5.9  The conversation between QuizActivity and CheatActivity

Illustration shows conversation between QuizActivity and CheatActivity.

The QuizActivity will inform the CheatActivity of the answer to the current question when the CheatActivity is started.

When the user presses the Back button to return to the QuizActivity, the CheatActivity will be destroyed. In its last gasp, it will send data to the QuizActivity about whether the user cheated.

You will start with passing data from QuizActivity to CheatActivity.

Using intent extras

To inform the CheatActivity of the answer to the current question, you will pass it the value of

    mQuestionBank[mCurrentIndex].isAnswerTrue()

You will send this value as an extra on the Intent that is passed into startActivity(Intent).

Extras are arbitrary data that the calling activity can include with an intent. You can think of them like constructor arguments, even though you cannot use a custom constructor with an activity subclass. (Android creates activity instances and is responsible for their lifecycle.) The OS forwards the intent to the recipient activity, which can then access the extras and retrieve the data, as shown in Figure 5.10.

Figure 5.10  Intent extras: communicating with other activities

Illustration shows an activity.

An extra is structured as a key-value pair, like the one you used to save out the value of mCurrentIndex in QuizActivity.onSaveInstanceState(Bundle).

To add an extra to an intent, you use Intent.putExtra(…). In particular, you will be calling:

    public Intent putExtra(String name, boolean value)

Intent.putExtra(…) comes in many flavors, but it always has two arguments. The first argument is always a String key, and the second argument is the value, whose type will vary. It returns the Intent itself, so you can chain multiple calls if you need to.

In CheatActivity.java, add a key for the extra.

Listing 5.8  Adding an extra constant (CheatActivity.java)

public class CheatActivity extends AppCompatActivity {

    private static final String EXTRA_ANSWER_IS_TRUE =
        "com.bignerdranch.android.geoquiz.answer_is_true";
    ...

An activity may be started from several different places, so you should define keys for extras on the activities that retrieve and use them. Using your package name as a qualifier for your extra, as shown in Listing 5.8, prevents name collisions with extras from other apps.

Now you could return to QuizActivity and put the extra on the intent, but there is a better approach. There is no reason for QuizActivity, or any other code in your app, to know the implementation details of what CheatActivity expects as extras on its Intent. Instead, you can encapsulate that work into a newIntent(…) method.

Create this method in CheatActivity now.

Listing 5.9  A newIntent(…) method for CheatActivity (CheatActivity.java)

public class CheatActivity extends AppCompatActivity {

    private static final String EXTRA_ANSWER_IS_TRUE =
            "com.bignerdranch.android.geoquiz.answer_is_true";

    public static Intent newIntent(Context packageContext, boolean answerIsTrue) {
        Intent intent = new Intent(packageContext, CheatActivity.class);
        intent.putExtra(EXTRA_ANSWER_IS_TRUE, answerIsTrue);
        return intent;
    }
    ...

This static method allows you to create an Intent properly configured with the extras CheatActivity will need. The answerIsTrue argument, a boolean, is put into the intent with a private name using the EXTRA_ANSWER_IS_TRUE constant. You will extract this value momentarily. Using a newIntent(…) method like this for your activity subclasses will make it easy for other code to properly configure their launching intents.

Speaking of other code, use this new method in QuizActivity’s cheat button listener now.

Listing 5.10  Launching CheatActivity with an extra (QuizActivity.java)

mCheatButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Start CheatActivity
        Intent intent = new Intent(QuizActivity.this, CheatActivity.class);
        boolean answerIsTrue = mQuestionBank[mCurrentIndex].isAnswerTrue();
        Intent intent = CheatActivity.newIntent(QuizActivity.this, answerIsTrue);
        startActivity(intent);
    }
});

You only need one extra, but you can put multiple extras on an Intent if you need to. If you do, add more arguments to your newIntent(…) method to stay consistent with the pattern.

To retrieve the value from the extra, you will use:

    public boolean getBooleanExtra(String name, boolean defaultValue)

The first argument is the name of the extra. The second argument of getBooleanExtra(…) is a default answer if the key is not found.

In CheatActivity, retrieve the value from the extra in onCreate(Bundle) and store it in a member variable.

Listing 5.11  Using an extra (CheatActivity.java)

public class CheatActivity extends AppCompatActivity {

    private static final String EXTRA_ANSWER_IS_TRUE =
            "com.bignerdranch.android.geoquiz.answer_is_true";

    private boolean mAnswerIsTrue;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cheat);

        mAnswerIsTrue = getIntent().getBooleanExtra(EXTRA_ANSWER_IS_TRUE, false);
    }
    ...
}

Note that Activity.getIntent() always returns the Intent that started the activity. This is what you sent when calling startActivity(Intent).

Finally, wire up the answer TextView and the SHOW ANSWER button to use the retrieved value.

Listing 5.12  Enabling cheating (CheatActivity.java)

public class CheatActivity extends AppCompatActivity {
    ...
    private boolean mAnswerIsTrue;

    private TextView mAnswerTextView;
    private Button mShowAnswerButton;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cheat);

        mAnswerIsTrue = getIntent().getBooleanExtra(EXTRA_ANSWER_IS_TRUE, false);

        mAnswerTextView = (TextView) findViewById(R.id.answer_text_view);

        mShowAnswerButton = (Button) findViewById(R.id.show_answer_button);
        mShowAnswerButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mAnswerIsTrue) {
                    mAnswerTextView.setText(R.string.true_button);
                } else {
                    mAnswerTextView.setText(R.string.false_button);
                }
            }
        });
    }
}

This code is pretty straightforward. You set the TextView’s text using TextView.setText(int). TextView.setText(…) has many variations, and here you use the one that accepts the resource ID of a string resource.

Run GeoQuiz. Press CHEAT! to get to CheatActivity. Then press SHOW ANSWER to reveal the answer to the current question.

Getting a result back from a child activity

At this point, the user can cheat with impunity. Let’s fix that by having the CheatActivity tell the QuizActivity whether the user chose to view the answer.

When you want to hear back from the child activity, you call the following Activity method:

    public void startActivityForResult(Intent intent, int requestCode)

The first parameter is the same intent as before. The second parameter is the request code. The request code is a user-defined integer that is sent to the child activity and then received back by the parent. It is used when an activity starts more than one type of child activity and needs to know who is reporting back. QuizActivity will only ever start one type of child activity, but using a constant for the request code is a best practice that will set you up well for future changes.

In QuizActivity, modify mCheatButton’s listener to call startActivityForResult(Intent, int).

Listing 5.13  Calling startActivityForResult(…) (QuizActivity.java)

public class QuizActivity extends AppCompatActivity {

    private static final String TAG = "QuizActivity";
    private static final String KEY_INDEX = "index";
    private static final int REQUEST_CODE_CHEAT = 0;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mCheatButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Start CheatActivity
                boolean answerIsTrue = mQuestionBank[mCurrentIndex].isAnswerTrue();
                Intent intent = CheatActivity.newIntent(QuizActivity.this,
                                                        answerIsTrue);
                startActivity(intent);
                startActivityForResult(intent, REQUEST_CODE_CHEAT);
            }
        });

Setting a result

There are two methods you can call in the child activity to send data back to the parent:

    public final void setResult(int resultCode)
    public final void setResult(int resultCode, Intent data)

Typically, the result code is one of two predefined constants: Activity.RESULT_OK or Activity.RESULT_CANCELED. (You can use another constant, RESULT_FIRST_USER, as an offset when defining your own result codes.)

Setting result codes is useful when the parent needs to take different action depending on how the child activity finished.

For example, if a child activity had an OK button and a Cancel button, the child activity would set a different result code depending on which button was pressed. Then the parent activity would take a different action depending on the result code.

Calling setResult(…) is not required of the child activity. If you do not need to distinguish between results or receive arbitrary data on an intent, then you can let the OS send a default result code. A result code is always returned to the parent if the child activity was started with startActivityForResult(…). If setResult(…) is not called, then when the user presses the Back button, the parent will receive Activity.RESULT_CANCELED.

Sending back an intent

In this implementation, you are interested in passing some specific data back to QuizActivity. So you are going to create an Intent, put an extra on it, and then call Activity.setResult(int, Intent) to get that data into QuizActivity’s hands.

In CheatActivity, add a constant for the extra’s key and a private method that does this work. Then call this method in the SHOW ANSWER button’s listener.

Listing 5.14  Setting a result (CheatActivity.java)

public class CheatActivity extends AppCompatActivity {

    private static final String EXTRA_ANSWER_IS_TRUE =
        "com.bignerdranch.android.geoquiz.answer_is_true";
    private static final String EXTRA_ANSWER_SHOWN =
        "com.bignerdranch.android.geoquiz.answer_shown";
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mShowAnswerButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mAnswerIsTrue) {
                    mAnswerTextView.setText(R.string.true_button);
                } else {
                    mAnswerTextView.setText(R.string.false_button);
                }
                setAnswerShownResult(true);
            }
        });
    }

    private void setAnswerShownResult(boolean isAnswerShown) {
        Intent data = new Intent();
        data.putExtra(EXTRA_ANSWER_SHOWN, isAnswerShown);
        setResult(RESULT_OK, data);
    }
}

When the user presses the SHOW ANSWER button, the CheatActivity packages up the result code and the intent in the call to setResult(int, Intent).

Then, when the user presses the Back button to return to the QuizActivity, the ActivityManager calls the following method on the parent activity:

    protected void onActivityResult(int requestCode, int resultCode, Intent data)

The parameters are the original request code from QuizActivity and the result code and intent passed into setResult(int, Intent).

Figure 5.11 shows this sequence of interactions.

Figure 5.11  Sequence diagram for GeoQuiz

Figure shows Sequence diagram for GeoQuiz.

The final step is to override onActivityResult(int, int, Intent) in QuizActivity to handle the result. However, because the contents of the result Intent are also an implementation detail of CheatActivity, add another method to help decode the extra into something QuizActivity can use.

Listing 5.15  Decoding the result intent (CheatActivity.java)

public static Intent newIntent(Context packageContext, boolean answerIsTrue) {
    Intent intent = new Intent(packageContext, CheatActivity.class);
    intent.putExtra(EXTRA_ANSWER_IS_TRUE, answerIsTrue);
    return intent;
}

public static boolean wasAnswerShown(Intent result) {
    return result.getBooleanExtra(EXTRA_ANSWER_SHOWN, false);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
}

Handling a result

In QuizActivity.java, add a new member variable to hold the value that CheatActivity is passing back. Then override onActivityResult(…) to retrieve it, checking the request code and result code to be sure they are what you expect. This, again, is a best practice to make future maintenance easier.

Listing 5.16  Implementing onActivityResult(…) (QuizActivity.java)

public class QuizActivity extends AppCompatActivity {
    ...
    private int mCurrentIndex = 0;
    private boolean mIsCheater;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode != Activity.RESULT_OK) {
            return;
        }

        if (requestCode == REQUEST_CODE_CHEAT) {
            if (data == null) {
                return;
            }
            mIsCheater = CheatActivity.wasAnswerShown(data);
        }
    }
    ...
}

Finally, modify the checkAnswer(boolean) method in QuizActivity to check whether the user cheated and to respond appropriately.

Listing 5.17  Changing toast message based on value of mIsCheater (QuizActivity.java)

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    mNextButton = (Button)findViewById(R.id.next_button);
    mNextButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;
            mIsCheater = false;
            updateQuestion();
        }
    });
    ...
}
...
private void checkAnswer(boolean userPressedTrue) {
    boolean answerIsTrue = mQuestionBank[mCurrentIndex].isAnswerTrue();

    int messageResId = 0;

    if (mIsCheater) {
        messageResId = R.string.judgment_toast;
    } else {
        if (userPressedTrue == answerIsTrue) {
            messageResId = R.string.correct_toast;
        } else {
            messageResId = R.string.incorrect_toast;
        }
    }

    Toast.makeText(this, messageResId, Toast.LENGTH_SHORT)
        .show();
}

Run GeoQuiz. Cheat and see what happens.

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

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