קבלת תוצאה מפעילות

התחלת פעילות אחרת, בפעילות בתוך האפליקציה או בפעילות אחרת יישום, לא חייבת להיות פעולה חד-כיוונית. אפשר גם להתחיל פעילות ולקבל תוצאה בחזרה. לדוגמה, האפליקציה שלך יכולה להפעיל אפליקציית מצלמה מקבלים את התמונה שצולמה. לחלופין, ניתן להפעיל את אפליקציית 'אנשי קשר' שהמשתמש יוכל לבחור איש קשר, ואז לקבל את איש הקשר כתוצאה מכך.

אומנם הערכים הבסיסיים startActivityForResult() וגם onActivityResult() ממשקי API זמינים במחלקה Activity בכל רמות ה-API, מומלץ להשתמש בממשקי ה-API של תוצאות הפעילות שנוספו ב-AndroidX Activity ו-Fragment כיתות.

ממשקי ה-API של תוצאות הפעילות מספקים רכיבים לרישום תוצאה, הפעלת הפעילות שיוצרת את התוצאה, וטיפול בתוצאה לאחר נשלחת על ידי המערכת.

רישום קריאה חוזרת (callback) לתוצאת פעילות

כשמתחילים פעילות לקבלת תוצאה מסוימת, זה אפשרי – וגם במקרים של הפעולות שדורשות צריכת זיכרון, כמו שימוש במצלמה, כמעט בטוחות — התהליך כולו והפעילות שלך תושמד בגלל מחסור בזיכרון.

לכן, ממשקי ה-API של תוצאות הפעילות מפרידים את התוצאה הקריאה החוזרת (callback) מהמקום שבו בקוד שלך הפעלת את הפעילות האחרת. כי הקריאה החוזרת של התוצאה צריכה להיות זמינה כשהתהליך והפעילות שלכם הקריאה החוזרת (callback) צריכה להירשם מחדש בכל פעם נוצרת פעילות, גם אם הלוגיקה של הפעלת הפעילות האחרת בלבד קורה על סמך קלט של משתמשים או לוגיקה עסקית אחרת.

במסגרת ComponentActivity או Fragment, תוצאת הפעילות ממשקי API מספקים registerForActivityResult() ממשק API לרישום הקריאה החוזרת של התוצאה. registerForActivityResult() לוקח ActivityResultContract וגם ActivityResultCallback ומחזירה ActivityResultLauncher, שבו אתם משתמשים כדי להפעיל פעילות אחרת.

ActivityResultContract מגדיר את סוג הקלט שנדרש כדי להפיק תוצאה יחד עם סוג הפלט של התוצאה. ממשקי ה-API מספקים חוזי ברירת מחדל לפעולות בסיסיות מתוך כוונות, כמו צילום תמונה, בקשת הרשאות וכן הלאה מופעלת. אפשר גם ליצור חוזה בהתאמה אישית.

ActivityResultCallback הוא ממשק method אחד עם onActivityResult() שלוקחת אובייקט מסוג הפלט שמוגדר ActivityResultContract:

Kotlin

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

Java

// GetContent creates an ActivityResultLauncher<String> to let you pass
// in the mime type you want to let the user select
ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(),
    new ActivityResultCallback<Uri>() {
        @Override
        public void onActivityResult(Uri uri) {
            // Handle the returned Uri
        }
});

אם יש לך מספר קריאות לתוצאות של פעילויות, וברצונך להשתמש חוזים או שרוצים להתקשר חזרה (callbacks) נפרדים, אפשר להפעיל את registerForActivityResult() כמה פעמים כדי לרשום כמה מופעים של ActivityResultLauncher. צריך קוראים לפונקציה registerForActivityResult() באותו סדר עבור כל יצירה של של חלק או פעילות כדי שתוצאות הבדיקה יישלחו אל הקריאה החוזרת (callback) הנכונה.

ניתן להפעיל את registerForActivityResult() לפני המקטע או הפעילות שלך נוצר, שמאפשר להשתמש בו באופן ישיר כשאתם מצהירים על משתני חבר למופעים של ActivityResultLauncher שהוחזרו.

