অ্যাক্টিভিটি থেকে ফলাফল পাওয়া

আপনার অ্যাপের মধ্যে বা অন্য অ্যাপ থেকে অন্য অ্যাক্টিভিটি শুরু করা, একমুখী অপারেশন হতে হবে না। এছাড়াও, আপনি কোনও অ্যাক্টিভিটি শুরু করতে এবং ফলাফল পেতে পারেন। যেমন, আপনার অ্যাপ ক্যামেরা অ্যাপ চালু করতে পারে এবং ফলাফল হিসেবে ক্যাপচার করা ফটো পেতে পারে। অথবা, ব্যবহারকারী যাতে কোনও পরিচিতি বেছে নিতে পারেন, তার জন্য আপনি Contacts অ্যাপ চালু করতে পারেন এবং তারপর ফলাফল হিসেবে পরিচিতির বিবরণ পেতে পারেন।

যদিও অন্তর্নিহিত startActivityForResult() এবং onActivityResult() API সব API লেভেলে Activity ক্লাসে উপলভ্য, Google AndroidX Activity এবং Fragment ক্লাসে চালু করা অ্যাক্টিভিটি ফলাফল API ব্যবহার করার জন্য জোরালোভাবে সাজেস্ট করে।

অ্যাক্টিভিটি ফলাফল API, ফলাফলের জন্য রেজিস্টার করা, ফলাফল তৈরি করে এমন অ্যাক্টিভিটি লঞ্চ করা এবং সিস্টেমের মাধ্যমে ফলাফল ডিসপ্যাচ করা হলে তা ম্যানেজ করার জন্য কম্পোনেন্ট প্রদান করে।

অ্যাক্টিভিটি ফলাফলের জন্য কলব্যাক রেজিস্টার করুন

কোনও ফলাফলের জন্য অ্যাক্টিভিটি শুরু করার সময়, এটি সম্ভব—এবং, ক্যামেরার ব্যবহারের মতো মেমরি-ইনটেনসিভ অপারেশনের ক্ষেত্রে, প্রায় নিশ্চিত—যে কম মেমরির কারণে আপনার প্রসেস এবং অ্যাক্টিভিটি নষ্ট হয়ে যাবে।

এই কারণে, অ্যাক্টিভিটি ফলাফল API আপনার কোডের সেই জায়গা থেকে ফলাফল কলব্যাক ডিকাপল করে যেখানে আপনি অন্য অ্যাক্টিভিটি লঞ্চ করেন। আপনার প্রসেস ও অ্যাক্টিভিটি আবার তৈরি করার সময় ফলাফল কলব্যাক উপলভ্য থাকতে হবে, তাই আপনার অ্যাক্টিভিটি তৈরি করার সময় প্রতিবার কলব্যাক অবশ্যই শর্তহীনভাবে রেজিস্টার করতে হবে, এমনকি অন্য অ্যাক্টিভিটি লঞ্চ করার লজিক শুধুমাত্র ব্যবহারকারীর ইনপুট বা অন্য বিজনেস লজিকের উপর ভিত্তি করে হলেও।

ComponentActivity বা Fragment-এ থাকলে, অ্যাক্টিভিটি ফলাফল API, ফলাফল কলব্যাক রেজিস্টার করার জন্য registerForActivityResult() API প্রদান করে। registerForActivityResult() একটি ActivityResultContract এবং একটি ActivityResultCallback নেয় এবং একটি ActivityResultLauncher ফেরত দেয়, যা আপনি অন্য অ্যাক্টিভিটি লঞ্চ করতে ব্যবহার করেন।

ActivityResultContract-এ ফলাফল তৈরি করার জন্য প্রয়োজনীয় ইনপুটের ধরন এবং ফলাফলের আউটপুটের ধরন উল্লেখ করা থাকে। API-গুলি ছবি তোলা, অনুমতির অনুরোধ করা ইত্যাদির মতো মৌলিক ইনটেন্ট অ্যাকশনের জন্য ডিফল্ট চুক্তি প্রদান করে। এছাড়াও, আপনি কাস্টম চুক্তি তৈরি করতে পারবেন।

ActivityResultCallback হল একটি সিঙ্গল মেথড ইন্টারফেস যার সাথে একটি onActivityResult() মেথড থাকে যা ActivityResultContract-এ সংজ্ঞায়িত আউটপুট টাইপের একটি অবজেক্ট নেয়:

Kotlin

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

জাভা

// 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
        }
});

আপনার একাধিক অ্যাক্টিভিটি ফলাফল কল থাকলে এবং আপনি আলাদা চুক্তি ব্যবহার করলে অথবা আলাদা কলব্যাক চাইলে, একাধিক ActivityResultLauncher ইনস্ট্যান্স রেজিস্টার করতে আপনি একাধিকবার registerForActivityResult() কল করতে পারেন। আপনার ফ্র্যাগমেন্ট বা অ্যাক্টিভিটি তৈরি করার সময় আপনাকে একই ক্রমে registerForActivityResult() কল করতে হবে যাতে ইনফ্লাইট ফলাফল সঠিক কলব্যাকে ডেলিভার করা হয়।

