OnActivityResult method is deprecated, what is the alternative?
Asked Answered
M

30

551

I recently discovered that onActivityResult is deprecated. What should we do to handle it?

Any alternative introduced for that?

Image showing code with onActivityResult striked out, indicating deprecation

Motorway answered 1/7, 2020 at 6:46 Comment(10)
if I remove it an error lint appeared to add super call!Motorway
I don't know if there's ever been a deprecation that was un-deprecated but I'm holding out hope for startActivityForResult. This new way overly complicates the code and reduces readability.Aden
It's hard to test the thing now :(Copy
I can see why Google decided to go with this route, it's trying to decouple startActivityForResult from the view lifecycle. I just wished that there's a more elegant way of doing this.Desperation
The docs don't show it as deprecated.Greenock
Please check this official documentation. Hope this helps you. developer.android.com/training/basics/intents/resultJordain
startActivityForResult also seems deprecated. Is it?Deice
If anyone looking for Xamarin android solution : appliedcodelog.com/2022/02/…Legible
Anyone know at what api level this function was deprecated ?Rooke
@Greenock They do. developer.android.com/reference/androidx/fragment/app/…Prevalent
M
783

A basic training is available at developer.android.com.

Here is an example on how to convert the existing code with the new one:

The old way:

public void openSomeActivityForResult() {
    Intent intent = new Intent(this, SomeActivity.class);
    startActivityForResult(intent, 123);
}

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

The new way (Java):

public void openSomeActivityForResult() {
    Intent intent = new Intent(this, SomeActivity.class);
    someActivityResultLauncher.launch(intent);
}

// You can do the assignment inside onAttach or onCreate, i.e, before the activity is displayed
ActivityResultLauncher<Intent> someActivityResultLauncher = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
                if (result.getResultCode() == Activity.RESULT_OK) {
                    // There are no request codes
                    Intent data = result.getData();
                    doSomeOperations();
                }
            }
        });

The new way (Kotlin):

fun openSomeActivityForResult() {
    val intent = Intent(this, SomeActivity::class.java)
    resultLauncher.launch(intent)
}

var resultLauncher = registerForActivityResult(StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        // There are no request codes
        val data: Intent? = result.data
        doSomeOperations()
    }
}

EDIT. A better approach would be to make it more generalised so that we can reuse it. The snippet below is used in one of my projects but beware that it's not well-tested and may not cover all the cases.

BetterActivityResult.java

import android.content.Intent;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class BetterActivityResult<Input, Result> {
    /**
     * Register activity result using a {@link ActivityResultContract} and an in-place activity result callback like
     * the default approach. You can still customise callback using {@link #launch(Object, OnActivityResult)}.
     */
    @NonNull
    public static <Input, Result> BetterActivityResult<Input, Result> registerForActivityResult(
            @NonNull ActivityResultCaller caller,
            @NonNull ActivityResultContract<Input, Result> contract,
            @Nullable OnActivityResult<Result> onActivityResult) {
        return new BetterActivityResult<>(caller, contract, onActivityResult);
    }

    /**
     * Same as {@link #registerForActivityResult(ActivityResultCaller, ActivityResultContract, OnActivityResult)} except
     * the last argument is set to {@code null}.
     */
    @NonNull
    public static <Input, Result> BetterActivityResult<Input, Result> registerForActivityResult(
            @NonNull ActivityResultCaller caller,
            @NonNull ActivityResultContract<Input, Result> contract) {
        return registerForActivityResult(caller, contract, null);
    }

    /**
     * Specialised method for launching new activities.
     */
    @NonNull
    public static BetterActivityResult<Intent, ActivityResult> registerActivityForResult(
            @NonNull ActivityResultCaller caller) {
        return registerForActivityResult(caller, new ActivityResultContracts.StartActivityForResult());
    }

    /**
     * Callback interface
     */
    public interface OnActivityResult<O> {
        /**
         * Called after receiving a result from the target activity
         */
        void onActivityResult(O result);
    }

    private final ActivityResultLauncher<Input> launcher;
    @Nullable
    private OnActivityResult<Result> onActivityResult;

    private BetterActivityResult(@NonNull ActivityResultCaller caller,
                                 @NonNull ActivityResultContract<Input, Result> contract,
                                 @Nullable OnActivityResult<Result> onActivityResult) {
        this.onActivityResult = onActivityResult;
        this.launcher = caller.registerForActivityResult(contract, this::callOnActivityResult);
    }

    public void setOnActivityResult(@Nullable OnActivityResult<Result> onActivityResult) {
        this.onActivityResult = onActivityResult;
    }

    /**
     * Launch activity, same as {@link ActivityResultLauncher#launch(Object)} except that it allows a callback
     * executed after receiving a result from the target activity.
     */
    public void launch(Input input, @Nullable OnActivityResult<Result> onActivityResult) {
        if (onActivityResult != null) {
            this.onActivityResult = onActivityResult;
        }
        launcher.launch(input);
    }

    /**
     * Same as {@link #launch(Object, OnActivityResult)} with last parameter set to {@code null}.
     */
    public void launch(Input input) {
        launch(input, this.onActivityResult);
    }

    private void callOnActivityResult(Result result) {
        if (onActivityResult != null) onActivityResult.onActivityResult(result);
    }
}

With the above approach, you still have to register it before or during launching the activity or fragment attachment. Once defined, it can be reused within the activity or fragment. For example, if you need to start new activities in most of the activity, you can define a BaseActivity and register a new BetterActivityResult like this:

BaseActivity.java

public class BaseActivity extends AppCompatActivity {
    protected final BetterActivityResult<Intent, ActivityResult> activityLauncher = BetterActivityResult.registerActivityForResult(this);
}