התחלת פעילות לקבלת תוצאה

אמנם registerForActivityResult() רושם את הקריאה החוזרת, אבל לא להשיק את הפעילות הנוספת ולהתחיל את הבקשה לקבלת תוצאה. במקום זאת, הוא באחריות המכונה של ActivityResultLauncher שהוחזרה.

אם קיים קלט, מרכז האפליקציות מקבל את הקלט שתואם לסוג ActivityResultContract ביצוע שיחה launch() מתחיל את התהליך של הפקת התוצאה. כשהמשתמש מסיים לבצע פעילות לאחר מכן והחזרות, את onActivityResult() לאחר מכן מבצעים את הפקודה ActivityResultCallback, כפי שמוצג בדוגמה הבאה:

Kotlin

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    val selectButton = findViewById<Button>(R.id.select_button)

    selectButton.setOnClickListener {
        // Pass in the mime type you want to let the user select
        // as the input
        getContent.launch("image/*")
    }
}

Java

ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(),
    new ActivityResultCallback<Uri>() {
        @Override
        public void onActivityResult(Uri uri) {
            // Handle the returned Uri
        }
});

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    // ...

    Button selectButton = findViewById(R.id.select_button);

    selectButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            // Pass in the mime type you want to let the user select
            // as the input
            mGetContent.launch("image/*");
        }
    });
}

גרסה עמוסה של launch() מאפשרת להעביר ActivityOptionsCompat נוסף לקלט.

קבלת תוצאת פעילות בכיתה נפרדת

המחלקות ComponentActivity ו-Fragment מטמיעות את ActivityResultCaller כדי לאפשר לך להשתמש בממשקי ה-API של registerForActivityResult(), אפשר גם לקבל את תוצאת הפעילות בקטגוריה נפרדת שלא מיישם ActivityResultCaller באמצעות ActivityResultRegistry ישירות.

לדוגמה, ייתכן שתרצו להטמיע LifecycleObserver שמטפל ברישום חוזה יחד עם הפעלת מרכז האפליקציות:

Kotlin

class MyLifecycleObserver(private val registry : ActivityResultRegistry)
        : DefaultLifecycleObserver {
    lateinit var getContent : ActivityResultLauncher<String>

    override fun onCreate(owner: LifecycleOwner) {
        getContent = registry.register("key", owner, GetContent()) { uri ->
            // Handle the returned Uri
        }
    }

    fun selectImage() {
        getContent.launch("image/*")
    }
}

class MyFragment : Fragment() {
    lateinit var observer : MyLifecycleObserver

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        observer = MyLifecycleObserver(requireActivity().activityResultRegistry)
        lifecycle.addObserver(observer)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val selectButton = view.findViewById<Button>(R.id.select_button)

        selectButton.setOnClickListener {
            // Open the activity to select an image
            observer.selectImage()
        }
    }
}

Java

class MyLifecycleObserver implements DefaultLifecycleObserver {
    private final ActivityResultRegistry mRegistry;
    private ActivityResultLauncher<String> mGetContent;

    MyLifecycleObserver(@NonNull ActivityResultRegistry registry) {
        mRegistry = registry;
    }

    public void onCreate(@NonNull LifecycleOwner owner) {
        // ...

        mGetContent = mRegistry.register(“key”, owner, new GetContent(),
            new ActivityResultCallback<Uri>() {
                @Override
                public void onActivityResult(Uri uri) {
                    // Handle the returned Uri
                }
            });
    }

    public void selectImage() {
        // Open the activity to select an image
        mGetContent.launch("image/*");
    }
}

class MyFragment extends Fragment {
    private MyLifecycleObserver mObserver;

    @Override
    void onCreate(Bundle savedInstanceState) {
        // ...

        mObserver = new MyLifecycleObserver(requireActivity().getActivityResultRegistry());
        getLifecycle().addObserver(mObserver);
    }

    @Override
    void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        Button selectButton = findViewById(R.id.select_button);
        selectButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                mObserver.selectImage();
            }
        });
    }
}

