Отримання результату з Activity

В попередній темі було розглянуто, як викликати нову Activity і передавати їй деякі дані. Але ми можемо не тільки передавати дані запускаємому activity, а й очікувати від неї певного результату роботи.

Наприклад, нехай у нас в проекті буде дві activity: MainActivity і SecondActivity. А для кожної activity є свій файл інтерфейсу: activity_main.xml і activity_second.xml відповідно.

У минулій темі ми викликали нову activity за допомогою методу startActivity(). Для отримання ж результату роботи активності, що запускається, необхідно використовувати Activity Result API.

Activity Result API надає компоненти для реєстрації, запуску та обробки результату іншої Activity. Однією з переваг застосування Activity Result API є те, що він відв'язує результат Activity від самої Activity. Це дає змогу отримати й обробити результат, навіть якщо Activity, яка повертає результат, через обмеження пам'яті або з інших причин завершила свою роботу. Коротенько розглянемо основні моменти застосування Activity Result API.

Реєстрація функції для отримання результату

Для реєстрації функції, яка буде обробляти результат, Activity Result API надає метод registerForActivityResult(). Цей метод приймає як параметри об'єкти ActivityResultContract і ActivityResultCallback та повертає об'єкт ActivityResultLauncher, який використовується для запуску іншої activity.

ActivityResultLauncher<I> registerForActivityResult (
                ActivityResultContract<I, O> contract, 
                ActivityResultCallback<O> callback)

ActivityResultContract визначає контракт: які дані типу будуть подаватися на вхід і який тип буде представляти результат.

ActivityResultCallback є інтерфейсом з єдиним методом onActivityResult(), який визначає обробку отриманого результату. Коли друга activity завершить роботу і поверне результат, цей метод буде викликаний. Результат передається в метод як параметр. При цьому тип параметра повинен відповідати типу результату, визначеному в ActivityResultContract. Наприклад:

ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(
    new ActivityResultContracts.StartActivityForResult(),
    new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            // обробка result
        }
    });

Клас ActivityResultContracts надає ряд вбудованих типів контрактів. Наприклад, в наведеному коді використовується вбудований тип ActivityResultContracts.StartActivityForResult, який на вхід приймає об'єкт Intent, а тип результату — це тип ActivityResult.

Запуск activity для отримання результату

Метод registerForActivityResult() реєструє функцію-колбек і повертає об'єкт ActivityResultLauncher. За допомогою цього об'єкта ми можемо запустити activity. Для цього у об'єкта ActivityResultLauncher викликається метод launch():

mStartForResult.launch(intent);

В метод launch() передається об'єкт того типу, який визначений в об'єкті ActivityResultContracts як вхідний.

Практичне застосування Activity Result API

Отже, визначимо в файлі activity_main.xml наступний інтерфейс:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Укажіть вік"
        android:textSize="22sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
    
    <EditText
        android:id="@+id/age"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView"/>
    
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="Відправити"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/age"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Для введення даних тут визначено елемент EditText, а для відправки — кнопка.

Тепер визначимо в класі MainActivity запуск другої activity:

package com.example.viewapp;

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    static final String AGE_KEY = "AGE";
    static final String ACCESS_MESSAGE = "ACCESS_MESSAGE";

    ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback<ActivityResult>() {
                @Override
                public void onActivityResult(ActivityResult result) {

                    TextView textView = findViewById(R.id.textView);
                    if(result.getResultCode() == Activity.RESULT_OK){
                        Intent intent = result.getData();
                        String accessMessage = intent.getStringExtra(ACCESS_MESSAGE);
                        textView.setText(accessMessage);
                    }
                    else{
                        textView.setText("Помилка доступу");
                    }
                }
            });

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

    public void onClick(View view) {
        // отримуємо введений вік
        EditText ageBox = findViewById(R.id.age);
        String age = ageBox.getText().toString();

        Intent intent = new Intent(this, SecondActivity.class);
        intent.putExtra(AGE_KEY, age);

        mStartForResult.launch(intent);
    }
}

Коротко розглянемо основні моменти цього коду. По-перше, ми визначаємо об'єкт ActivityResultLauncher, за допомогою якого будемо запускати другу activity та передавати їй дані:

ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
        new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {

                TextView textView = findViewById(R.id.textView);
                if(result.getResultCode() == Activity.RESULT_OK){
                    Intent intent = result.getData();
                    String accessMessage = intent.getStringExtra(ACCESS_MESSAGE);
                    textView.setText(accessMessage);
                }
                else{
                    textView.setText("Помилка доступу");
                }
            }
        });