After that, you can simply launch an activity from any child activities like this:

public void openSomeActivityForResult() {
    Intent intent = new Intent(this, SomeActivity.class);
    activityLauncher.launch(intent, result -> {
        if (result.getResultCode() == Activity.RESULT_OK) {
            // There are no request codes
            Intent data = result.getData();
            doSomeOperations();
        }
    })
}

Since you can set the callback function along with the Intent, you can reuse it for any activities.

Similarly, you can also use other activity contracts using the other two constructors.

Maltzman answered 30/8, 2020 at 4:46 Comment(42)
The new way looks unnecessarily complicated compared to the old way...Adenoidal
@Adenoidal it removes the hassle of maintaining request codes and adds the ability to run tests. But I think its main problem is reusability. In my opinion ActivityResultCallback should be a part of ActivityResultLauncher#launch() so that we can reuse it for other purposes, eg., if I'd needed to check storage permissions for multiple actions, I could've use a single ActivityResultContracts.RequestPermission() for all of them. You could implement ActivityResultCallback in an attempt to solve this but that will be worse than the old way since you need something similar to request codes.Maltzman
If my activity is starting 2 new activties for result then how would I differentiate it on onActivityResult?Tombolo
The only convenience that I see is removing result codes. We had to add different constants for every activity. Sometimes we could mix and two result codes became equal. That could lead to different bugs.Blepharitis
@drmrbrewer, agree. They made an unnecessaty tool that broke usual multi-activity application (especially MVC). Now we have to create many callbacks instead of one onActivityResult. One activity contains 10 fragments, some of them can start other activities and return result. Before we simply returned setResult() in fragment and handled in onActivityResult. Now we have to create many callbacks.Blepharitis
The new way is way worse than the old. It breaks code modularity and forces you to use much more codelines to cover use cases of previous version. Deprecation should be used when you provide better API design, but at Google are deprecated stuff arbitrarely on the base of opinable decisions not technically justified that look based on few use cases.Merideth
@Merideth Yeah, I was absolutely shocked when Google deprecated ViewPager in favour of ViewPager2 which created more problems than solving anything.Maltzman
@MuntashirAkon The list is very long, not talking about the SAF-maggedon, and not only on Android platform, and without any regard of backward compatibility of API interfaces, to the point that now I avoid to use any Google backed framework when possible, to avoid to face future hassles and waste of time in codebase migrations.Merideth
with help of lambda Java version can be reduced to KotlinPolychrome
What if activity is started by a library?Arnelle
@YaMiN The library has to provide some sort of initialiser that must be called before or during activity creation or fragment attachment.Maltzman
Was the dramatic change really necessary?! Google keeps changing things like some kind of baby diapers!Amal
@ianhanniballake Correct, the results may not be delivered correctly due to lifecycle issues. However, since BetterActivityResult also needs to be initialised during or before fragment/activity creation, there might be a way to handle them internally within the class.Maltzman
I'm surprised this API isn't called startActivityForResult2. If you think working with result codes was tedious, just wait till you get a load of this hot mess.Montano
I found this ZXing example of the new way github.com/journeyapps/zxing-android-embedded/issues/628Hull
some times result code getting 0 instead -1Genova
@Adenoidal : what happens if we keep using the old way, it seems to be working still and not getting caught in code inspection : code maturityPilgarlic
Wow Google!, Thanks for this new complex way! the code was much easier before. improved result code against re-usability...Subtraction
Leaking 'this' in constructor of non-final class BaseActivitySelectivity
JAVA users: It's more complicated. KOTLIN users: It's easier.Frig
@ShubhamGupta It would be almost the same if you set 1.8 compatibility (lamda functions) in Java.Maltzman
So instead of request code now you will have name of the function to call.Pilgarlic
Trying to figure out how to migrate this deprecated code to the new method in Kotlin. However, I am still not understanding how you migrate the requestCode situation, when there is no replacement for it or at least I see no clear one. @ShubhamGupta I am using Kotlin. Care to explain how to replace dependence on requestCode?Coprolalia
A more handy piece of code as a generalized solution: github.com/pseusys/contribution/blob/main/kotlin/android/app/…Anticlinal
There is no request code. How do I know where the result is coming from?Keloid
@Keloid There is no concept of request code in the new design. It is expected that for each separate Intent, you will register a separate callback. This way, the activity/fragment knows where the result has to be sent. You can create alternatives like the one I've outlined above but as others have said, this new approach is totally useless and does not attempt to solve the actual issues.Maltzman
@MilTy This, like BetterActivityResult, would fall into the same trap if the device configurations have changed while the activity is running. A solution is to reintroduce request codes but only internally. (I will update my post to fix the issue later on.)Maltzman
@MuntashirAkon what kind of device configurations do you mean?Anticlinal
@MilTy Changing screen size, orientation, etc.Maltzman
Change the existing working component which was super easy and working as expected and put it in the training/basic section, thanks google!Constellate
How i pass request code ?Althorn
I have an issue: I have two activities: from the first one starts the second one by calling this BetterActivityResult functionality. Everythings going well until I rotate the device screen after second activity was launched. In this case I get result.getResultCode() = RESULT_CANCELLED - ALWAYS!!! And ofcourse doSomeOperations(); is nevere run. How I can resolve this issue?Louisiana
Xamarin android solution appliedcodelog.com/2022/02/…Legible
This remindes me the camera2 api :)Wrac
Does anyone understand what is the benefit of the new API in terms of robustness? My tests show that when there's a configuration change after startActivityForResult, onActivityResult is correctly called on the new Activity instance (not the destroyed one).Blip
Benefits: 1: You can now launch activities for result from fragments and receive the results in fragments without going through the activity. 2: Instead of distinguishing requests with fallible INT codes you now have strongly typed classes. 3: You can have separate callbacks for separate requests, eliminating the need for switch-case in response handling, There are probably more benefits. People should do some research before blindly upvoting "this looks more complicated" when it truly isn't (unless you're stuck in Java)Baccivorous
I used your "The new way (Java)" to pick a contact from the agenda and it works very well. ThanksPinchas
'Who' even gets to decided that this is now the 'new way' to do it?Andress
Anyone know at what api level this function was deprecated ?Rooke
@RonTLV: This is shipped with the AndroidX libraries, not the Android SDKs.Maltzman
typical Google way of making perfectly well way of doing a thing unnecessarily complicated. I thought it was just me but good to know that others also feel the same.Cob
Your code does not work in the same Activity, because it causes an infinite loop...Gallardo
L
56

