Is there any default way to get the request code in ActivityResultContract?
I know about StartActivityForResult contract, which returns ActivityResult, but there is no requestCode, only resultCode.
I can do something like this, but maybe there is a better solution:
class StartActivityForResult : ActivityResultContract<ActivityInput, ActivityOutput?>() {
override fun createIntent(
context: Context,
input: ActivityInput
): Intent {
return input.data.apply { putExtra(requestCodeKey, input.requestCode) }
}
override fun parseResult(resultCode: Int, intent: Intent?): ActivityOutput? {
return if (intent == null || resultCode != Activity.RESULT_OK) null
else ActivityOutput(
// should never return default value
requestCode = intent.getIntExtra(requestCodeKey, -1),
resultCode = resultCode,
data = intent
)
}
override fun getSynchronousResult(
context: Context,
input: ActivityInput?
): SynchronousResult<ActivityOutput?>? {
return if (input == null) SynchronousResult(null) else null
}
companion object {
const val requestCodeKey = "requestCodeKey";
}
}
data class ActivityInput(val requestCode: Int, val data: Intent)
data class ActivityOutput(val requestCode: Int,
val resultCode: Int,
val data: Intent
)
Related
I want to use a preference summary to show the preference's current value, so I want to update the summary whenever the preference is changed. The preference in question is a storage location, chosen interactively by the user via an intent, using Android's Storage Access Framework. I've been beating my head over this for hours, trying all sorts of things found in SO threads, but I just can't figure out what combination of setSummary,findPreference, onSharedPreferenceChanged, onSharedPreferenceChangeListener, invoked in which class, I need.
My code currently looks something like this:
const val REQUEST_TARGET_FOLDER = 4
class SettingsActivity : AppCompatActivity() {
private lateinit var prefs: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
}
}
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
// from: https://stackoverflow.com/questions/63575398/how-to-correctly-receive-and-store-a-local-directory-path-in-android-preferences
val targetDirPreference: Preference? = findPreference("export_dir")
targetDirPreference?.setOnPreferenceClickListener {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
activity?.startActivityForResult(intent, REQUEST_TARGET_FOLDER)
true
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
// from: https://stackoverflow.com/questions/34331956/trying-to-takepersistableuripermission-fails-for-custom-documentsprovider-via
if (requestCode == REQUEST_TARGET_FOLDER && resultCode == RESULT_OK && intent != null) {
val treeUri = intent.data
if (treeUri != null) {
// do stuff
}
with(prefs.edit()) {
putString("export_dir", intent.data.toString())
apply()
}
}
}
}
This is the preference involved:
<Preference
android:key="export_dir"
android:title="Export to directory:" />
Can someone please help me figure out what to do to set / update the preference's summary when the user selects a directory? (The directory selection part itself currently works.)
Since you are manually changing the setting outside of the Preference itself, you cannot do this with a SummaryProvider. Instead, you must manually change the summary both (1) when the summary first appears and (2) when you manually change the preference value and commit it. (You could use an OnSharedPreferenceChangeListener to do the second step automatically, but that's more complicated.)
So, create a function that updates its summary and call it in both places: in onCreatePreferences and in onActivityResult where you are setting the value.
By the way you can use preferences.edit { ... } extension function instead of with(preferences.edit) { ... ; apply() } for simpler code.
class SettingsFragment : PreferenceFragmentCompat() {
private val TARGET_DIR_KEY = "export_dir"
private val prefs by lazy { preferenceManager.sharedPreferences }
private val targetDirPreference: Preference by lazy {
findPreference<Preference>(TARGET_DIR_KEY) ?: error("Missing target directory preference!")
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
targetDirPreference.setOnPreferenceClickListener {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, REQUEST_TARGET_FOLDER)
true
}
updateTargetDirPreferenceSummary()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
// from: https://stackoverflow.com/questions/34331956/trying-to-takepersistableuripermission-fails-for-custom-documentsprovider-via
if (requestCode == REQUEST_TARGET_FOLDER && resultCode == RESULT_OK && intent != null) {
val treeUri = intent.data
if (treeUri != null) {
// do stuff
}
prefs.edit {
putString(TARGET_DIR_KEY, intent.data.toString())
}
updateTargetDirPreferenceSummary()
}
}
private fun updateTargetDirPreferenceSummary() {
targetDirPreference.summary = prefs.getString("feedback", "")
}
}
OR, if you want to solve this in a way that provides cleaner code in your Fragment, you can create a subclass of Preference that helps manage the changing of the setting value and internally uses a SummaryProvider mechanism to automatically update itself.
class ManualStringPreference #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
): Preference(context, attrs) {
init {
setSummaryProvider { getPersistedString("") }
}
var value: String = ""
set(inValue) {
if (inValue != field) {
field = inValue
persistString(inValue)
notifyChanged()
}
}
override fun onSetInitialValue(defaultValue: Any?) {
value = getPersistedString(defaultValue as? String ?: "")
}
}
You need to set this as your preference type in your XML.
Then your Fragment looks like this. Notice that you change the SharedPreferences value through the Preference subclass you created.
class SettingsFragment : PreferenceFragmentCompat() {
private val prefs by lazy { preferenceManager.sharedPreferences }
private val targetDirPreference: ManualStringPreference by lazy {
findPreference<ManualStringPreference>("export_dir") ?: error("Missing target directory preference!")
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
targetDirPreference.setOnPreferenceClickListener {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, REQUEST_TARGET_FOLDER)
true
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
// from: https://stackoverflow.com/questions/34331956/trying-to-takepersistableuripermission-fails-for-custom-documentsprovider-via
if (requestCode == REQUEST_TARGET_FOLDER && resultCode == RESULT_OK && intent != null) {
val treeUri = intent.data
if (treeUri != null) {
// do stuff
}
targetDirPreference.value = intent.data.toString()
}
}
}
I am trying to get image file from external storage using new activity result Api.
My contract always return null from else branch (inside when). Why?
class GetImageContract : ActivityResultContract<Unit, Bitmap>() {
override fun createIntent(context: Context, input: Unit?): Intent {
val intent = Intent(ACTION_GET_CONTENT)
intent.type = "image/*"
return intent
}
override fun parseResult(resultCode: Int, intent: Intent?): Bitmap? {
return when {
resultCode != Activity.RESULT_OK -> null
else -> intent?.extras?.get("data") as? Bitmap
}
}
}
I can scan a barcode successfully but I somehow cannot get the result. I found out that since I am calling the barcode scanner in a fragment, I need change my code to use this:
class AddIerFragment : Fragment() { ....
val intentIntegrator = IntentIntegrator.forFragment(this)
....
}
The problem is, the "this" keyword is not allowed because it gives me an error of
Type mismatch
Requred: Fragment
Found AddIerFragment
See image below.
I have this code in the fragment
companion object {
#JvmStatic
fun newInstance(param1: String, param2: String) =
AddIerFragment().apply {
arguments = Bundle().apply {
}
}
private const val CAMERA = 1
private const val GALLERY = 2
private const val SCAN = 3
}
R.id.button_atgScan -> {
Dexter.withContext(context!!).withPermissions(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
)
.withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let{
if(report!!.areAllPermissionsGranted()) {
intentIntegrator.setDesiredBarcodeFormats(IntentIntegrator.ONE_D_CODE_TYPES)
intentIntegrator.setPrompt("Scan a barcode")
intentIntegrator.setCameraId(0)
intentIntegrator.setBeepEnabled(false)
intentIntegrator.setBarcodeImageEnabled(true)
intentIntegrator.setOrientationLocked(false)
intentIntegrator.initiateScan()
}
}
}
override fun onPermissionRationaleShouldBeShown(
p0: MutableList<PermissionRequest>?,
p1: PermissionToken?
) {
showRationalDialogForPermission()
}
}).onSameThread().check()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == GALLERY) {
data?.let {
val selectedPhotoUri = data.data
file = File(getPath(selectedPhotoUri))
gView!!.iv_ier_image.setImageURI(selectedPhotoUri)
}
} else if (requestCode == CAMERA) {
data?.extras?.let {
val thumbnail: Bitmap =
data.extras!!.get("data") as Bitmap
file = savebitmap(thumbnail)!!
gView!!.iv_ier_image.setImageBitmap(thumbnail)
}
}
val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
if (result != null) {
if (result.contents == null) {
Log.i("TAG", "NOTHING")
} else {
Log.i("TAG", result.contents)
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
For support or androidx fragments you should use IntentIntegrator.forSupportFragment(this)
AddIerFragment must not be a subclass of the correct Fragment. At the top of its file, make sure you have imported androidx.fragment.app.Fragment instead of android.app.Fragment. And assuming you're using zxing-android-embedded, make sure you call forSupportFragment, not forFragment.
I am currently listening for Google Play services broadcasted action SmsRetriever.SMS_RETRIEVED_ACTION to check whether OTP SMS was retrieved. First, I start SmsRetriever:
SmsRetriever.getClient(context).startSmsUserConsent(null)
My BroadcastReceiver looks like this and works perfectly fine in real scenario:
object : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
try {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val smsRetrieverStatus = extras?.get(SmsRetriever.EXTRA_STATUS) as? Status
when (smsRetrieverStatus?.statusCode) {
CommonStatusCodes.SUCCESS -> {
extras.getParcelable<Intent>(SmsRetriever.EXTRA_CONSENT_INTENT)?.let {
myOtpFragment.startActivityForResult(it, SMS_REQUEST_CODE)
}
}
}
}
} catch (e: Exception) {
Timber.e(e)
}
}
I want to somehow mock this onReceive method, in order to test and verify that my onActivityResult code works as expected and autofills EditText with retrieved OTP.
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?
) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == SMS_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val otp = data?.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE) ?: ""
et_otp?.let {
it.setText(otp)
adjustOtpSelection(it)
}
}
Any tips on how could I do that with Espresso and Mockito?
I want to get some info like artist, duration and title from a selected mp3 file.But I seem to get something wrong. I just get some random numbers and that is not what I hoped for. I am thankful for every help I get.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_music)
SelectTrack()
}
private fun SelectTrack() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "audio/mpeg"
startActivityForResult(intent, 0)
}
var selectedTrackUri: Uri? = null
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 0 && resultCode == Activity.RESULT_OK && data !=null) {
selectedTrackUri = data.data
val title = MediaMetadataRetriever.METADATA_KEY_TITLE.toString()
val duration = MediaMetadataRetriever.METADATA_KEY_DURATION.toString()
val artist = MediaMetadataRetriever.METADATA_KEY_ARTIST.toString()
AddTrackName_txt.text = title
AddArtistName_txt.text = artist
AddTrackLength_txt.text = duration
//Picasso.get().load(album).into(AddTrackPic_View)
}
}
}
The problem is in the way you retrieve the metadata of the file, you are assigning the values of the keys used to extract the metadata, not reading the actual data from the MediaMetadataRetriever.
Example
private fun selectTrack() {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply { type = "audio/mpeg" }
startActivityForResult(intent, RC_MEDIA_FILE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
if (requestCode == RC_MEDIA_FILE && resultCode == Activity.RESULT_OK && intent != null) {
val mmr = MediaMetadataRetriever()
mmr.setDataSource(this, intent.data)
val title = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)
val artist = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST)
val duration = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
Log.d("MP3", "title=$title, artist=$artist, duration=$duration")
}
}
companion object {
const val RC_MEDIA_FILE = 100
}
Output
D/MP3: title=Sweet Child O´Mine, artist=Guns N' Roses, duration=356444