65

I have a main activity which serves as an entry point to call different activities, depending on condition. Among others, I use Firebase Auth to manage user sign in:

startActivityForResult(
            AuthUI.getInstance().createSignInIntentBuilder()
                    .setAvailableProviders(providers)
                    .build(),
            RC_SIGN_IN)

I overwrite onActivityResult() to distinguish the returned intent/data, for example:

 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    when (requestCode) {

        REQUEST_CODE_1 -> {
          // update UI and stuff
        }

        RC_SIGN_IN -> {
          // check Firebase log in
        }
        // ...
    }
}

With the Activity Result APIs which is strongly recommended by the documentation, I get that I should make prepareCall() before ActivityResultLauncher and to make sure the activity is in created state when I launch, but I still don't understand how to handle multiple activity results gracefully (at least, in one place) like in onActivityResult().

Looking at this article, it seems I need to implement multiple child inner classes of ActivityResultContract type (therefore multiple prepareCall()'s?), because they are suppose to be different contracts, am I correct? Can someone please show me some skeleton example that mirrors the above onActivityResult() logic?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Neoh
  • 15,906
  • 14
  • 66
  • 78

8 Answers8

64

You can call as many Activities for result as you wish and have separate callback for each:

val startForResult = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) { result: ActivityResult ->
    if (result.resultCode == Activity.RESULT_OK) {
        //  you will get result here in result.data
    }
}

startForResult.launch(Intent(activity, CameraCaptureActivity::class.java))

You just need to specify Activity class - CameraCaptureActivity::class.java

Update:

The prepareCall() method has been renamed to registerForActivityResult() in Activity 1.2.0-alpha04 and Fragment 1.3.0-alpha04. And it should be startForResult.launch(...) in the last line

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
Misha Akopov
  • 12,241
  • 27
  • 68
  • 82
  • 2
    The `prepareCall()` method has been renamed to `registerForActivityResult()` in [Activity `1.2.0-alpha04`](https://developer.android.com/jetpack/androidx/releases/activity#1.2.0-alpha04) and [Fragment `1.3.0-alpha04`](https://developer.android.com/jetpack/androidx/releases/fragment#1.3.0-alpha04). And it should be `startForResult.launch(...)` in the last line – Rafael Tavares Sep 10 '20 at 11:23
  • 2
    What about passing `'android.content.Intent intent, int requestCode'` as parameters? – AtomX Apr 10 '21 at 12:58
  • 1
    But, in order to send back the result from Activity2, is the same as always?: setResult(Bundle)??? – mantc_sdr Jun 10 '21 at 09:19
  • 1
    it returns result.data as null – Karunesh Palekar Aug 04 '21 at 12:39
48

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

Example

public void openActivityForResult() {
    
 //Instead of startActivityForResult use this one
        Intent intent = new Intent(this,OtherActivity.class);
        someActivityResultLauncher.launch(intent);
    }


//Instead of onActivityResult() method use this one

    ActivityResultLauncher<Intent> someActivityResultLauncher = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback<ActivityResult>() {
                @Override
                public void onActivityResult(ActivityResult result) {
                    if (result.getResultCode() == Activity.RESULT_OK) {
                        // Here, no request code
                        Intent data = result.getData();
                        doSomeOperations();
                    }
                }
            });
Stefano Sansone
  • 2,377
  • 7
  • 20
  • 39
Hardik Hirpara
  • 2,594
  • 20
  • 34
23

First, don’t forget to add this to your Gradle dependency

implementation 'androidx.activity:activity-ktx:1.2.0-alpha05'
implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha05'

Second, create your result contract by extending an abstract class called ActivityResultContract<I, O>. I mean the type of input and O means the type of output. And then you only need to override 2 methods

class PostActivityContract : ActivityResultContract<Int, String?>() {

    override fun createIntent(context: Context, input: Int): Intent {
        return Intent(context, PostActivity::class.java).apply {
            putExtra(PostActivity.ID, postId)
        }
    }

    override fun parseResult(resultCode: Int, intent: Intent?): String? {
        val data = intent?.getStringExtra(PostActivity.TITLE)
        return if (resultCode == Activity.RESULT_OK && data != null) data
        else null
    }
}

And finally, the last step is registering the contract to Activity. You need to pass your custom contract and callback into registerForActivityResult.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
      
        start_activity_contract.setOnClickListener {
            openPostActivityCustom.launch(1)
        }
    }
  
    // Custom activity result contract
    private val openPostActivityCustom =
        registerForActivityResult(PostActivityContract()) { result ->
            // parseResult will return this as string?                                              
            if (result != null) toast("Result : $result")
            else toast("No Result")
        }
}

For More info check this Post

Krishna Sony
  • 1,286
  • 13
  • 27
5

In this case, what was returned by AuthUI was already an Intent, so, we use it like in the example below.