From now, startActivityForResult() has been deprecated so use new method instead of that.

Kotlin Example

    fun openActivityForResult() {
        startForResult.launch(Intent(this, AnotherActivity::class.java))
    }


    val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { 
    result: ActivityResult ->
        if (result.resultCode == Activity.RESULT_OK) {
            val intent = result.data
            // Handle the Intent
            //do stuff here
        }
    }
Linnell answered 1/10, 2020 at 13:31 Comment(0)
Y
42

There are 4 simple steps to follow while replacing the deprecated method startActivityForResult(...).

  1. In place of overridden method onActivityResult(..) -

     ActivityResultLauncher<Intent> activityResultLaunch = registerForActivityResult(
             new ActivityResultContracts.StartActivityForResult(),
             new ActivityResultCallback<ActivityResult>() {
                 @Override
                 public void onActivityResult(ActivityResult result) {
                     if (result.getResultCode() == 123) {
                         // ToDo : Do your stuff...
                     } else if(result.getResultCode() == 321) {
                         // ToDo : Do your stuff...
                     }
                 }
    });
    

For multiple custom requests, append the condition as

if (result.getResultCode() == 123) {
..
} else if(result.getResultCode() == 131){
..
} // so on..
  1. Imports :

     import androidx.activity.result.ActivityResult;
     import androidx.activity.result.ActivityResultCallback;
     import androidx.activity.result.ActivityResultLauncher;
     import androidx.activity.result.contract.ActivityResultContracts;
    
  2. In place of startActivityForResult(intent, 123), use

     Intent intent = new Intent(this, SampleActivity.class);
     activityResultLaunch.launch(intent);
    
  3. In SampleActivity.java class, while returning back to source activity, code will remain the same like -

    Intent intent = new Intent();
    setResult(123, intent);
    finish();
    

Happy Coding! :)

Yepez answered 27/7, 2021 at 6:54 Comment(1)
Thank you so much , your guide is quiet detailedTanah
S
23

In Java 8 it can be written alike this:

ActivityResultLauncher<Intent> startActivityForResult = registerForActivityResult(
    new ActivityResultContracts.StartActivityForResult(),
    result -> {
        if (result.getResultCode() == AppCompatActivity.RESULT_OK) {
            Intent data = result.getData();
            // ...
        }
    }
);

Intent intent = new Intent( ... );
startActivityForResult.launch(intent);
Slone answered 21/2, 2021 at 21:13 Comment(2)
How can I handle the multiple requestCode? Please help. Thanks in advance.Mcpeak
I like this approach!Pinchas
D
21

In KOTLIN I changed my code

startActivityForResult(intent, Constants.MY_CODE_REQUEST)

