activityResult; The new way
Launching the camera or dialing a number or even asking for runtime permission was some sort of a bad experience (for me at least) as you should fire your request and then wait for the result in another method, onActivityResult callback in your activity or fragment and if you happen to have a case when you want to do this in another class you will have to do a workaround or a delegation for this.
The real problem with me in that happens when I have many things I should take care of in the same activity as asking for permission and based on the result do another thing and after that another thing and so on. Can you imagine how messy my onActivityResult method becomes? In the latest release of AndroidX’s Fragment and Activity, they have released a good replacement for that pattern.
The new way of getting a result from an Activity.
This basic concept of this patter has four three components but before we start don’t forget to add the dependency to your app Gradle file:
implementation 'androidx.activity:activity:1.2.0-alpha02'
ActivityResultLauncher: An interface represents the launcher of the required intent which has a method launch takes input for that intent:
/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.activity.result; import android.annotation.SuppressLint; import androidx.activity.result.contract.ActivityResultContract; /** * A launcher for a prevoiusly-{@link ActivityResultCaller#prepareCall prepared call} to start * the process of executing an {@link ActivityResultContract}. * * @param <I> type of the input required to launch */ public interface ActivityResultLauncher<I> { /** * Executes an {@link ActivityResultContract}. * * @param input the input required to execute an {@link ActivityResultContract}. */ void launch(@SuppressLint("UnknownNullness") I input); /** * Disposes of this launcher, releasing the underlying result callback, and any references * captured within it. * * You should call this if the registry may live longer than the callback registered for this * launcher. */ void dispose();
}
ActivityResultContract: A contract specifying that an activity can be called with an input of type I and produce an output of type O, it is an abstract class contains 2 methods the first one createIntent in which you create the target intent to the second activity which you want to get results from, and the second one parseResult from which you get your results, here’s the implementation:
/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.activity.result.contract; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import androidx.activity.result.ActivityResultCaller; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * A contract specifying that an activity can be called with an input of type I * and produce an output of type O * * Makes calling an activity for result type-safe. * * @param <I> input type * @param <O> output type * * @see ActivityResultCaller */ public abstract class ActivityResultContract<I, O> { /** Create an intent that can be used for {@link Activity#startActivityForResult} */ public abstract @NonNull Intent createIntent(@SuppressLint("UnknownNullness") I input); /** Convert result obtained from {@link Activity#onActivityResult} to O */ public abstract @SuppressLint("UnknownNullness") O parseResult( int resultCode, @Nullable Intent intent);
}
ActivityResultCallback: An interface which provides you with the result once it becomes available throw onActivityResult method:
/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.activity.result; import android.annotation.SuppressLint; import android.app.Activity; /** * A type-safe callback to be called when an {@link Activity#onActivityResult activity result} * is available. * * @param <O> result type */ public interface ActivityResultCallback<O> { /** * Called when result is available */ void onActivityResult(@SuppressLint("UnknownNullness") O result);
}
How to?
After we saw the components and what does each one of them mean let’s see how to connect them together, let’s say that we want to request location permission from the user and based on the response on the user we show a toast.
- At first, we want to make our launcher ready, fortunately, there’s a method called prepareCall which is built for that purpose it takes an ActivityResultContract and an ActivityResultCallback and returns an ActivityResultLauncher which you’ll use to launch the other activity:
@NonNull @Override public <I, O> ActivityResultLauncher<I> prepareCall( @NonNull ActivityResultContract<I, O> contract, @NonNull ActivityResultCallback<O> callback) { return prepareCall(contract, mActivityResultRegistry, callback);
}
- Then we want to build our contract and also for that there are some built-in contracts for the most common use-cases like dialing a number, requesting permission or even open your own activity result, you can find all of them in a class called ActivityResultContracts.java, here’s what we want from it:
/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.activity.result.contract; import static androidx.activity.result.contract.ActivityResultContracts.RequestPermissions.ACTION_REQUEST_PERMISSIONS; import static androidx.activity.result.contract.ActivityResultContracts.RequestPermissions.EXTRA_PERMISSIONS; import static androidx.activity.result.contract.ActivityResultContracts.RequestPermissions.EXTRA_PERMISSION_GRANT_RESULTS; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.net.Uri; import android.provider.MediaStore; import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultCaller; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * A collection of some standard activity call contracts, as provided by android. */ public class ActivityResultContracts { private ActivityResultContracts() {} // ... // ... /** * An {@link ActivityResultContract} to {@link Activity#requestPermissions request permissions} */ public static class RequestPermissions extends ActivityResultContract<String[], java.util.Map<String, Boolean>> { /** * An {@link Intent} action for making a permission request via a regular * {@link Activity#startActivityForResult} API. * * Caller must provide a {@code String[]} extra {@link #EXTRA_PERMISSIONS} * * Result will be delivered via {@link Activity#onActivityResult(int, int, Intent)} with * {@code String[]} {@link #EXTRA_PERMISSIONS} and {@code int[]} * {@link #EXTRA_PERMISSION_GRANT_RESULTS}, similar to * {@link Activity#onRequestPermissionsResult(int, String[], int[])} * * @see Activity#requestPermissions(String[], int) * @see Activity#onRequestPermissionsResult(int, String[], int[]) */ public static final String ACTION_REQUEST_PERMISSIONS = "androidx.activity.result.contract.action.REQUEST_PERMISSIONS"; /** * Key for the extra containing all the requested permissions. * * @see #ACTION_REQUEST_PERMISSIONS */ public static final String EXTRA_PERMISSIONS = "androidx.activity.result.contract.extra.PERMISSIONS"; /** * Key for the extra containing whether permissions were granted. * * @see #ACTION_REQUEST_PERMISSIONS */ public static final String EXTRA_PERMISSION_GRANT_RESULTS = "androidx.activity.result.contract.extra.PERMISSION_GRANT_RESULTS"; @NonNull @Override public Intent createIntent(@NonNull String[] input) { return new Intent(ACTION_REQUEST_PERMISSIONS).putExtra(EXTRA_PERMISSIONS, input); } @NonNull @Override public Map<String, Boolean> parseResult(int resultCode, @Nullable Intent intent) { if (resultCode != Activity.RESULT_OK) return Collections.emptyMap(); if (intent == null) return Collections.emptyMap(); String[] permissions = intent.getStringArrayExtra(EXTRA_PERMISSIONS); int[] grantResults = intent.getIntArrayExtra(EXTRA_PERMISSION_GRANT_RESULTS); if (grantResults == null || permissions == null) return Collections.emptyMap(); Map<String, Boolean> result = new HashMap<String, Boolean>(); for (int i = 0, size = permissions.length; i < size; i++) { result.put(permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED); } return result; } } // ... // ... /** * An {@link ActivityResultContract} to {@link Activity#requestPermissions request a permission} */ public static class RequestPermission extends ActivityResultContract<String, Boolean> { @NonNull @Override public Intent createIntent(@NonNull String input) { return new Intent(ACTION_REQUEST_PERMISSIONS) .putExtra(EXTRA_PERMISSIONS, new String[] { input }); } @NonNull @Override public Boolean parseResult(int resultCode, @Nullable Intent intent) { if (resultCode != Activity.RESULT_OK) return false; if (intent == null) return false; int[] grantResults = intent.getIntArrayExtra(EXTRA_PERMISSION_GRANT_RESULTS); if (grantResults == null) return false; return grantResults[0] == PackageManager.PERMISSION_GRANTED; } }
}
- The remaining thing is the callback and that’s what we want to write for handling what we want after the user takes the action and that’s the second parameter for our prepareCall now let’s see how we connect all of these together:
class MainActivity : AppCompatActivity() { // here's our ActivityResultLauncher, the RequestPermission like previos takes a string represents the permission // and returns boolean represents the user acitns val permissionRequester = prepareCall(ActivityResultContracts.RequestPermission()) { permissionGranted: Boolean -> if (permissionGranted) Toast.makeText(this, "Permission has been accepted", Toast.LENGTH_SHORT).show() else Toast.makeText(this, "Permission has been denied", Toast.LENGTH_SHORT).show() } // Here we can have multiple launchers, as many as we want. override fun onCreate(savedInstanceState: Bundle?) { // ... // here we fire the permission request based on a button in our activity with this id. request_permission_btn.setOnClickListener { permissionRequester.launch(/* we launch with the required Input*/ Manifest.permission.ACCESS_FINE_LOCATION) } }
}
That’s the basic idea of how to implement it, in case you wanted to add another intent you will have another handler(ActivityResultLauncher) for it like the permissionRequester.
Please follow this link to know how more and know how to create your own contract.
Stay at home everyone and I hope we get over this fast. ??
Thanks for reading! Be sure to like below and recommend this article if you liked it.
Reach me out on Twitter , Medium