כשמשתמשים בממשקי ה-API של ActivityResultRegistry, Google ממליצה מאוד להשתמש את ממשקי ה-API שלוקחים LifecycleOwner, בתור LifecycleOwner באופן אוטומטי מסירה את מרכז האפליקציות הרשום כש-Lifecycle מושמד. אבל, לפעמים במקרים שבהם LifecycleOwner לא זמין, כל אחד בכיתה ActivityResultLauncher אפשר להתקשר ידנית unregister() כחלופה.

בדיקה

כברירת מחדל, registerForActivityResult() משתמש באופן אוטומטי ActivityResultRegistry שסופקה על ידי הפעילות. הוא גם מספק עומס יתר שמאפשר במופע שלכם של ActivityResultRegistry, שבו תוכלו להשתמש כדי לבדוק קריאות לתוצאות פעילות מבלי להפעיל פעילות נוספת בפועל.

בזמן בדיקת מקטעי האפליקציה, ייתן בדיקת ActivityResultRegistry באמצעות FragmentFactory כדי לעבור ב-ActivityResultRegistry ל-constructor של המקטע.

לדוגמה, מקטע שמשתמש בחוזה TakePicturePreview כדי לקבל תמונה ממוזערת של התמונה עשויה להיות כתובה כך:

Kotlin

class MyFragment(
    private val registry: ActivityResultRegistry
) : Fragment() {
    val thumbnailLiveData = MutableLiveData<Bitmap?>

    val takePicture = registerForActivityResult(TakePicturePreview(), registry) {
        bitmap: Bitmap? -> thumbnailLiveData.setValue(bitmap)
    }

    // ...
}

Java

public class MyFragment extends Fragment {
    private final ActivityResultRegistry mRegistry;
    private final MutableLiveData<Bitmap> mThumbnailLiveData = new MutableLiveData();
    private final ActivityResultLauncher<Void> mTakePicture =
        registerForActivityResult(new TakePicturePreview(), mRegistry, new ActivityResultCallback<Bitmap>() {
            @Override
            public void onActivityResult(Bitmap thumbnail) {
                mThumbnailLiveData.setValue(thumbnail);
            }
        });

    public MyFragment(@NonNull ActivityResultRegistry registry) {
        super();
        mRegistry = registry;
    }

    @VisibleForTesting
    @NonNull
    ActivityResultLauncher<Void> getTakePicture() {
        return mTakePicture;
    }

    @VisibleForTesting
    @NonNull
    LiveData<Bitmap> getThumbnailLiveData() {
        return mThumbnailLiveData;
    }

    // ...
}

כשיוצרים ActivityResultRegistry ספציפי לבדיקה, צריך להטמיע ה onLaunch() . במקום להתקשר אל startActivityForResult(), יכול לקרוא dispatchResult() ולספק את התוצאות המדויקות שברצונך להשתמש בהן בבדיקה:

val testRegistry = object : ActivityResultRegistry() {
    override fun <I, O> onLaunch(
            requestCode: Int,
            contract: ActivityResultContract<I, O>,
            input: I,
            options: ActivityOptionsCompat?
    ) {
        dispatchResult(requestCode, expectedResult)
    }
}

הבדיקה המלאה יוצרת את התוצאה הצפויה, ובונה בדיקה ActivityResultRegistry, מעביר אותו למקטע, מפעיל את מרכז האפליקציות באופן ישיר או באמצעות ממשקי API אחרים לבדיקה כגון Espresso, ולאחר מכן מאמת התוצאות:

@Test
fun activityResultTest {
    // Create an expected result Bitmap
    val expectedResult = Bitmap.createBitmap(1, 1, Bitmap.Config.RGBA_F16)

    // Create the test ActivityResultRegistry
    val testRegistry = object : ActivityResultRegistry() {
            override fun <I, O> onLaunch(
            requestCode: Int,
            contract: ActivityResultContract<I, O>,
            input: I,
            options: ActivityOptionsCompat?
        ) {
            dispatchResult(requestCode, expectedResult)
        }
    }

    // Use the launchFragmentInContainer method that takes a
    // lambda to construct the Fragment with the testRegistry
    with(launchFragmentInContainer { MyFragment(testRegistry) }) {
            onFragment { fragment ->
                // Trigger the ActivityResultLauncher
                fragment.takePicture()
                // Verify the result is set
                assertThat(fragment.thumbnailLiveData.value)
                        .isSameInstanceAs(expectedResult)
            }
    }
}