and

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    super.onActivityResult(requestCode, resultCode, data)
    if (resultCode == Activity.RESULT_OK) {
        when (requestCode) {
            Constants.MY_CODE_REQUEST -> {
            ...
}

to

registerForActivityResult(StartActivityForResult()) { result ->
    onActivityResult(Constants.MY_CODE_REQUEST, result)
}.launch(intent)

and

private fun onActivityResult(requestCode: Int, result: ActivityResult) {
    if(result.resultCode == Activity.RESULT_OK) {
        val intent = result.data
        when (requestCode) {
            Constants.MY_CODE_REQUEST -> {
            ...

I hope it works for you. :D

Dyson answered 14/10, 2020 at 17:58 Comment(4)
onActivityResult from your third code snippet on registerForActivityResult is deprecated.Yawmeter
@FilipeBrito onActivityResult is not an overwritten method, it is my own method, the name can be whatever ;)Dyson
the requestCode in the new way seems practically useless.Poolroom
This is not the right approach . If you set the launch together with the registerForActivityResult we might run into initialization error. It's always better to create a variable first and do the registration there.Wellfavored
C
10

For those whose fragments have more than one requestCode, and if you are not sure how to handle multiple results by those requestCodes, you need to understand that requestCode is useless in the new approach.

I imagine the old way you code like this:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (resultCode == Activity.RESULT_CODE) {
        when (requestCode) {
            REQUEST_TAKE_PHOTO -> {
                // handle photo from camera
            }
            REQUEST_PICK_IMAGE_FROM_GALLERY -> {
                // handle image from gallery
            }
        }
    }
}

In the new API, you need to implement the result of every requests in a separate ActivityResultContract:

val takePhotoForResult = registerForActivityResult(StartActivityForResult()) { result: ActivityResult ->
    if (result.resultCode == Activity.RESULT_OK) {
        val intent = result.data
        // handle photo from camera
    }
}

val pickImageFromGalleryForResult = registerForActivityResult(StartActivityForResult()) { result: ActivityResult ->
    if (result.resultCode == Activity.RESULT_OK) {
        val intent = result.data
        // handle image from gallery
    }
}

Then you need to start those activities/intents like this:

private fun startTakePhotoActivity() {
    takePhotoForResult.launch(Intent(requireActivity(), TakePhotoActivity::class.java))
}

private fun pickImageFromGallery() {
    val pickIntent = Intent(Intent.ACTION_PICK)
    pickIntent.setDataAndType(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        "image/*"
    )
    pickImageFromGalleryForResult.launch(pickIntent)
}

By doing this, you can get rid of hundreds of const val REQUEST_ values in your project.

Catlett answered 14/4, 2022 at 11:45 Comment(2)
You get rid of hundreds of const val REQUEST_ values in your project by replacing them by hundreds of ActivityResultContracts. Where's the advantage?Prevalent
Managing the values of these REQUEST_ integers takes time, and integers can be used for different purposes, messing up the project. However ActivityResultContracts are used only one purpose guiding the developer to the right point. That makes the difference. @TheincredibleJanCyclopropane
F
9

onActivityResult, startActivityForResult, requestPermissions, and onRequestPermissionsResult are deprecated on androidx.fragment from 1.3.0-alpha04, not on android.app.Activity.
Instead, you can use Activity Result APIs with registerForActivityResult.

Fillander answered 17/8, 2020 at 3:11 Comment(2)
where do you get to know about this " Activity Result APIs" with "registerForActivityResult" whats the source ?Proffer
@Proffer As I linked change log on text deprecated, Android team said 'Please use the Activity Result APIs'Fillander
A
7

Reference : Kotlin - Choose Image from gallery

The Simplest Alernative I've found so far

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.id.activity_main)

    var ivPhoto = findViewById<ImageView>(R.id.ivPhoto)
    var btnChoosePhoto = findViewById<Button>(R.id.btnChoosePhoto)

    

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


    btnChoose.setOnClickListener {
        getContent.launch("image/*")
    }
    
    }
Addams answered 30/5, 2021 at 21:28 Comment(1)
Why does your link point to "Launch an activity for result" if you label it "Kotlin - Choose Image from gallery"?Prevalent
V
7

Here i explain the new way

private val scan =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult())
        { result: ActivityResult ->
            if (result.resultCode == AppCompatActivity.RESULT_OK && result.data != null) {

                var selected_hub = result!!.data!!.getParcelableExtra<ExtendedBluetoothDevice>(Utils.EXTRA_DEVICE)
                Log.d(TAG,"RECONNECT PROCESS "+selected_hub!!.name)
                reconnect(selected_hub!!)

            }
        }

call this from activity or fragment

private fun callScan() {
        val intent = Intent(requireActivity(), ScanningMeshDevices::class.java)
        scan.launch(intent)
    }
Volans answered 23/8, 2022 at 9:59 Comment(0)
T
6

The below code works in the Kotlin fragment to check the Bluetooth permission.

val intent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)

registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        // There are no request codes
        val data: Intent? = result.data
        bluetoothAdapter.enable()
        Toast.makeText(context, "Permission Granted: ", Toast.LENGTH_SHORT).show()
        dynamicButton()
    }
    else{Toast.makeText(context, "You have to enable bluetooth to use this app.", Toast.LENGTH_SHORT).show()}
    
}.launch(intent)
Tamberg answered 14/12, 2022 at 8:29 Comment(0)
C
4

Here's my solution:

In our project, we had 20+ occurrences of startActivityForResult (and onActivityResult).

We wanted to change the code as little as possible (and keep using request codes), while introducing an elegant solution for future use.

Since lots of us, developers, use BaseActivity concept - why not take advantage of it?

Here is BaseActivity:

abstract class BaseActivity : AppCompatActivity()
{
    private var requestCode: Int = -1
    private var resultHandler: ActivityResultLauncher<Intent>? = null

    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        registerForActivityResult()
    }

    private fun registerForActivityResult()
    {
        if (shouldRegisterForActivityResult())
        {
            resultHandler = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->

                onActivityResult(result.data, requestCode, result.resultCode)
                this.requestCode = -1
            }
        }
    }

   fun startActivityForResult(requestCode: Int, intent: Intent)
   {
       this.requestCode = requestCode
       resultHandler?.launch(intent)
   }

   protected open fun onActivityResult(data: Intent?, requestCode: Int, resultCode: Int)
   {
       // For sub activities
   }

   protected open fun shouldRegisterForActivityResult(): Boolean
   {
      // Sub activities that need the onActivityResult "mechanism", should override this and return true
       return false
   }
}

Here is SubActivity:

class SubActivity : BaseActivity()
{
    companion object
    {
        private const val SOME_REQUEST_CODE = 300
    }

    private fun testActivityResult()
    {
        val intent = Intent(this, OtherActivity::class.java)
        startActivityForResult(SOME_REQUEST_CODE, intent)
    }

    override fun shouldRegisterForActivityResult(): Boolean
    {
        return true
    }

    override fun onActivityResult(data: Intent?, requestCode: Int, resultCode: Int)
    {
        if (requestCode == SOME_REQUEST_CODE)
        {
            // Yes!
        }
    }
}

Hope it helps someone

Cartage answered 1/3, 2021 at 15:27 Comment(0)
R
4

I figured how to do it properly from a Fragment in Kotlin, to capture an image and handle returned bitmap. It is pretty much the same in other cases too.

First, you have to register the fragment to listen for the activity results. This has to be done before initiating the fragment, which means creating a member variable instead of initiating within onCreate function.

class DummyFragment : Fragment() {

  //registering fragment for camera listener
  private val takePhoto = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
  ) {
    if (it.resultCode == Activity.RESULT_OK) {
      val imageBitmap = it.data?.extras?.get("data") as Bitmap
      // do your thing with the obtained bitmap
    }
  }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
  }

}