private val startForResult =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            when(result.resultCode){
                RESULT_OK -> {
                    val intent = result.data
                    // Handle the Intent...
                    mUser = FirebaseAuth.getInstance().currentUser
                }
                RESULT_CANCELED -> {

                } else -> {
            } }
        }

start the activity from anywhere (for example on button click) using:

 AuthUI.getInstance().createSignInIntentBuilder().setAvailableProviders(providers)
            .build().apply {
                startForResult.launch(this)
            }
nyxee
  • 2,773
  • 26
  • 22
1

If you start an activity from a fragment and return result to the fragment, do this.

In fragment:

private lateinit var activityResult: ActivityResultLauncher<Intent>

activityResult = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode == RESULT_OK) {
        val data = result.data
        doSomeOperations(data)
    }
}

SomeActivity.showScreen(activityResult, requireContext())

In activity:

// activity?.setResult(Activity.RESULT_OK) doesn't change.

companion object {

    fun showScreen(activityResult: ActivityResultLauncher<Intent>, context: Context) {
        val intent = Intent(context, SomeActivity::class.java)
        activityResult.launch(intent)
    }
}
CoolMind
  • 26,736
  • 15
  • 188
  • 224
  • what is SomeActivity.showScreen(activityResult, requireContext()) i mean what is showScreen ??? can you more describe ? – Yogi Arif Widodo Feb 01 '22 at 22:24
  • 1
    @YogiArifWidodo, `fun showScreen(activityResult: ActivityResultLauncher, context: Context)` in the 2nd part of the answer. You can open an activity `SomeActivity` from a fragment, do some actions and return a result from that activity to the fragment. – CoolMind Feb 02 '22 at 07:15
  • @YogiArifWidodo, forgot to say that in Activity we should return a result with `setResult(RESULT_OK, data)` if I remember. – CoolMind Dec 27 '22 at 07:32
1
List<AuthUI.IdpConfig> providers = Arrays.asList(
                    new AuthUI.IdpConfig.EmailBuilder().build(),
                    new AuthUI.IdpConfig.GoogleBuilder().build());

            ActivityResultLauncher<Intent> launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
                if (result.getResultCode() == Activity.RESULT_OK) {
                    Log.v("LOGIN OK", "OK Result for Login");
                }
            });
            
            launcher.launch(AuthUI.getInstance()
                    .createSignInIntentBuilder()
                    .setIsSmartLockEnabled(false)
                    .setAvailableProviders(providers)
                    .build());

See this For More Details : https://githubmemory.com/repo/firebase/FirebaseUI-Android/issues?cursor=Y3Vyc29yOnYyOpK5MjAyMS0wMy0wNVQyMjoxNzozMyswODowMM4xEAQZ&pagination=next&page=2

Pallav Gupta
  • 101
  • 1
  • 3
1

Use This for Firebase AuthUI;

final ActivityResultLauncher<Intent> launcher = registerForActivityResult(
            new FirebaseAuthUIActivityResultContract(), this::onSignInResult);

    binding.loginSignup.setOnClickListener(view -> {
        List<AuthUI.IdpConfig> provider = Arrays.asList(new AuthUI.IdpConfig.EmailBuilder().build(),
                new AuthUI.IdpConfig.GoogleBuilder().build(),
                new AuthUI.IdpConfig.PhoneBuilder().build());
        Intent intent = AuthUI.getInstance()
                .createSignInIntentBuilder()
                .setIsSmartLockEnabled(false)
                .setAlwaysShowSignInMethodScreen(true)
                .setAvailableProviders(provider)
                .build();
        launcher.launch(intent);
    });


private void onSignInResult(FirebaseAuthUIAuthenticationResult result) {

    if (result.getResultCode() == RESULT_OK) {
        FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
        if (user != null) {
            if (user.getMetadata() != null) {
                if (user.getMetadata().getCreationTimestamp() != user.getMetadata().getLastSignInTimestamp()) {
                    Toast.makeText(this, "Welcome Back", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(this, "Welcome", Toast.LENGTH_SHORT).show();
                }
                startMainActivity();
            }
        }
    } else {
        IdpResponse response = result.getIdpResponse();
        if (response == null)
            Toast.makeText(this, "Canceled By You", Toast.LENGTH_SHORT).show();
        else Log.d(TAG, "onCreate: ActivityResult" + response.getError());
    }
}

private void startMainActivity() {
    Intent intent = new Intent(LoginActivity.this, MainActivity.class);
    startActivity(intent);
    finish();
}

Like This.

0
public void openSomeActivityForResult() {
    someActivityResultLauncher.launch(AuthUI.getInstance().createSignInIntentBuilder()
                .setAvailableProviders(providers)
                .build());
}

// 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();
                }
                else if(result.getResultCode() == Activity.RESULT_CANCELED) {
                    // code for cancelled result here
                }
            }
        });
Milan Hirpara
  • 534
  • 4
  • 18