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+
Related
From the Flutter side, using the PlatformChannel, I am navigating to an Android Java activity, and doing some processes.
The activity successfully opens and I'm able to do the functionality and have the final result of it.
How may I navigate back to the Flutter side to a specific page and pass a value?
P.S.: without going back to the same page and then redirecting to the
next page.
On the Flutter side:
I have these variables
/// Filters Method Channel
final filtersChannel = const MethodChannel('flutter.native/filters');
/// Filters Method Channel
final filtersResultChannel = const MethodChannel("flutter.native/result_filters");
I have a floatingActionButton with this function which invokes a MethodChannel
Future<void> startNewActivity() async {
try {
await filtersChannel.invokeMethod('open_filters');
} on PlatformException catch (e) {
debugPrint("Failed to Invoke: '${e.message}'.");
}
}
On the MainActivity.java
On the protected void onCreate(#Nullable Bundle savedInstanceState) function, I'm starting an activity which has the AR video recording like this:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, FiltersActivity.class);
startActivity(intent);
}
On the FiltersActivity.java
On the public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) function
I’m defining and invoking my two channels:
The flutter.native/result_filters channel which builds the UI and
the functionality.
The flutter.native/filters channel which returns the final result.
Here:
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
String resultFiltersChannelIdentifier = "flutter.native/result_filters";
filtersResultChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), resultFiltersChannelIdentifier);
String filtersChannelIdentifier = "flutter.native/filters";
MethodChannel filtersChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), filtersChannelIdentifier);
filtersChannel.setMethodCallHandler(this::filtersMethodCallHandler);
}
Then, the flutter.native/filters displays the UI using the filtersMethodCallHandler function. Here:
private void filtersMethodCallHandler(MethodCall methodCall, MethodChannel.Result result) {
if (methodCall.method.equals("open_filters")) {
openUI();
} else {
result.notImplemented();
}
}
In the openUI function, I'm assigning the record button a function, here:
recordButton.setOnClickListener(this::toggleRecording);
And here's the toggleRecording function:
public void toggleRecording(View unusedView) {
boolean recording = videoRecorder.onToggleRecord();
if (recording) {
recordButton.setImageResource(R.drawable.round_stop);
Toast.makeText(this, "Started Recording", Toast.LENGTH_SHORT).show();
} else {
recordButton.setImageResource(R.drawable.round_videocam);
Toast.makeText(this, "Recording Stopped", Toast.LENGTH_SHORT).show();
videoPath = videoRecorder.getVideoPath().getAbsolutePath();
Toast.makeText(this, "Video saved: " + videoPath, Toast.LENGTH_SHORT).show();
Log.d(TAG, "Video saved: " + videoPath);
// Send notification of updated content.
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.TITLE, "Sceneform Video");
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
values.put(MediaStore.Video.Media.DATA, videoPath);
getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
filtersResultChannel.invokeMethod("filters_result", videoPath);
finish();
}
}
As shown above, I'm invoking the filters_result method for the filtersResultChannel channel and I'm adding the videoPath to it.
And then, I'm calling the finish(); method to close the FiltersActivity and return back to the MainAvtivity which successfully returns me to the Flutter page!
BACK to the Flutter side,
I'm listening to the filtersResultChannel like this:
#override
void initState() {
super.initState();
filtersResultChannel.setMethodCallHandler(_filtersResultHandler);
}
Future _filtersResultHandler(MethodCall methodCall) async {
if (methodCall.method == "filters_result") {
final videoPath = methodCall.arguments;
if (videoPath != null && videoPath.length >= 0) {
SchedulerBinding.instance.addPostFrameCallback((_) {
debugPrint("YES YES YES => $videoPath");
setState(() {
reportStatus = videoPath;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VideoShow(clipPath: videoPath),
),
);
});
});
}
return null;
} else {
return null;
}
}
As shown above, I have a debugPrint statement, this statement prints the returned videoPath from the filtersResultChannel
<--------->
THE PROBLEM
<--------->
Even though I'm successfully getting the videoPath value and successfully returning back to the Flutter page, I'm NOT able to use it!!
The setState(); doesn't update the UI NOR navigate to the next screen, the VideoShow screen!
HOW MAY I FIX SUCH AN ISSUE?
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.
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.
I am working on a Android Streaming App using MediaBrowserServiceCompat based on this great article https://medium.com/androiddevelopers/mediabrowserservicecompat-and-the-modern-media-playback-app-7959a5196d90. My app works pretty good except when phone goes to Doze Mode. I have been looking over internet and see some possible solutions to keep my service alive. One of those is running the UI and the Music Service in different Process. However since I am using the MediaBrowserService I don't know how to start the service from UI using AIDL.
This is how I connected the service with the Main Activity in the same process:
MainActivity.java
private class MediaBrowserConnection extends MediaBrowserHelper {
private MediaBrowserConnection(Context context) {
super(context, MusicService.class);
}
#Override
protected void onConnected(#NonNull MediaControllerCompat mediaController) {
mMediaController = mediaController;
}
#Override
protected void onChildrenLoaded(#NonNull String parentId,
#NonNull List<MediaBrowserCompat.MediaItem> children) {
super.onChildrenLoaded(parentId, children);
}
}
MediaBrowserHelper.java
public MediaBrowserHelper(Context context,
Class<? extends MediaBrowserServiceCompat> serviceClass) {
mContext = context;
mMediaBrowserServiceClass = serviceClass;
mMediaBrowserConnectionCallback = new MediaBrowserConnectionCallback();
mMediaControllerCallback = new MediaControllerCallback();
mMediaBrowserSubscriptionCallback = new MediaBrowserSubscriptionCallback();
}
public void onStart() {
if (mMediaBrowser == null) {
mMediaBrowser =
new MediaBrowserCompat(
mContext,
new ComponentName(mContext, mMediaBrowserServiceClass),
mMediaBrowserConnectionCallback,
null);
mMediaBrowser.connect();
}
Log.d(TAG, "onStart: Creating MediaBrowser, and connecting");
}
public void onStop() {
if (mMediaController != null) {
mMediaController.unregisterCallback(mMediaControllerCallback);
mMediaController = null;
}
if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
mMediaBrowser.disconnect();
mMediaBrowser = null;
}
resetState();
Log.d(TAG, "onStop: Releasing MediaController, Disconnecting from MediaBrowser");
}
Anyone knows how to do this or is there any other reliable solution to keep streaming alive?
Thanks
I made an app as a service which runs in background. This app is basically a battery alarm. It works fine but the only problem is that when this service is running it also displays this app in the active application task manager. So when I exit this app it stops that service as well. So what I want is to only stop this service when the user unchecks the box in the app settings. If it is checked then it should not be stopped even if it is closed in active application task manager.
How can I stop showing my app in task manager?
I think I should provide code over here This is my service class
public class BatteryService extends Service {
Notify notification = new Notify();
BatteryAlarm alarm = new BatteryAlarm();
private MediaPlayer mMediaPlayer;
boolean flag = false;
#Override
public IBinder onBind(Intent arg0) {
return null;
}
//method to start service
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
notification.initNotification(this, false);
this.registerReceiver(this.mBatInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
return START_STICKY;
}
//Broadcast receiver to get battery info
private BroadcastReceiver mBatInfoReceiver = new BroadcastReceiver(){
#Override
public void onReceive(Context c, Intent i) {
//notification.initNotification(c);
int level = i.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
int plugged = i.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
SharedPreferences getAlarm = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
String alarms = getAlarm.getString("ringtones", "content://media/internal/audio/media/45"); // /system/media/audio/ringtones/ANDROMEDA.ogg , content://media/internal/audio/media/45
Uri uri = Uri.parse(alarms);
if(plugged == 2) {
if(level == 100) {
if(uri != null) {
if(flag == false) {
playAlarm(c, uri);
notification.initNotification(c, true);
Toast.makeText(c, "Battery charge is completed. Unplug your mobile phone!", Toast.LENGTH_LONG).show();
flag = true;
}
}
}
} else if (plugged == 0) {
if(uri != null) {
stopAlarm();
}
notification.cancelNotification(c);
//Toast.makeText(c, "Mobile is unplugged", Toast.LENGTH_LONG).show();
}
}
};
//play alarm method
private void playAlarm(Context c, Uri uri) {
mMediaPlayer = new MediaPlayer();
try {
mMediaPlayer.reset();
mMediaPlayer.setDataSource(getBaseContext(), uri);
final AudioManager audioManager = (AudioManager) c.getSystemService(Context.AUDIO_SERVICE);
if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
mMediaPlayer.prepare();
mMediaPlayer.start();
}
} catch (Exception ex) {
ex.printStackTrace();
onDestroy();
}
}
//method to stop playing alarm
private void stopAlarm() {
mMediaPlayer.stop();
flag = false;
}
//method to stop service
public void onDestroy() {
super.onDestroy();
notification.cancelNotification(this);
unregisterReceiver(this.mBatInfoReceiver);
stopAlarm();
Toast.makeText(this, "Service Stopped", Toast.LENGTH_LONG).show();
}
}
This is my main activity
public class BatteryNotify extends PreferenceActivity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.xml.prefs);
addPreferencesFromResource(R.xml.prefs);
SharedPreferences getCB = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
boolean cb = getCB.getBoolean("checkbox", true);
final CheckBoxPreference checkboxPref = (CheckBoxPreference) getPreferenceManager().findPreference("checkbox");
if(cb == true) {
startService(new Intent(getBaseContext(), BatteryService.class));
} else if(cb == false) {
stopService(new Intent(getBaseContext(), BatteryService.class));
}
checkboxPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
if(newValue.toString().equals("true")) {
startService(new Intent(getBaseContext(), BatteryService.class));
} else {
stopService(new Intent(getBaseContext(), BatteryService.class));
}
return true;
}
});
}
}
and here is my menifest file
<uses-sdk android:minSdkVersion="10" />
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name" >
<activity
android:name=".BatteryNotify"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".BatteryService"></service>
</application>
The best way to do this would be to create a BroadcastReceiver, register it in the manifest with the appropriate intent-filters and when it receives one it starts the Service or Activity to perform whatever task you need.
EDIT:
Create your BroadcastReceiver as a separate class and register it in the manifest. When it receives a battery event, create a PendingIntent to start the Service. That way it doesn't matter if your app isn't running. It will be started for you.
How can I stop showing my app in task manager?
You can't, for obvious security reasons.