Then, call the camera intent as you would normally do. And use this above-created variable to launch the intent.

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  someRandomButton.setOnClickListener {
    val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
    takePhoto.launch(takePictureIntent)
  }
}
Roomy answered 26/12, 2021 at 11:53 Comment(0)
T
3

My goal was to reuse the current implementation of the startActivityForResult method with minimum code changes. For that purpose, I made a wrapper class and interface with an onActivityResultFromLauncher method.

interface ActivityResultLauncherWrapper {

    fun launchIntentForResult(activity: FragmentActivity, intent: Intent, requestCode: Int, callBack: OnActivityResultListener)

    fun unregister()

    interface OnActivityResultListener {
        fun onActivityResultFromLauncher(requestCode: Int, resultCode: Int, data: Intent?)
    }
}

class ActivityResultLauncherWrapperImpl : ActivityResultLauncherWrapper {
    private var weakLauncher: WeakReference<ActivityResultLauncher<Intent>>? = null

    override fun launchIntentForResult(
            activity: FragmentActivity,
            intent: Intent,
            requestCode: Int,
            callBack: ActivityResultLauncherWrapper.OnActivityResultListener
    ) {

        weakLauncher = WeakReference(
                activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
                    callBack.onActivityResultFromLauncher(requestCode, result.resultCode, result.data)
                }
        )

        weakLauncher?.get()?.launch(intent)
    }

    override fun unregister() {
        weakLauncher?.get()?.unregister()
    }
}

I am using Dagger in my project and I injected the wrapper where it is needed

@Inject
lateinit var activityResultLauncher: ActivityResultLauncherWrapper

But the wrapper also can be instantiated directly:

val activityResultLauncher = ActivityResultLauncherWrapper()

then you have to change the startActivityForResult method with launchIntentForResult. Here is example where it is called from a fragment:

activityResultLauncher.launchIntentForResult(
        requireActivity(),
        intent,
        REQUEST_CODE_CONSTANT,
        object: ActivityResultLauncherWrapper.OnActivityResultListener {
            override fun onActivityResultFromLauncher(requestCode: Int, resultCode: Int, data: Intent?) {
                /*do something*/
            }
        }
)

You will receive the result in the anonymous object. You could use OnActivityResultListener in a Fragment or an FragmentActivity if you implement the Interface and refactor the current implementation like this:

class MyFragment : Fragment(), OnActivityResultListener {
   
 ...
    
override fun onActivityResultFromLauncher(requestCode: Int, resultCode: Int, data: Intent?) {/*do somthing*/}

 ...

}

As we know, the Kotlin class ActivityResultLauncherWrapper could be used in java code as well. There are java classes in my project as well. There is an example with implementation of the callback interface in a Fragment:

public class MyFragment extends Fragment implements OnActivityResultListener {
    
...

    @Inject
    ActivityResultLauncherWrapper activityResultLauncher;
//ActivityResultLauncherWrapper activityResultLauncher = new ActivityResultLauncherWrapper()

...

public void launnchActivity(@NotNull Intent intent) {
        activityResultLauncher.launchIntentForResult(requireActivity(), intent, REQUEST_CODE_CONSTANT, this);
    }

...

 @Override
    public void onActivityResultFromLauncher(int requestCode, int resultCode, Intent data) {/*do somthing*/}
...
}

I hope this helps to build the solution for your case.

Troth answered 3/2, 2021 at 13:23 Comment(0)
M
3

You can use extension functions for Koltin. For example:

//random utils file
fun Fragment.buildGetContentRequest(function: (Uri) -> Unit): ActivityResultLauncher<String> {
    return this.registerForActivityResult(ActivityResultContracts.GetContent()) {
        function(it)
    }
}

fun Fragment.buildTakePhotoRequest(function: (Boolean) -> Unit): ActivityResultLauncher<Uri> {
    return this.registerForActivityResult(ActivityResultContracts.TakePicture()) {
        function(it)
    }
}

fun Fragment.buildSelectMultipleContentRequest(function: (MutableList<Uri>?) -> Unit): ActivityResultLauncher<String> {
    return this.registerForActivityResult(ActivityResultContracts.GetMultipleContents()) {
        function(it)
    }
}

And then in your fragment something like this

//your actual fragment logic
class YourFragment : Fragment() {
    //we can assign our request in init process
    private val mRequestSelectFiles = buildSelectMultipleContentRequest { 
        onFilesSelected(it) 
    }


    fun onSelectFiles() {
        val mime = "*/*"
        mRequestSelectFiles.launch(mime)
    }

    fun onFilesSelected(list: MutableList<Uri>?) {
        //your logic
    }
}
Mayers answered 21/5, 2021 at 13:12 Comment(1)
You will need to add these dependencies :- implementation "androidx.activity:activity-ktx:1.3.0" implementation "androidx.fragment:fragment-ktx:1.3.6"Lotic
N
3

In my case I was trying to use the intent I was moving directly to the next activity without using the Google Sign In.

What worked for me :

Inside OnCreate set the onClickListener for the sign-in button :

     btnSignIn.setOnClickListener {
        signIn()
        }

    private fun signIn() {
        val intent = client.signInIntent
        mainActivityResultLauncher.launch(intent)
    }

In the above code I was writing the intent to go to the next activity but I had to write client.signInIntent

    var mainActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ result ->

        if(result.resultCode == Activity.RESULT_OK){
            val data = result.data
            val task = GoogleSignIn.getSignedInAccountFromIntent(data)
            try {
                // Google Sign In was successful, authenticate with Firebase
                val account = task.getResult(ApiException::class.java)!!
                Log.d(TAG, "firebaseAuthWithGoogle:" + account.id)
                firebaseAuthWithGoogle(account.idToken!!)
            } catch (e: ApiException) {
                // Google Sign In failed, update UI appropriately
                Log.w(TAG, "Google sign in failed", e)
            }
        }
    }
