I recently discovered that onActivityResult is deprecated. What should we do to handle it?
Any alternative introduced for that?
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.
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
}
}
There are 4 simple steps to follow while replacing the deprecated method startActivityForResult(...).
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..
Imports :
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
In place of startActivityForResult(intent, 123), use
Intent intent = new Intent(this, SampleActivity.class);
activityResultLaunch.launch(intent);
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! :)
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
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);
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.
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/*")
}
}
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.
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)
}
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.
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
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
}
}
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);
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)
}
}
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)
}
}
}
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
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())
}
}
}
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
}
}
}
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)
The below code works in the Kotlin fragment to check the Bluetooth permission. Year - 2022
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)
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.
ActivityResultLauncher<Intent> someActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
#Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
}
}
});
An alternate way to do this is in 3 steps. (Considering you have a startActivityForResult(0 and onActivityResult())
create a variable in the form var resultLauncher:ActivityResultLauncher<Intent>
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
}
}
Go to the line with startActivityForResult() and replace it with the line resultLauncher.launch(intent)
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);
}
});
}
}
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)
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()
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)
}
}
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:
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...
}
}
}
Launch the action
val i = Intent(this, TargetActivity::class.java)
myActionResult.launch(i)
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.
Related
This question already has answers here:
Java solution for "startActivityForResult(Intent,int) in Fragment has been deprecated" when opening external URL?
(2 answers)
Closed last month.
'startActivityForResult(android.content.Intent, int)' is deprecated, what can I do? This is the code for my QR code scanner Android app (Java):
package com.example.wfr;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.qr_scanner_layout);
//Button click event to open QR scanner
findViewById(R.id.camera_button).setOnClickListener(v -> {
Intent intent = new Intent(MainActivity.this, QRCodeScanner.class);
intent.putExtra("SCAN_FORMATS", "QR_CODE");
startActivityForResult(intent, REQUEST_CODE);
});
}
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
if (data != null) {
String scannedText = data.getStringExtra("com.journeyapps.barcodescanner.CaptureActivity.SCAN_RESULT");
TextView scannedTextView = findViewById(R.id.scanned_text);
scannedTextView.setText(scannedText);
}
}
}
}
A basic training is available at https://developer.android.com/training/basics/intents/result
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();
}
}
});
This is Your Code
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.qr_scanner_layout);
//Button click event to open QR scanner
findViewById(R.id.camera_button).setOnClickListener(v -> {
Intent intent = new Intent(MainActivity.this, QRCodeScanner.class);
intent.putExtra("SCAN_FORMATS", "QR_CODE");
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();
if (data != null) {
String scannedText = data.getStringExtra("com.journeyapps.barcodescanner.CaptureActivity.SCAN_RESULT");
TextView scannedTextView = findViewById(R.id.scanned_text);
scannedTextView.setText(scannedText);
}
}
}
});
}
If a function is deprecated, the documentation usually tells you why and what you should use instead:
This function is deprecated.
This method has been deprecated in favor of using the Activity Result API which brings increased type safety via an ActivityResultContract and the prebuilt contracts for common intents available in androidx.activity.result.contract.ActivityResultContracts, provides hooks for testing, and allow receiving results in separate, testable classes independent from your activity. Use registerForActivityResult passing in a StartActivityForResult object for the ActivityResultContract.
The other answer shows you the developer guide for using this stuff and has an example, but it's important to always look at the documentation when you run into deprecated stuff - it's rare a function is removed with no information, and sometimes you won't need to worry about the deprecation anyway (e.g. if it only happened with the newest version of Android, meaning they've just decided it will be removed sometime in the future as the older APIs fall out of use)
I recently discovered that onActivityResult is deprecated. What should we do to handle it?
Any alternative introduced for that?
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.
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
}
}
There are 4 simple steps to follow while replacing the deprecated method startActivityForResult(...).
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..
Imports :
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
In place of startActivityForResult(intent, 123), use
Intent intent = new Intent(this, SampleActivity.class);
activityResultLaunch.launch(intent);
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! :)
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
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);
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.
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/*")
}
}
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.
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)
}
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.
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
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
}
}
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);
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)
}
}
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)
}
}
}
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
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())
}
}
}
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
}
}
}
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)
The below code works in the Kotlin fragment to check the Bluetooth permission. Year - 2022
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)
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.
ActivityResultLauncher<Intent> someActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
#Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
}
}
});
An alternate way to do this is in 3 steps. (Considering you have a startActivityForResult(0 and onActivityResult())
create a variable in the form var resultLauncher:ActivityResultLauncher<Intent>
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
}
}
Go to the line with startActivityForResult() and replace it with the line resultLauncher.launch(intent)
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);
}
});
}
}
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)
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()
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)
}
}
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:
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...
}
}
}
Launch the action
val i = Intent(this, TargetActivity::class.java)
myActionResult.launch(i)
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.
In my activity, I'm calling a second activity from the main activity by startActivityForResult. In my second activity, there are some methods that finish this activity (maybe without a result), however, just one of them returns a result.
For example, from the main activity, I call a second one. In this activity, I'm checking some features of a handset, such as does it have a camera. If it doesn't have then I'll close this activity. Also, during the preparation of MediaRecorder or MediaPlayer if a problem happens then I'll close this activity.
If its device has a camera and recording is done completely, then after recording a video if a user clicks on the done button then I'll send the result (address of the recorded video) back to the main activity.
How do I check the result from the main activity?
From your FirstActivity, call the SecondActivity using the startActivityForResult() method.
For example:
int LAUNCH_SECOND_ACTIVITY = 1
Intent i = new Intent(this, SecondActivity.class);
startActivityForResult(i, LAUNCH_SECOND_ACTIVITY);
In your SecondActivity, set the data which you want to return back to FirstActivity. If you don't want to return back, don't set any.
For example: In SecondActivity if you want to send back data:
Intent returnIntent = new Intent();
returnIntent.putExtra("result",result);
setResult(Activity.RESULT_OK,returnIntent);
finish();
If you don't want to return data:
Intent returnIntent = new Intent();
setResult(Activity.RESULT_CANCELED, returnIntent);
finish();
Now in your FirstActivity class, write the following code for the onActivityResult() method.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == LAUNCH_SECOND_ACTIVITY) {
if(resultCode == Activity.RESULT_OK){
String result=data.getStringExtra("result");
}
if (resultCode == Activity.RESULT_CANCELED) {
// Write your code if there's no result
}
}
} //onActivityResult
To implement passing data between two activities in a much better way in Kotlin, please go through 'A better way to pass data between Activities'.
How to check the result from the main activity?
You need to override Activity.onActivityResult() and then check its parameters:
requestCode identifies which app returned these results. This is defined by you when you call startActivityForResult().
resultCode informs you whether this app succeeded, failed, or something different
data holds any information returned by this app. This may be null.
Example
To see the entire process in context, here is a supplemental answer. See my fuller answer for more explanation.
MainActivity.java
public class MainActivity extends AppCompatActivity {
// Add a different request code for every activity you are starting from here
private static final int SECOND_ACTIVITY_REQUEST_CODE = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// "Go to Second Activity" button click
public void onButtonClick(View view) {
// Start the SecondActivity
Intent intent = new Intent(this, SecondActivity.class);
startActivityForResult(intent, SECOND_ACTIVITY_REQUEST_CODE);
}
// This method is called when the second activity finishes
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// check that it is the SecondActivity with an OK result
if (requestCode == SECOND_ACTIVITY_REQUEST_CODE) {
if (resultCode == RESULT_OK) { // Activity.RESULT_OK
// get String data from Intent
String returnString = data.getStringExtra("keyName");
// set text view with string
TextView textView = (TextView) findViewById(R.id.textView);
textView.setText(returnString);
}
}
}
}
SecondActivity.java
public class SecondActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
// "Send text back" button click
public void onButtonClick(View view) {
// get the text from the EditText
EditText editText = (EditText) findViewById(R.id.editText);
String stringToPassBack = editText.getText().toString();
// put the String to pass back into an Intent and close this activity
Intent intent = new Intent();
intent.putExtra("keyName", stringToPassBack);
setResult(RESULT_OK, intent);
finish();
}
}
Complementing the answer from Nishant, the best way to return the activity result is:
Intent returnIntent = getIntent();
returnIntent.putExtra("result",result);
setResult(RESULT_OK,returnIntent);
finish();
I was having a problem with
new Intent();
Then I found out that the correct way is using
getIntent();
to get the current intent.
startActivityForResult: Deprecated in Android X
For the new way we have registerForActivityResult.
In Java :
// You need to create a launcher variable inside onAttach or onCreate or global, i.e, before the activity is displayed
ActivityResultLauncher<Intent> launchSomeActivity = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
#Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
// your operation....
}
}
});
public void openYourActivity() {
Intent intent = new Intent(this, SomeActivity.class);
launchSomeActivity.launch(intent);
}
In Kotlin :
var resultLauncher = registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
// your operation...
}
}
fun openYourActivity() {
val intent = Intent(this, SomeActivity::class.java)
resultLauncher.launch(intent)
}
Advantage:
The new way is reduce complexity which we faced when we call the activity from a fragment or from another activity
Easily ask for any permission and get callback
For those who have problem with wrong requestCode in onActivityResult
If you are calling startActivityForResult() from your Fragment, the requestCode is changed by the Activity that owns the Fragment.
If you want to get the correct resultCode in your activity try this:
Change:
startActivityForResult(intent, 1); To:
getActivity().startActivityForResult(intent, 1);
The ActivityResultRegistry is the recommended approach
ComponentActivity now provides an ActivityResultRegistry that lets you handle the startActivityForResult()+onActivityResult() as well as requestPermissions()+onRequestPermissionsResult() flows without overriding methods in your Activity or Fragment, brings increased type safety via ActivityResultContract, and provides hooks for testing these flows.
It is strongly recommended to use the Activity Result APIs introduced in Android 10 Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02.
Add this to your build.gradle
def activity_version = "1.2.0-beta01"
// Java language implementation
implementation "androidx.activity:activity:$activity_version"
// Kotlin
implementation "androidx.activity:activity-ktx:$activity_version"
How to use the pre-built contract
This new API has the following pre-built functionalities
TakeVideo
PickContact
GetContent
GetContents
OpenDocument
OpenDocuments
OpenDocumentTree
CreateDocument
Dial
TakePicture
RequestPermission
RequestPermissions
An example that uses the takePicture contract:
private val takePicture = prepareCall(ActivityResultContracts.TakePicture()) { bitmap: Bitmap? ->
// Do something with the Bitmap, if present
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener { takePicture() }
}
So what’s going on here? Let’s break it down slightly. takePicture is just a callback which returns a nullable Bitmap - whether or not it’s null depends on whether or not the onActivityResult process was successful. prepareCall then registers this call into a new feature on ComponentActivity called the ActivityResultRegistry - we’ll come back to this later. ActivityResultContracts.TakePicture() is one of the built-in helpers which Google have created for us, and finally invoking takePicture actually triggers the Intent in the same way that you would previously with Activity.startActivityForResult(intent, REQUEST_CODE).
How to write a custom contract
A simple contract that takes an Int as an input and returns a string that the requested Activity returns in the result Intent.
class MyContract : ActivityResultContract<Int, String>() {
companion object {
const val ACTION = "com.myapp.action.MY_ACTION"
const val INPUT_INT = "input_int"
const val OUTPUT_STRING = "output_string"
}
override fun createIntent(input: Int): Intent {
return Intent(ACTION)
.apply { putExtra(INPUT_INT, input) }
}
override fun parseResult(resultCode: Int, intent: Intent?): String? {
return when (resultCode) {
Activity.RESULT_OK -> intent?.getStringExtra(OUTPUT_STRING)
else -> null
}
}
}
class MyActivity : AppCompatActivity() {
private val myActionCall = prepareCall(MyContract()) { result ->
Log.i("MyActivity", "Obtained result: $result")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
button.setOnClickListener {
myActionCall(500)
}
}
}
Check this official documentation for more information.
If you want to update the user interface with the activity result, you can't to use this.runOnUiThread(new Runnable() {}. Doing this, the UI won't refresh with the new value. Instead, you can do this:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_CANCELED) {
return;
}
global_lat = data.getDoubleExtra("LATITUDE", 0);
global_lng = data.getDoubleExtra("LONGITUDE", 0);
new_latlng = true;
}
#Override
protected void onResume() {
super.onResume();
if(new_latlng)
{
PhysicalTagProperties.this.setLocation(global_lat, global_lng);
new_latlng=false;
}
}
This seems silly, but it works pretty well.
In Kotlin
Suppose A & B are activities the navigation is from A -> B
We need the result back from A <- B
in A
// calling the Activity B
resultLauncher.launch(Intent(requireContext(), B::class.java))
// we get data in here from B
private var resultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
when (result.resultCode) {
Activity.RESULT_OK -> {
result.data?.getStringExtra("VALUE")?.let {
// data received here
}
}
Activity.RESULT_CANCELED -> {
// cancel or failure
}
}
}
In B
// Sending result value back to A
if (success) {
setResult(RESULT_OK, Intent().putExtra("VALUE", value))
} else {
setResult(RESULT_CANCELED)
}
It is a very common problem on Android
It can be broken down into three pieces
Start Activity B (happens in Activity A)
Set requested data (happens in activity B)
Receive requested data (happens in activity A)
startActivity B
Intent i = new Intent(A.this, B.class);
startActivity(i);
Set requested data
In this part, you decide whether you want to send data back or not when a particular event occurs.
E.g.: In activity B there is an EditText and two buttons b1, b2.
Clicking on Button b1 sends data back to activity A.
Clicking on Button b2 does not send any data.
Sending data
b1......clickListener
{
Intent resultIntent = new Intent();
resultIntent.putExtra("Your_key", "Your_value");
setResult(RES_CODE_A, resultIntent);
finish();
}
Not sending data
b2......clickListener
{
setResult(RES_CODE_B, new Intent());
finish();
}
The user clicks the back button
By default, the result is set with Activity.RESULT_CANCEL response code
Retrieve result
For that override onActivityResult method
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RES_CODE_A) {
// b1 was clicked
String x = data.getStringExtra("RES_CODE_A");
}
else if(resultCode == RES_CODE_B){
// b2 was clicked
}
else{
// The back button was clicked
}
}
I will post the new "way" with Android X in a short answer (because in some case you does not need custom registry or contract). If you want more information, see: Getting a result from an activity
Important: there is actually a bug with the backward compatibility of Android X so you have to add fragment_version in your Gradle file. Otherwise you will get an exception "New result API error : Can only use lower 16 bits for requestCode".
dependencies {
def activity_version = "1.2.0-beta01"
// Java language implementation
implementation "androidx.activity:activity:$activity_version"
// Kotlin
implementation "androidx.activity:activity-ktx:$activity_version"
def fragment_version = "1.3.0-beta02"
// Java language implementation
implementation "androidx.fragment:fragment:$fragment_version"
// Kotlin
implementation "androidx.fragment:fragment-ktx:$fragment_version"
// Testing Fragments in Isolation
debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
}
Now you just have to add this member variable of your activity. This use a predefined registry and generic contract.
public class MyActivity extends AppCompatActivity{
...
/**
* Activity callback API.
*/
// https://developer.android.com/training/basics/intents/result
private ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
#Override
public void onActivityResult(ActivityResult result) {
switch (result.getResultCode()) {
case Activity.RESULT_OK:
Intent intent = result.getData();
// Handle the Intent
Toast.makeText(MyActivity.this, "Activity returned ok", Toast.LENGTH_SHORT).show();
break;
case Activity.RESULT_CANCELED:
Toast.makeText(MyActivity.this, "Activity canceled", Toast.LENGTH_SHORT).show();
break;
}
}
});
Before new API you had :
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(MyActivity .this, EditActivity.class);
startActivityForResult(intent, Constants.INTENT_EDIT_REQUEST_CODE);
}
});
You may notice that the request code is now generated (and hold) by the Google framework.
Your code becomes:
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(MyActivity .this, EditActivity.class);
mStartForResult.launch(intent);
}
});
First you use startActivityForResult() with parameters in the first Activity and if you want to send data from the second Activity to first Activity then pass the value using Intent with the setResult() method and get that data inside the onActivityResult() method in the first Activity.
In your Main Activity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.takeCam).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent=new Intent(getApplicationContext(),TakePhotoActivity.class);
intent.putExtra("Mode","Take");
startActivity(intent);
}
});
findViewById(R.id.selectGal).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent=new Intent(getApplicationContext(),TakePhotoActivity.class);
intent.putExtra("Mode","Gallery");
startActivity(intent);
}
});
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
In Second Activity to Display
private static final int CAMERA_REQUEST = 1888;
private ImageView imageView;
private static final int MY_CAMERA_PERMISSION_CODE = 100;
private static final int PICK_PHOTO_FOR_AVATAR = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_take_photo);
imageView=findViewById(R.id.imageView);
if(getIntent().getStringExtra("Mode").equals("Gallery"))
{
pickImage();
}
else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.CAMERA}, MY_CAMERA_PERMISSION_CODE);
} else {
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(cameraIntent, CAMERA_REQUEST);
}
}
}
}
public void pickImage() {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, PICK_PHOTO_FOR_AVATAR);
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults)
{
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == MY_CAMERA_PERMISSION_CODE)
{
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
{
Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(cameraIntent, CAMERA_REQUEST);
}
else
{
Toast.makeText(this, "Camera Permission Denied..", Toast.LENGTH_LONG).show();
}
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CAMERA_REQUEST && resultCode == Activity.RESULT_OK) {
Bitmap photo = (Bitmap) data.getExtras().get("data");
imageView.setImageBitmap(photo);
}
if (requestCode == PICK_PHOTO_FOR_AVATAR && resultCode == Activity.RESULT_OK) {
if (data == null) {
Log.d("ABC","No Such Image Selected");
return;
}
try {
Uri selectedData=data.getData();
Log.d("ABC","Image Pick-Up");
imageView.setImageURI(selectedData);
InputStream inputStream = getApplicationContext().getContentResolver().openInputStream(selectedData);
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
Bitmap bmp=MediaStore.Images.Media.getBitmap(getContentResolver(),selectedData);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e){
}
}
}
You need to override Activity.onActivityResult():
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_CODE_ONE) {
String a = data.getStringExtra("RESULT_CODE_ONE");
}
else if(resultCode == RESULT_CODE_TWO){
// b was clicked
}
else{
}
}
I know how to listen to volume buttons in an activity. But can I do that in a background service? If yes, how to do that?
It is possible. Use code below (for newer Android versions, especially Marshmallow, see bottom of the answer):
public class SettingsContentObserver extends ContentObserver {
int previousVolume;
Context context;
public SettingsContentObserver(Context c, Handler handler) {
super(handler);
context=c;
AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
previousVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC);
}
#Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
int currentVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC);
int delta=previousVolume-currentVolume;
if(delta>0)
{
Logger.d("Ściszył!"); // volume decreased.
previousVolume=currentVolume;
}
else if(delta<0)
{
Logger.d("Zrobił głośniej!"); // volume increased.
previousVolume=currentVolume;
}
}
}
Then in your service onCreate register it with:
mSettingsContentObserver = new SettingsContentObserver(this,new Handler());
getApplicationContext().getContentResolver().registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, mSettingsContentObserver );
Then unregister in onDestroy:
getApplicationContext().getContentResolver().unregisterContentObserver(mSettingsContentObserver);
Note that this example judges by change of media volume, if you want to use other volume, change it!
UPDATE:
Above method supposedly doesn't work on Marshmallow, BUT there's much better way now since MediaSession was introduced! So first you have to migrate your code to MediaController/MediaSession pattern and then use this code:
private VolumeProviderCompat myVolumeProvider = null;
myVolumeProvider = new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, maxVolume, currentVolume) {
#Override
public void onAdjustVolume(int direction) {
// <0 volume down
// >0 volume up
}
};
mSession.setPlaybackToRemote(myVolumeProvider);
Somehow volume button presses are detected even with screen off (just be sure to register proper media button intent receiver if applicable for your platform!)
UPDATE 2 since GalDude requested some more info on getting media MediaSession/MediaController. Sorry, but since I stopped using Java it will be in Kotlin:
lateinit var mediaSession: MediaSessionCompat // you have to initialize it in your onCreate method
val kontroler: MediaControllerCompat
get() = mediaSession.controller // in Java it's just getController() on mediaSession
// in your onCreate/start method:
mediaSession = MediaSessionCompat(this, "YourPlayerName", receiver, null)
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
mediaSession.isActive = true
if (ratingIsWorking) // note: rating crashes on some machines you have to check it!
mediaSession.setRatingType(RatingCompat.RATING_5_STARS)
mediaSession.setCallback(object : MediaSessionCompat.Callback() {
...
// here you have to implement what happens with your player when play/pause/stop/ffw etc. is requested - see exaples elsewhere
})
// onDestroy/exit method:
mediaSession.isActive = false
mediaSession.release()
Unfortunately, this is another area of Android where there are like five different ways to "solve the problem", but most of them don't work very well. For my own sanity, I'll attempt to list all the different approaches below.
Solutions
1) MediaSession (from Service)
Answer by Denis Kniazhev: https://stackoverflow.com/a/43304591/2441655
Drawbacks:
Requires Android API level 21+ (Android 5.0+).
2) android.media.VOLUME_CHANGED_ACTION (from Service)
Answer by Nikhil: https://stackoverflow.com/a/44040282/2441655
Drawbacks:
Not an official part of the SDK: https://stackoverflow.com/a/8974510/2441655
Ignores first-press of volume-key (since it only shows the volume-bar).
Ignores volume-up key when at 100%, and volume-down key when at 0%.
3) ContentObserver (from Service)
Answer by ssuukk: https://stackoverflow.com/a/15292255/2441655 (first part)
Drawbacks:
Doesn't work in newer versions of Android: comment by dsemi
Ignores first-press of volume-key (since it only shows the volume-bar).
Ignores volume-up key when at 100%, and volume-down key when at 0%.
4) AudioManager.registerMediaButtonEventReceiver (from Service)
Answer by Joe: https://stackoverflow.com/a/11510564/2441655
Drawbacks:
Doesn't work on most roms: comment by elgui
5) onKeyDown (from Activity)
Answer by dipali: https://stackoverflow.com/a/21086563/2441655
Drawbacks:
Doesn't work if screen is off, in different app, etc.
6) dispatchKeyEvent (from Activity)
Answer by Maurice Gavin: https://stackoverflow.com/a/11462962/2441655
Drawbacks:
Doesn't work if screen is off, in different app, etc.
Conclusion
The solution I'm currently using is #1, because:
It's an official part of the SDK.
It is usable from a service. (ie. regardless of what app you're in)
It captures every volume-key press, regardless of current-volume/ui-state.
It works when the screen is off.
Let me know if you find any others -- or if you've found more drawbacks to some of them!
The AOSP Music app has a Service (MediaPlaybackService) that responds to volume key events by registering a BroadcastReceiver (MediaButtonIntentReceiver).
Here's the code snippet where it registers the receiver:
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
ComponentName rec = new ComponentName(getPackageName(),
MediaButtonIntentReceiver.class.getName());
mAudioManager.registerMediaButtonEventReceiver(rec);
Also, don't forget about manifest:
<receiver android:name="com.android.music.MediaButtonIntentReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
This works even if the Music app is not in the foreground. Isn't that what you want?
I was able to make it work on android 5+ devices using MediaSession. However,ContentObserver suggested by #ssuukk didn't work for me on both 4.4 and 7.0 devices (at least on ROMs that I've been testing on).
Here is a full example which works on android 5+.
Service:
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.v4.media.VolumeProviderCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
public class PlayerService extends Service {
private MediaSessionCompat mediaSession;
#Override
public void onCreate() {
super.onCreate();
mediaSession = new MediaSessionCompat(this, "PlayerService");
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 0) //you simulate a player which plays something.
.build());
//this will only work on Lollipop and up, see https://code.google.com/p/android/issues/detail?id=224134
VolumeProviderCompat myVolumeProvider =
new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, /*max volume*/100, /*initial volume level*/50) {
#Override
public void onAdjustVolume(int direction) {
/*
-1 -- volume down
1 -- volume up
0 -- volume button released
*/
}
};
mediaSession.setPlaybackToRemote(myVolumeProvider);
mediaSession.setActive(true);
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
super.onDestroy();
mediaSession.release();
}
}
In AndroidManifest.xml:
<application ...>
...
<service android:name=".PlayerService"/>
</application>
In your activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
startService(new Intent(this, PlayerService.class));
}
There are several things to be aware of:
It intercepts volume buttons completely so while this code is running you won't be able to adjust ring volume using volume buttons. This might be possible to fix, I just didn't try.
If you run the example as-is the volume buttons will remain controlled by the app even when the screen is off and the app has been removed from "Recent Apps" list. You'll have to go to Settings->Applications, find the app and force stop it to get volume buttons back.
Judging by the couple of other questions about this topic, no.
Other question 1,
Other question 2
Services simply do not receive KeyEvent callbacks.
You need to play blank sound from service then only you can listen to volume changes. Following worked for me
Steps
1. Put blank.mp3 in raw folder (Download from here)
2. Start media at onStartCommand()
private MediaPlayer mediaPlayer;
public MyService() {
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
........
mediaPlayer = MediaPlayer.create(this, R.raw.blank);
mediaPlayer.setLooping(true);
mediaPlayer.start();
.......
return START_STICKY;
}
3. You must choose to stop and release mediaplayer. It's better to do so in onDestroy()
#Override
public void onDestroy() {
mediaPlayer.stop();
mediaPlayer.release();
super.onDestroy();
}
4. Create Broadcast receiver that will listen for volume changes
int volumePrev = 0;
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if ("android.media.VOLUME_CHANGED_ACTION".equals(intent.getAction())) {
int volume = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_VALUE",0);
Log.i(TAG, "volume = " + volume);
if (volumePrev < volume) {
Log.i(TAG, "You have pressed volume up button");
} else {
Log.i(TAG, "You have pressed volume down button");
}
volumePrev = volume;
}
}
};
5. Register the broadcast receiver in onStartCommand()
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
.....
IntentFilter filter = new IntentFilter();
filter.addAction("android.media.VOLUME_CHANGED_ACTION");
registerReceiver(broadcastReceiver, filter);
....
return START_STICKY;
}
6. Unregister broadccast receiver in onDestroy()
#Override
public void onDestroy() {
.....
unregisterReceiver(broadcastReceiver);
.....
super.onDestroy();
}
That's all
This requires Lollipop (v5.0/API 21) or higher
My goal was to adjust system volume from a Service. Any action can be taken on press though.
public class VolumeKeyController {
private MediaSessionCompat mMediaSession;
private final Context mContext;
public VolumeKeyController(Context context) {
mContext = context;
}
private void createMediaSession() {
mMediaSession = new MediaSessionCompat(mContext, KeyUtil.log);
mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mMediaSession.setPlaybackState(new Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 0)
.build());
mMediaSession.setPlaybackToRemote(getVolumeProvider());
mMediaSession.setActive(true);
}
private VolumeProviderCompat getVolumeProvider() {
final AudioManager audio = mContext.getSystemService(Context.AUDIO_SERVICE);
int STREAM_TYPE = AudioManager.STREAM_MUSIC;
int currentVolume = audio.getStreamVolume(STREAM_TYPE);
int maxVolume = audio.getStreamMaxVolume(STREAM_TYPE);
final int VOLUME_UP = 1;
final int VOLUME_DOWN = -1;
return new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, maxVolume, currentVolume) {
#Override
public void onAdjustVolume(int direction) {
// Up = 1, Down = -1, Release = 0
// Replace with your action, if you don't want to adjust system volume
if (direction == VOLUME_UP) {
audio.adjustStreamVolume(STREAM_TYPE,
AudioManager.ADJUST_RAISE, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
else if (direction == VOLUME_DOWN) {
audio.adjustStreamVolume(STREAM_TYPE,
AudioManager.ADJUST_LOWER, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
setCurrentVolume(audio.getStreamVolume(STREAM_TYPE));
}
};
}
// Call when control needed, add a call to constructor if needed immediately
public void setActive(boolean active) {
if (mMediaSession != null) {
mMediaSession.setActive(active);
return;
}
createMediaSession();
}
// Call from Service's onDestroy method
public void destroy() {
if (mMediaSession != null) {
mMediaSession.release();
}
}
}
#venryx: Solution 1 no longer works in Android 12
#ssuukk: I can confirm #venryx's comment that SettingsContentObserver does not get triggered if the volume is already at min or max.
#bikram: I created a VolumeButtonHelper class that uses this approach. Although it does use an undocumented SDK feature, it still works in 2022. I have extensively researched this topic and this was the only solution I could find.
class VolumeButtonHelper(private var context: Context,
private var stream: Int? = null,
enabledScreenOff: Boolean)
{
companion object
{
const val VOLUME_CHANGE_ACTION = "android.media.VOLUME_CHANGED_ACTION"
const val EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE"
}
enum class Direction
{
Up,
Down,
Release
}
private lateinit var mediaPlayer: MediaPlayer
private var volumeBroadCastReceiver: VolumeBroadCastReceiver? = null
private var volumeChangeListener: VolumeChangeListener? = null
private val audioManager: AudioManager? =
context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
private var priorVolume = -1
private var volumePushes = 0.0
private var longPressReported = false
var doublePressTimeout = 350L
var buttonReleaseTimeout = 100L
var minVolume = -1
private set
var maxVolume = -1
private set
var halfVolume = -1
private set
var currentVolume = -1
private set
init
{
if (audioManager != null)
{
minVolume = audioManager.getStreamMinVolume(STREAM_MUSIC)
maxVolume = audioManager.getStreamMaxVolume(STREAM_MUSIC)
halfVolume = (minVolume + maxVolume) / 2
/*************************************
* BroadcastReceiver does not get triggered for VOLUME_CHANGE_ACTION
* if the screen is off and no media is playing.
* Playing a silent media file solves that.
*************************************/
if (enabledScreenOff)
{
mediaPlayer =
MediaPlayer.create(context,
R.raw.silence)
.apply {
isLooping = true
setWakeMode(context, PARTIAL_WAKE_LOCK)
start()
}
}
}
else
Log.e(TAG, "Unable to initialize AudioManager")
}
fun registerVolumeChangeListener(volumeChangeListener: VolumeChangeListener)
{
if (volumeBroadCastReceiver == null)
{
this.volumeChangeListener = volumeChangeListener
volumeBroadCastReceiver = VolumeBroadCastReceiver()
if (volumeBroadCastReceiver != null)
{
val filter = IntentFilter()
filter.addAction(VOLUME_CHANGE_ACTION)
context.registerReceiver(volumeBroadCastReceiver, filter)
}
else
Log.e(TAG, "Unable to initialize BroadCastReceiver")
}
}
fun unregisterReceiver()
{
if (volumeBroadCastReceiver != null)
{
context.unregisterReceiver(volumeBroadCastReceiver)
volumeBroadCastReceiver = null
}
}
fun onVolumePress(count: Int)
{
when (count)
{
1 -> volumeChangeListener?.onSinglePress()
2 -> volumeChangeListener?.onDoublePress()
else -> volumeChangeListener?.onVolumePress(count)
}
}
interface VolumeChangeListener
{
fun onVolumeChange(direction: Direction)
fun onVolumePress(count: Int)
fun onSinglePress()
fun onDoublePress()
fun onLongPress()
}
inner class VolumeBroadCastReceiver : BroadcastReceiver()
{
override fun onReceive(context: Context, intent: Intent)
{
if (stream == null ||
intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1) == stream)
{
currentVolume = audioManager?.getStreamVolume(STREAM_MUSIC) ?: -1
if (currentVolume != -1)
{
if (currentVolume != priorVolume)
{
if (currentVolume > priorVolume)
volumeChangeListener?.onVolumeChange(Up)
else if (currentVolume < priorVolume)
volumeChangeListener?.onVolumeChange(Down)
priorVolume = currentVolume
}
volumePushes += 0.5 // For some unknown reason (to me), onReceive gets called twice for every button push
if (volumePushes == 0.5)
{
CoroutineScope(Dispatchers.Main).launch {
delay(doublePressTimeout - buttonReleaseTimeout)
buttonDown()
}
}
}
}
}
private fun buttonDown()
{
val startVolumePushes = volumePushes
CoroutineScope(Dispatchers.Main).launch {
delay(buttonReleaseTimeout)
val currentVolumePushes = volumePushes
if (startVolumePushes != currentVolumePushes)
{
if (volumePushes > 2 && !longPressReported)
{
longPressReported = true
volumeChangeListener?.onLongPress()
}
buttonDown()
}
else
{
onVolumePress(volumePushes.toInt())
volumeChangeListener?.onVolumeChange(Release)
volumePushes = 0.0
longPressReported = false
}
}
}
}
}
Instantiate that class in a Service (with the appropriate wake lock):
class ForegroundService : Service()
{
private lateinit var volumeButtonHelper: VolumeButtonHelper
companion object
{
var wakeLock: WakeLock? = null
const val TAG = "VolumeButtonHelper"
const val ACTION_FOREGROUND_WAKELOCK = "com.oliverClimbs.volumeButtonHelper.FOREGROUND_WAKELOCK"
const val ACTION_FOREGROUND = "com.oliverClimbs.volumeButtonHelper.FOREGROUND"
const val WAKELOCK_TAG = "com.oliverClimbs.volumeButtonHelper:wake-service"
const val CHANNEL_ID = "Running in background"
}
override fun onBind(p0: Intent?): IBinder?
{
return null
}
override fun onCreate()
{
super.onCreate()
volumeButtonHelper = VolumeButtonHelper(this,
STREAM_MUSIC,
enabledScreenOff = true)
volumeButtonHelper.registerVolumeChangeListener(
object : VolumeButtonHelper.VolumeChangeListener
{
override fun onVolumeChange(direction: VolumeButtonHelper.Direction)
{
Log.i(TAG, "onVolumeChange: $direction")
}
override fun onVolumePress(count: Int)
{
Log.i(TAG, "onVolumePress: $count")
}
override fun onSinglePress()
{
Log.i(TAG, "onSinglePress")
}
override fun onDoublePress()
{
Log.i(TAG, "onDoublePress")
}
override fun onLongPress()
{
Log.i(TAG, "onLongPress")
}
})
}
#SuppressLint("WakelockTimeout")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int
{
super.onStartCommand(intent, flags, startId)
if (intent?.action == ACTION_FOREGROUND || intent?.action == ACTION_FOREGROUND_WAKELOCK)
startForeground(R.string.foreground_service_started,
Notification.Builder(this, CHANNEL_ID).build())
if (intent?.action == ACTION_FOREGROUND_WAKELOCK)
{
if (wakeLock == null)
{
wakeLock = getSystemService(PowerManager::class.java)?.newWakeLock(
PARTIAL_WAKE_LOCK,
WAKELOCK_TAG)
wakeLock?.acquire()
}
else
{
releaseWakeLock()
}
}
return START_STICKY
}
private fun releaseWakeLock()
{
wakeLock?.release()
wakeLock = null
}
override fun onDestroy()
{
super.onDestroy()
releaseWakeLock()
stopForeground(STOP_FOREGROUND_REMOVE)
volumeButtonHelper.unregisterReceiver()
}
}
Start the Service from your Activity:
class MainActivity : AppCompatActivity()
{
private var configurationChange = false
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (!configurationChange)
startService(Intent(ForegroundService.ACTION_FOREGROUND_WAKELOCK).setClass(this,
ForegroundService::class.java))
}
override fun onDestroy()
{
Log.d(TAG, "MainActivity: onDestroy")
configurationChange =
if (isChangingConfigurations)
true
else
{
stopService(Intent(this, ForegroundService::class.java))
false
}
super.onDestroy()
}
}
I have shared the full project at github.com/oliverClimbs/VolumeButtonDemo.
As for me accessibility service only works as expected
class KeyService : AccessibilityService() {
override fun onServiceConnected() {}
override fun onAccessibilityEvent(event: AccessibilityEvent) {}
override fun onKeyEvent(event: KeyEvent): Boolean {
when (event.keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> {
when (event.action) {
KeyEvent.ACTION_DOWN -> {
}
KeyEvent.ACTION_UP -> {
}
}
}
KeyEvent.KEYCODE_VOLUME_DOWN -> {
when (event.action) {
KeyEvent.ACTION_DOWN -> {
}
KeyEvent.ACTION_UP -> {
}
}
}
}
return super.onKeyEvent(event)
}
override fun onInterrupt() {}
}
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityFlags="flagRequestFilterKeyEvents"
android:canRequestFilterKeyEvents="true"
android:description="#string/app_name" />
<service
android:name=".KeySrvice"
android:exported="true"
android:label="#string/app_name"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<meta-data
android:name="android.accessibilityservice"
android:resource="#xml/key" />
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
</service>
inline fun <reified T : Service> Context.hasAccessibility(): Boolean {
var enabled = 1
try {
enabled = Secure.getInt(contentResolver, Secure.ACCESSIBILITY_ENABLED)
} catch (ignored: Throwable) {
}
if (enabled == 1) {
val name = ComponentName(applicationContext, T::class.java).flattenToString()
val services = Secure.getString(contentResolver, Secure.ENABLED_ACCESSIBILITY_SERVICES)
return services?.contains(name) ?: false
}
return false
}
if (!hasAccessibility<KeyService>()) {
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
}
Android doesn't document APIs on interacting with volume buttons in that case. So I guess the answer is no…
checkout Controlling Your App’s Volume and Playback ...This will help to solve your problem... multiple applications might want to listen for button presses from background, this may be the reason why KeyEvents can only be handled by Activities as they are the interface to the user pressing the keys.
Note: this only works for Activities, and not Services as the question states.
Depending on the context in which the callback is required an alternative solution might be available.
To be capable of detecting the volume button an Activity would need to override the dispatchKeyEvent function. For this to be present in multiple activities could could write a superclass containing the overridden function which is extended by all subsequent activities.
Here is the code for detecting Volume Up/Down key presses:
// Over-ride this function to define what should happen when keys are pressed (e.g. Home button, Back button, etc.)
#Override
public boolean dispatchKeyEvent(KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_DOWN)
{
switch (event.getKeyCode())
{
case KeyEvent.KEYCODE_VOLUME_UP:
// Volume up key detected
// Do something
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
// Volume down key detected
// Do something
return true;
}
}
return super.dispatchKeyEvent(event);
}
Been googling around this problem 10 years later, and I dug out this here:
https://stackoverflow.com/a/59064820
By providing an AccessibilityService, it is possible to listen to the volume buttons, outside the activity, even when the phone is locked, even before it is sent down to the specific apps (just tested it). Only downside: The user must activate this service manually in the settings.
MyService.java
public class MyService extends Service {
private BroadcastReceiver vReceiver;
#Override
public void onCreate() {
super.onCreate();
vReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
FileLog.e("Something just happens");
}
};
registerReceiver(vReceiver, new IntentFilter("android.media.VOLUME_CHANGED_ACTION"));
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
unregisterReceiver(vReceiver);
}
}
AndroidManifest.xml
<application>
...
<service android:name=".MyService" android:exported="true"/>
</application>
onCreate || onStartActivity
public void onCreate(){
....
startService(new Intent(this, MyService.class));
}
that solution like little bit same as Answer
but working on Android 33+
Okay lets say we have 2 activities such as mainActivity(mA) and secondActivity(sA)
mA is the user interface that takes name,number from the user.
Also there is a textview and a button.
The button leads to the sA.
sA has a calendar that lets you select a day.
You select the day and you come back to mA and the date you selected is written on the text view.
Its okay till here now the problem is, when i come back to mA my previous data that i got from the user is wiped away its empty except my text view that i got the date. How can i save the user data before going to other activity and when i come back they'll stay.
I expect to see my data from the first screen when i come back from the second activity.
EDIT:
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), calendarActivity.class);
startActivity(intent);
}
}
calendar.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
#Override
public void onSelectedDayChange(#NonNull CalendarView view, int year, int month, int dayOfMonth) {
String date = dayOfMonth + "/" + month + "/" + year;
intent = new Intent(getApplicationContext(), MainActivity.class);
intent.putExtra("date", date);
startActivity(intent);
}
});
String date = getIntent().getStringExtra("date");
tarih.setText(date);
Try using startActivityForResult.
Read the docs from the official Android Developer Docs
Essentially what you would do is to pass data through objects called Intents like so (in Kotlin for instance):
In your first activity:
private fun goToSelectDateActivity() {
val requestCode =324
val intent = Intent(this, CalendarActivity::class.java)
startActivityForResult(intent, requestCode)
}
In your Second activity
private fun passSelectedDateToPreviousActivity(selectedDate: Date) {
val data = Intent()
data.putExtra("DATE", selectedDate.toString());
setResult(Activity.RESULT_OK, data);
finish()
}
Back in your First Activity override the method onActivityResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if(resultCode != Activity.RESULT_OK) return
when(requestCode) {
324 -> { yourTextView.text = data.getStringExtra("DATE"); }
// Other result codes
else -> {}
}
}
These snippets are very quick and dirty snippets I'd suggest for instance that you have your requestCode as a static variable in Java or inside an attribute of the companion object for the Activity in Kotlin.
The Java equivalent would be
In your first activity:
private void goToSelectDateActivity() {
int requestCode =324;
Intent intent = new Intent(this, CalendarActivity.getClass());
startActivityForResult(intent, requestCode);
}
In your Second activity
private void passSelectedDateToPreviousActivity(Date selectedDate) {
Intent data = new Intent();
data.putExtra("DATE", selectedDate.toString());
setResult(Activity.RESULT_OK, data);
finish();
}
Back in your First Activity override the method onActivityResult
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if(resultCode != Activity.RESULT_OK) return;
switch(requestCode) {
case 324:
yourTextView.setText(data.getStringExtra("DATE"));
break;
// Other result codes
default: break;
}
}