יצירת חוזה בהתאמה אישית

בזמן ActivityResultContracts מכיל מספר מחלקות ActivityResultContract מוכנות מראש לשימוש, אפשר לספק חוזים משלכם שמספקים את ממשק ה-API המדויק, הבטוח מהסוג הדרוש לכם.

כל ActivityResultContract דורש סיווגי קלט ופלט מוגדרים, נשתמש ב-Void כסוג הקלט, לא דורשים קלט (ב-Kotlin צריך להשתמש ב-Void? או ב-Unit).

בכל חוזה יש להטמיע createIntent() שלוקחת את Context ואת הקלט ובונה את ה-Intent בשימוש עם startActivityForResult().

בכל חוזה צריך ליישם גם את parseResult(), שמפיק את הפלט מ-resultCode הנתון, כמו Activity.RESULT_OK או Activity.RESULT_CANCELED, וגם Intent.

ניתן ליישם חוזים באופן אופציונלי getSynchronousResult() אם ניתן לקבוע את התוצאה של קלט נתון בלי צריך לקרוא ל-createIntent(), להתחיל את הפעילות האחרת ולהשתמש parseResult() כדי ליצור את התוצאה.

הדוגמה הבאה מראה איך ליצור ActivityResultContract:

Kotlin

class PickRingtone : ActivityResultContract<Int, Uri?>() {
    override fun createIntent(context: Context, ringtoneType: Int) =
        Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
            putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType)
        }

    override fun parseResult(resultCode: Int, result: Intent?) : Uri? {
        if (resultCode != Activity.RESULT_OK) {
            return null
        }
        return result?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
    }
}

Java

public class PickRingtone extends ActivityResultContract<Integer, Uri> {
    @NonNull
    @Override
    public Intent createIntent(@NonNull Context context, @NonNull Integer ringtoneType) {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType.intValue());
        return intent;
    }

    @Override
    public Uri parseResult(int resultCode, @Nullable Intent result) {
        if (resultCode != Activity.RESULT_OK || result == null) {
            return null;
        }
        return result.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
    }
}

אם אתם לא צריכים חוזה מותאם אישית, תוכלו להשתמש StartActivityForResult חוזה. זהו חוזה כללי שמקבל כל Intent כקלט, מחזירה ActivityResult, וכך לחלץ את resultCode ו-Intent כחלק מהקריאה החוזרת, כפי שאפשר לראות בדוגמה הבאה:

Kotlin

val startForResult = registerForActivityResult(StartActivityForResult()) { result: ActivityResult ->
    if (result.resultCode == Activity.RESULT_OK) {
        val intent = result.data
        // Handle the Intent
    }
}

override fun onCreate(savedInstanceState: Bundle) {
    // ...

    val startButton = findViewById(R.id.start_button)

    startButton.setOnClickListener {
        // Use the Kotlin extension in activity-ktx
        // passing it the Intent you want to start
        startForResult.launch(Intent(this, ResultProducingActivity::class.java))
    }
}

Java

ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(new StartActivityForResult(),
        new ActivityResultCallback<ActivityResult>() {
    @Override
    public void onActivityResult(ActivityResult result) {
        if (result.getResultCode() == Activity.RESULT_OK) {
            Intent intent = result.getData();
            // Handle the Intent
        }
    }
});

@Override
public void onCreate(@Nullable savedInstanceState: Bundle) {
    // ...

    Button startButton = findViewById(R.id.start_button);

    startButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            // The launcher with the Intent you want to start
            mStartForResult.launch(new Intent(this, ResultProducingActivity.class));
        }
    });
}