আপনার ফ্র্যাগমেন্ট বা অ্যাক্টিভিটি তৈরি করার আগে registerForActivityResult()-কে কল করা নিরাপদ, যাতে রিটার্ন করা ActivityResultLauncher ইনস্ট্যান্সের জন্য মেম্বার ভেরিয়েবল ঘোষণা করার সময় এটি সরাসরি ব্যবহার করা যায়।

ফলাফলের জন্য অ্যাক্টিভিটি লঞ্চ করুন

registerForActivityResult() আপনার কলব্যাক রেজিস্টার করার সময়, এটি অন্য অ্যাক্টিভিটি লঞ্চ করে না এবং ফলাফলের জন্য অনুরোধ শুরু করে। পরিবর্তে, এটি ফেরত আসা ActivityResultLauncher ইনস্ট্যান্সের দায়িত্ব।

ইনপুট থাকলে, লঞ্চার সেই ইনপুট নেয় যা ActivityResultContract-এর ধরনের সাথে মেলে। launch() কল করলে ফলাফল তৈরি করার প্রসেস শুরু হয়। ব্যবহারকারী পরবর্তী অ্যাক্টিভিটি সম্পূর্ণ করে ফিরে এলে, ActivityResultCallback থেকে onActivityResult() এক্সিকিউট করা হয়, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে:

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/*")
    }
}

জাভা

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 ক্লাস registerForActivityResult() API ব্যবহার করার জন্য ActivityResultCaller ইন্টারফেস প্রয়োগ করলেও, আপনি সরাসরি ActivityResultRegistry ব্যবহার করে ActivityResultCaller প্রয়োগ করে না এমন একটি পৃথক ক্লাসেও অ্যাক্টিভিটির ফলাফল পেতে পারেন।

যেমন, আপনি হয়ত এমন একটি 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()
        }
    }
}

জাভা

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();
            }
        });
    }
}

ActivityResultRegistry API ব্যবহার করার সময়, Google LifecycleOwner নেয় এমন API ব্যবহার করার জন্য জোরালোভাবে সাজেস্ট করে, কারণ Lifecycle নষ্ট হয়ে গেলে LifecycleOwner অটোমেটিক আপনার রেজিস্টার করা লঞ্চার সরিয়ে দেয়। তবে, যেসব ক্ষেত্রে LifecycleOwner উপলভ্য নয়, সেখানে প্রতিটি ActivityResultLauncher ক্লাস আপনাকে বিকল্প হিসেবে ম্যানুয়ালি unregister() কল করতে দেয়।

পরীক্ষা

ডিফল্ট হিসেবে, registerForActivityResult() অটোমেটিক অ্যাক্টিভিটির মাধ্যমে প্রদান করা ActivityResultRegistry ব্যবহার করে। এছাড়াও, এটি একটি ওভারলোড প্রদান করে যা আপনাকে ActivityResultRegistry-এর নিজস্ব ইনস্ট্যান্স পাস করতে দেয়। আপনি এটি ব্যবহার করে অন্য অ্যাক্টিভিটি চালু না করেই আপনার অ্যাক্টিভিটি ফলাফল কল পরীক্ষা করতে পারবেন।

আপনার অ্যাপের ফ্র্যাগমেন্ট পরীক্ষা করার সময়, আপনি ফ্র্যাগমেন্টের কনস্ট্রাক্টরে ActivityResultRegistry পাস করার জন্য FragmentFactory ব্যবহার করে একটি টেস্ট ActivityResultRegistry প্রদান করেন।

যেমন, ছবির থাম্বনেল পেতে TakePicturePreview কন্ট্র্যাক্ট ব্যবহার করে এমন ফ্র্যাগমেন্ট নিম্নলিখিত কোডের মতো লেখা হতে পারে:

Kotlin

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

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

    // ...
}

জাভা

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, এটি ফ্র্যাগমেন্টে পাস করে, লঞ্চার ট্রিগার করে সরাসরি বা Espresso-এর মতো অন্যান্য টেস্ট API ব্যবহার করে এবং তারপর ফলাফল যাচাই করে:

@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।

createIntent()-এ কল না করে, অন্য অ্যাক্টিভিটি শুরু না করে এবং ফলাফল তৈরি করতে parseResult() ব্যবহার না করে, প্রদত্ত ইনপুটের জন্য ফলাফল নির্ধারণ করা সম্ভব হলে, কন্ট্র্যাক্ট ঐচ্ছিকভাবে getSynchronousResult() প্রয়োগ করতে পারে।

নিম্নলিখিত উদাহরণ থেকে কীভাবে 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)
    }
}

জাভা

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))
    }
}

জাভা

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));
        }
    });
}