Nancynandor answered 1/10, 2021 at 11:14 Comment(0)
L
3

This was what how I replaced multiple requestCodes (put this code in your Activity):

    ActivityResultLauncher<Intent> launchCameraActivity = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
                if (result.getResultCode() == Activity.RESULT_OK) {
                    Intent data = result.getData();
                    Bitmap photoBitmap;
                    if(data != null && data.getExtras() != null){
                        photoBitmap = (Bitmap) data.getExtras().get("data");
                        if (photoBitmap != null) {
                            dataModel.setPhoto(ImageUtil.convert(photoBitmap));
                            imageTaken.setVisibility(View.VISIBLE);
                            imageTaken.setImageBitmap(photoBitmap);
                        }

                    }
                }
            }
        });

ActivityResultLauncher<Intent> launchCameraAndGalleryActivity = registerForActivityResult(
    new ActivityResultContracts.StartActivityForResult(),
    new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            if (result.getResultCode() == Activity.RESULT_OK) {
                
                Intent data = result.getData();
                Uri imageUri;
                if (data != null) {
                    imageUri = data.getData();
                    InputStream imageStream;
                    try {
                        imageStream = getContentResolver().openInputStream(imageUri);
                        Bitmap photoBitmap = BitmapFactory.decodeStream(imageStream);
                        dataModel.setOtherImage(ImageUtil.convert(photoBitmap));
                        documentImageTaken.setVisibility(View.VISIBLE);
                        documentImageTaken.setImageBitmap(photoBitmap);
                    }catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    });

I launch the activities like this:

                    Intent photoIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
                launchCameraAndGalleryActivity.launch(photoIntent );

Intent galleryIntent= new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    launchCameraActivity.launch(galleryIntent);
Laryngeal answered 27/11, 2021 at 6:54 Comment(1)
Great !!! Alternative for Image pickerDelicatessen
S
2

It seems that onActivityResult is deprecated in the super class but you did not mention the super class name and compileSdkVersion here in your question.

In Java and Kotlin every class or method could be marked as deprecated simply by adding @Deprecated to it so check your super class you may extend a wrong class.

When a class is deprecated all of its methods are deprecated too.

To see a quick solution click on deprecated method and press Ctrl+Q in Android studio to see documentation of method there should be a solution for it.


In my project using androidx and API 29 as compileSdkVersion, this method is NOT deprecated in activities and fragments

Sapajou answered 8/7, 2020 at 5:15 Comment(1)
Since now, in project, using androidx and API 29 as compileSdkVersion, this method is deprecated too.Canaletto
P
2

Kotlin version of @Muntashir Akon solution

class BetterActivityResult<Input, Result> private constructor(
  caller : ActivityResultCaller,
  contract : ActivityResultContract<Input, Result>,
  var onActivityResult : ((Result) -> Unit)?,
) {

private val launcher : ActivityResultLauncher<Input> =
   caller.registerForActivityResult(contract) { onActivityResult?.invoke(it) }

  /**
   * Launch activity, same as [ActivityResultLauncher.launch] except that it 
   * allows a callback
   * executed after receiving a result from the target activity.
   */
  /**
   * Same as [.launch] with last parameter set to `null`.
   */
  @JvmOverloads
  fun launch(
     input : Input,
     onActivityResult : ((Result) -> Unit)? = this.onActivityResult,
  ) {
    this.onActivityResult = onActivityResult
    launcher.launch(input)
  }

  companion object {
  /**
   * Register activity result using a [ActivityResultContract] and an in-place 
   * activity result callback like
   * the default approach. You can still customise callback using [.launch].
   */
  fun <Input, Result> registerForActivityResult(
    caller : ActivityResultCaller,
    contract : ActivityResultContract<Input, Result>,
    onActivityResult : ((Result) -> Unit)?,
  ) : BetterActivityResult<Input, Result> {
    return BetterActivityResult(caller, contract, onActivityResult)
  }

  /**
   * Same as [.registerForActivityResult] except
   * the last argument is set to `null`.
   */
  fun <Input, Result> registerForActivityResult(
    caller : ActivityResultCaller,
    contract : ActivityResultContract<Input, Result>,
  ) : BetterActivityResult<Input, Result> {
    return registerForActivityResult(caller, contract, null)
  }

  /**
   * Specialised method for launching new activities.
   */
  fun registerActivityForResult(
    caller : ActivityResultCaller,
  ) : BetterActivityResult<Intent, ActivityResult> {
    return registerForActivityResult(caller, StartActivityForResult())
  }
 }
}
Pilarpilaster answered 24/3, 2021 at 11:3 Comment(0)
W
2

An alternate way to do this is in 3 steps. (Considering you have a startActivityForResult(0 and onActivityResult())

  1. create a variable in the form var resultLauncher:ActivityResultLauncher<Intent>
  2. create a private function where you initialize the resultLauncher in this basic format
resultLauncher=registerForActivityResult(ActivityResultContracts.StartActivityForResult()){result ->  

// copy paste the code from the onActivityResult replacing resultcode to result.resultCode  

if(result.resultcode==Activity.Result_OK){
val data=result.data // this data variable is of type intent and you can use it 

}else{
//code if you do not get the data 
}
}
  1. Go to the line with startActivityForResult() and replace it with the line resultLauncher.launch(intent)
Wellfavored answered 22/6, 2021 at 9:55 Comment(0)
A
2

dor506 answer worked for me as i use BaseActivity in most of my projects so it is easier for me to change the code in single file rather than all my activites. I have written the java version of this code.

BaseActivity code :

private int requestCode = -1;
private ActivityResultLauncher<Intent> resultHandler = null;

 @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mContext = this;

    registerForActivityResult();
}
  private final void registerForActivityResult() {
    if (shouldRegisterForActivityResult()) {
        this.resultHandler = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
                new ActivityResultCallback() {

            public void onActivityResult(Object var1) {
                this.onActivityResult((ActivityResult)var1);
            }

            public final void onActivityResult(ActivityResult result) {
                Intrinsics.checkNotNullExpressionValue(result, "result");
                AppActivityClass.onActivityResult(result.getData(), AppActivityClass.this.requestCode, result.getResultCode());
                AppActivityClass.this.requestCode = -1;
            }
        });
    }
}

public final void startActivityForResult(int requestCode, Intent intent) {
    this.requestCode = requestCode;
    if (resultHandler != null) {
        resultHandler.launch(intent);
    }
}

protected static void onActivityResult(Intent intent, int requestCode, int resultCode) {
}

protected Boolean shouldRegisterForActivityResult() {
    return false;
}

Now in any activity use this code like this:

 @Override
protected Boolean shouldRegisterForActivityResult() {
    return true;  // this will override the baseactivity method and we can use onactivityresult
}

  private void someMethod(){
    Intent i = new Intent(mContext,SomeOtherClassActivity.class);
    startActivityForResult(101,i);
}

  @Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 101) {
        if (resultCode == RESULT_OK) {
            //revert from called class
        }
    }
}
Attorn answered 1/12, 2021 at 12:2 Comment(0)
B
2

Sharing solution that I've found

First, register this activity for result using registerForActivityResult This will return an object of type ActivityResultLauncher<Intent!> Like this,

private val getResult =
        registerForActivityResult(
            ActivityResultContracts.StartActivityForResult()
        ) {
            if (it.resultCode == Activity.RESULT_OK) {
                val value = it.data?.getStringExtra("input")
            }
        }

Now where ever we want to launch activity for result we can use getResult.launch(intent)

Bruell answered 7/12, 2021 at 15:13 Comment(1)
Simple, easy to follow example - thanks!Deaton
A
2

Combine with the above answer, I have a approach that compatible with the old way startActivityForResult() keep using requestCode without changing old code structure:

ActivityLauncher.class

public class ActivityLauncher {

private final ActivityResultLauncher<Intent> launcher;
private ActivityResultCallback<ActivityResult> activityResultCallback;

private ActivityLauncher(@NonNull ActivityResultCaller caller,
                         @NonNull ActivityResultContract<Intent, ActivityResult> contract,
                         @Nullable ActivityResultCallback<ActivityResult> activityResultCallback) {
    this.activityResultCallback = activityResultCallback;
    this.launcher = caller.registerForActivityResult(contract, this::onActivityResult);
}

public static ActivityLauncher registerActivityForResult(
        @NonNull ActivityResultCaller caller) {
    return new ActivityLauncher(caller, new ActivityResultContracts.StartActivityForResult(), null);
}

public void launch(Intent intent, @Nullable ActivityResultCallback<ActivityResult> activityResultCallback) {
    if (activityResultCallback != null) {
        this.activityResultCallback = activityResultCallback;
    }
    launcher.launch(intent);
}

private void onActivityResult(ActivityResult result) {
    if (activityResultCallback != null) activityResultCallback.onActivityResult(result);
}

public interface OnActivityResult {
    void onActivityResultCallback(int requestCode, int resultCode, Intent data);
}

}

Code in BaseActivity.java

private final ActivityLauncher activityLauncher = ActivityLauncher.registerActivityForResult(this);

public void startActivityForResult(Intent intent, int requestCode, ActivityLauncher.OnActivityResult onActivityResult) {
    activityLauncher.launch(intent, result -> onActivityResult.onActivityResultCallback(requestCode, result.getResultCode(), result.getData()));
}

And finally in each Activity that extends BaseActivity, implements ActivityLauncher.OnActivityResult and change the name of override function "onActivityResult" to "onActivityResultCallback". Also rember to remove super.onActivityResult()

How to use: startActivityForResult(intent, requestCode, this)

Augmentative answered 28/10, 2022 at 11:8 Comment(0)
A
1

startActivityForResult and onActivityResult is deprecated in android 10 API 30 now we have a new way to get the result using registerForActivityResult

resultContract =
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            // There are no request codes
            val country = result.data?.getParcelableExtra<Country>("Country")
            showLiveDemoDialogue(country)
        }
    }

and to launch activity

val intent = Intent(this, CountriesListActivity::class.java)
        resultContract.launch(intent)

but you should register before you call launch And launch wherever you want. otherwise, you will get this exception

attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.
Anatolio answered 7/4, 2021 at 7:33 Comment(0)
D
1
ActivityResultLauncher<Intent> someActivityResultLauncher = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
                if (result.getResultCode() == Activity.RESULT_OK) {

                }
            }
        });
Dufresne answered 9/6, 2021 at 9:19 Comment(0)
P
1

If you implement your base Activity like this, you may continure using startActivityForResult in old fashion. The only limitation is you will have to use setResult(result, intent) to set the result within your activity. The key is to let the result carry the request code back to the result consumer.

public class MyBaseActivity extends AppCompatActivity {
    private ActivityResultLauncher<Intent> activityLauncher;
    protected static String ACTIVITY_REQUEST_CODE = "my.activity.request.code";
    protected _originalIntent; 

    public void launchActivityForResult(Intent intent, int requestCode){
        intent.putExtra(UGM_ACTIVITY_REQUEST_CODE, requestCode);
        activityLauncher.launch(intent);
    }

    //
    //In order to be signature compatible for the rest of derived activities, 
    //we will override the deprecated method with our own implementation!
    //
    @SuppressWarnings( "deprecation" )
    public void startActivityForResult(Intent intent, int requestCode){
        launchActivityForResult(intent, requestCode);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    _originalIntent = getIntent();
        //set the default result
        setResult(Activity.RESULT_OK, _originalIntent);

        activityLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
                Intent intent = result.getData();
                int requestCode = intent.getIntExtra(ACTIVITY_REQUEST_CODE, -1);
                MyBaseActivity.this.onActivityResult(requestCode, result.getResultCode(), intent);
            }
        });
    }

}
Prithee answered 2/9, 2021 at 5:6 Comment(0)
O
0

Simple Example of registerForActivityResult for both StartActivityForResult & RequestMultiplePermissions from Activity and Fragment [in Kotlin]

Requesting activity for result from Activity

registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) { activityResult ->
    if (activityResult.resultCode == Activity.RESULT_OK) {
        //...
    }
}

Check out ActivityResult

Requesting for permissions from Activity?

registerForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) {
    //it: Map<String, Boolean>
}

From Fragment?

Use same methods but make sure you put these implementations in initialization, onAttach(), or onCreate()

Olwena answered 25/6, 2021 at 9:43 Comment(0)
I
0

In case you are using SMS consent API then use the following code (Kotlin):

resultLauncher.launch( consentIntent
                            )

    var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        // There are no request codes
    //    val data: Intent? = result.data
        val message = result.data?.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE)
        getOtpFromMessage(message)

    }
}
Impeach answered 28/7, 2021 at 6:29 Comment(0)
A
0

I am using kotlin extension to make it very simple. Add below extensiton fucntion in your Extenstions.kt file:

fun AppCompatActivity.startForResult(intent: Intent,
    onResult: (resultCode: Int, data: Intent?) -> Unit
) {
    this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {result ->
        onResult(result.resultCode, result.data)
    }.launch(intent)
}

Now, inside any activity that inherits AppCompatActivity, you can use below simple code:

val i = Intent(this, TargetActivity::class.java)
startForResult(i) { resultCode, data ->
   //put your code here like:
   if (resultCode == RESULT_OK) {
      //your code here...
      }
   }
}

Update Above implementaion may cause below exception: java.lang.IllegalStateException: LifecycleOwner xxxx is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.

So registerForActivityResult should be called in advance for example before onCreate. Here is the alternative solution.

Add below extensiton fucntion in your Extenstions.kt file:

fun AppCompatActivity.registerForResult(onResult: (resultCode: Int, data: Intent?) -> Unit):
        ActivityResultLauncher<Intent> {
    return this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        onResult(result.resultCode, result.data)
    }
}

Now, inside any activity that inherits AppCompatActivity, you can use below simple code:

  1. Define a class memeber variable for every action requires result
private val myActionResult = registerForResult { resultCode, data ->
   //put your code here like:
   if (resultCode == RESULT_OK) {
      //your code here...
      }
   }
}
  1. Launch the action
val i = Intent(this, TargetActivity::class.java)
myActionResult.launch(i)
Aargau answered 31/8, 2021 at 19:53 Comment(0)
R
0

Adding on to the answers by muntashir akon and abhijeet, you can modify the new format to work like the old format by passing values in the intent, for example:

// calling class
....
val i = Intent(this@GEBShopActivity, BarcodeScannerActivity::class.java)
when(loadedFragment){   
   is ShopHomeFragment      -> { i.putExtra("myapp.result.code", CODE_ACTIVITY_SCAN_LIST_MAINT) }
   is ShopListFragment      -> { i.putExtra("myapp.result.code", CODE_ACTIVITY_SCAN_LIST_MAINT) }
   is ShopItemMaintFragment -> { i.putExtra("myapp.result.code", CODE_ACTIVITY_SCAN_ITEM_MAINT) }
   is ShopPriceFragment     -> { i.putExtra("myapp.result.code", CODE_ACTIVITY_PRICE_CAPTURE) }
   is ShopCompareFragment   -> { i.putExtra("myapp.result.code", CODE_ACTIVITY_PRICE_CAPTURE) }
}
shopFragmentLauncher.launch(i)
....
// called class
....
val resultIntent = Intent()
val bundle = Bundle()
bundle.putStringArrayList("scanned_barcodes", scanned_barcodes)
bundle.putInt("scan_count", scan_count)
resultIntent.putExtras(bundle)
resultIntent.putExtra("myapp.result.code", intent.getIntExtra("myapp.result.code", 0))
setResult(Activity.RESULT_OK, resultIntent)
....

This will allow you to keep the class called the same with just the one extra line to add your original called result code. Also allows you to create a reusable launcher instance.

Rema answered 23/10, 2021 at 4:59 Comment(0)
C
0

2023 Kotlin Answer to get an Uri.

An example for those who are searching for a way to fetch an image from the image library and handle the Uri because super.onActivityResult(requestCode, resultCode, data) is crossed out/deprecated

enter image description here

I got this answer from google docs and it works.

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

FYI you should be able to use this to get any Uri, not just for images. Here it is used below.

import androidx.activity.result.contract.ActivityResultContracts

class MainActivity : AppCompatActivity() {

    lateinit var binding: ActivityMainBinding

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

        binding.selectPhotoButton.setOnClickListener {
            selectPhoto()
        }
    }

    fun selectPhoto() {

        getContent.launch("image/*") // *** What Google said to do to launch the image library ***
    }

    // *** This is what Google posted to handle the Uri ***
    val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->

        // Handle the returned Uri

        binding.myImageView.setImageURI(uri)
    }
}
Calipee answered 29/4, 2023 at 6:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.