Об'єкт ActivityResultLauncher типізується типом Intent, оскільки об'єкт цього типу буде передаватися в метод launch() при запуску другої activity.

Тип контракту визначається типом ActivityResultContracts.StartActivityForResult, який і визначає тип Intent як вхідний тип і тип ActivityResult як тип результату.

Другий аргумент методу registerForActivityResult() — об'єкт ActivityResultCallback типізується типом результату — типом ActivityResult і визначає функцію-колбек onActivityResult(), яка отримує результат і обробляє його. У цьому випадку обробка полягає в тому, що ми виводимо в текстове поле відповідь від другої activity.

При обробці ми перевіряємо отриманий код результату:

if (result.getResultCode() == Activity.RESULT_OK)

Як результат, зазвичай використовуються вбудовані константи Activity.RESULT_OK і Activity.RESULT_CANCELED. Згідно з умовами, Activity.RESULT_OK означає, що activity успішно обробила запит, а Activity.RESULT_CANCELED — що activity відмовилася обробляти запит.

За допомогою методу getData() результату отримуємо передані з другої activity дані у вигляді об'єкта Intent:

Intent intent = result.getData();

Далі витягуємо з Intent рядок, яка має ключ ACCESS_MESSAGE, і виводимо її в текстове поле.

Таким чином, ми визначили об'єкт ActivityResultLauncher. Далі в обробнику натискання onClick за допомогою цього об'єкта запускаємо другу activity — SecondActivity:

public void onClick(View view) {
    // отримуємо введений вік
    EditText ageBox = findViewById(R.id.age);
    String age = ageBox.getText().toString();
 
    Intent intent = new Intent(this, SecondActivity.class);
    intent.putExtra(AGE_KEY, age);
 
    mStartForResult.launch(intent);
}

В обробнику натискання кнопки onClick() отримуємо введений в текстове поле вік, додаємо його в об'єкт Intent з ключем AGE_KEY і запускаємо SecondActivity за допомогою методу launch().

Тепер перейдемо до SecondActivity і визначимо в файлі activity_second.xml набір кнопок:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <TextView
        android:id="@+id/ageView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:textSize="26sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
 
    <Button
        android:id="@+id/button1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Відкрити доступ"
        android:onClick="onButton1Click"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/ageView"/>
 
    <Button
        android:id="@+id/button2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Відхилити доступ"
        android:onClick="onButton2Click"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button1"/>
 
    <Button
        android:id="@+id/button3"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Невірний вік"
        android:onClick="onButton3Click"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button2" />
 
    <Button
        android:id="@+id/cancel"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Скасувати"
        android:onClick="onCancelClick"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button3" />
 
</androidx.constraintlayout.widget.ConstraintLayout>

А в класі SecondActivity визначимо обробники для цих кнопок:

package com.example.viewapp;
 
import androidx.appcompat.app.AppCompatActivity;
 
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
 
public class SecondActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        Bundle extras = getIntent().getExtras();
        if (extras != null) {
            TextView ageView = findViewById(R.id.ageView);
            String age = extras.getString(MainActivity.AGE_KEY);
            ageView.setText("Вік: " +  age);
        }
    }
    public void onCancelClick(View v) {
        setResult(RESULT_CANCELED);
        finish();
    }
    public void onButton1Click(View v) {
        sendMessage("Доступ дозволено");
    }
    public void onButton2Click(View v) {
        sendMessage("Доступ заборонено");
    }
    public void onButton3Click(View v) {
        sendMessage("Невірний вік");
    }
    private void sendMessage(String message){
 
        Intent data = new Intent();
        data.putExtra(MainActivity.ACCESS_MESSAGE, message);
        setResult(RESULT_OK, data);
        finish();
    }
}

Три кнопки викликають метод sendMessage(), в який передають відправлене повідомлення. Це і буде те повідомлення, яке отримає MainActivity в методі onActivityResult.

Для повернення результату необхідно викликати метод setResult(), в який передаються два параметри:

  • числовий код результату
  • відправлені дані

Після виклику методу setResult() потрібно викликати метод finish, який знищить поточну activity.

Одна кнопка викликає обробник onCancelClick(), в якому передається в setResult тільки код результату — RESULT_CANCELED.

Отже, умовно кажучи, ми отримуємо в SecondActivity введений в MainActivity вік і за допомогою натискання певної кнопки повертаємо певний результат у вигляді повідомлення.

Залежно від натиснутої кнопки на SecondActivity ми будемо отримувати різні результати в MainActivity: