Updating a preference summary in Android when the user sets it - android

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()
}
}
}

Related

Why do I get a type mismatch when using "this" on fragment Kotlin

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.

Android SMS Verification API result code is always 0

I implemented the Android SMS Verification API on activities and fragments on the same project and it went well. My problem is with fragments in tabs. No matter what I do, onActivityResult always returns result code 0 when "Allow" is pressed. Here's my lot of code which was also implemented and tested to be working on the activities and fragments.
override fun onStart() {
super.onStart()
registerToSmsBroadcastReceiver()
}
override fun onStop() {
myActivity.unregisterReceiver(smsBroadcastReceiver)
super.onStop()
}
private fun startSmsUserConsent() {
SmsRetriever.getClient(myActivity).also {
it.startSmsUserConsent(null)
.addOnSuccessListener {
Log.d("LISTENING", "SUCCESS")
}
.addOnFailureListener {
Log.d("LISTENING", "FAIL")
}
}
}
private fun registerToSmsBroadcastReceiver() {
smsBroadcastReceiver = SmsBroadcastReceiver().also {
it.smsBroadcastReceiverListener =
object : SmsBroadcastReceiver.SmsBroadcastReceiverListener {
override fun onSuccess(intent: Intent?) {
intent?.let { context -> startActivityForResult(context, REQ_USER_CONSENT) }
}
override fun onFailure() {
}
}
}
val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
myActivity.registerReceiver(smsBroadcastReceiver, intentFilter)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQ_USER_CONSENT -> {
if ((resultCode == Activity.RESULT_OK) && (data != null)) {
val message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE)
val code = message?.let { fetchVerificationCode(it) }
otpField.setText(code)
}
}
}
}
private fun fetchVerificationCode(message: String): String {
return Regex("(\\d{6})").find(message)?.value ?: ""
}
Oh, and startSmsUserConsent() is called whenever I call for the API to send an OTP. Anything I missed?
Thank you.
I solved the issue by handling the OTP SMS Retrieval on the activity instead of on the fragment, then passed on the fragment if need be.

I don't want to lose photo on image view button when come back from other app

Users can show their photo on the imageView button. Codes are given below.
The problem is, savedInstanceState returns null since photo on the imageView is obtained in the onActivityResult function.
Therefore, if users click on btnRegistration and come back to this app again, they lose photo on the imageView.
Could you please help, how to edit these codes to solve this problem
private var iv_crop: ImageView = null
public var tmpResultUri: Uri?=null
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
val cimg = CropImage.getActivityResult(data)
iv_crop.setImageURI(cimg.uri)
val resultUri = cimg.uri
tmpResultUri = resultUri
}}
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
var strResultUri: String?= null
strResultUri = savedInstanceState.getString(strResultUri)
// var drawable: BitmapDrawable = iv_crop.getDrawable() as BitmapDrawable
//var bitmapImgCropped = drawable.getBitmap()
}
else {
iv_crop.setOnClickListener {
CropImage.activity().start(this) // <== Starts a new activity here.
}
}
btnRegistration?.setOnClickListener {
val intent = Intent()
intent.setClassName( "com.mylab.myApp","com.mylab.myApp.MainActivity")
startActivity(intent) // <== Starts a new activity here.
finish()}
}
override fun onSaveInstanceState(outState:Bundle ) {
outState.run{
outState.putString(tmpResultUri.toString(), tmpResultUri.toString())
}
super.onSaveInstanceState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
super.onRestoreInstanceState(savedInstanceState!!)
savedInstanceState.run {
val strtmpResultUri = getString(tmpResultUri.toString())
}
}
You need to store your image URI using a static key. Something like this.
companion object {
private const val ARG_IMAGE_URI = "imageuri"
}
Then when you save and retrieve your URI, use this value as your key and not the uri.
override fun onSaveInstanceState(outState:Bundle ) {
outState.putString(ARG_IMAGE_URI, tmpResultUri.toString())
super.onSaveInstanceState(outState)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
val strResultUri: String?= savedInstanceState.getString(ARG_IMAGE_URI)
}
}

how to use repository or viewmodel method in onAvitivityResult

I am trying to follow MVVM pattern in android project, I have to call network api onAcitivityResult method. According to MVVM repository should interact with network calls and viewmodel should do the interaction between Activity and repository. So if I have to access network api then I have to call viewmodel method in onActivityResult. This is my onActivityResult method:
class Profile : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val networkConnectionInterceptor = NetworkConnectionInterceptor(this)
val api = Api.invoke(networkConnectionInterceptor)
val repository = UserRepository(api)
val factory = ProfileViewModelFactory(repository, Photo(""))
val viewModel = ViewModelProvider(this, factory).get(ProfileViewModel::class.java)
val binding: ActivityProfileBinding =
DataBindingUtil.setContentView(this, R.layout.activity_profile)
binding.viewmodel = viewModel
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode == Activity.RESULT_OK) {
if (data != null) {
when (requestCode) {
ImageIntent.CAMERA_REQUEST -> {
/* I want to call Viewmodel method here */
viewmodel.onProfileImageUpload(ImageIntent.imageUri)
}
}
}
} else if (resultCode == Activity.RESULT_CANCELED) {
toast("Image upload cancelled !")
}
}
This is the method defined in my viewmodel :
fun onProfileImageUpload(uri: Uri) {
Coroutines.main{
try {
val imageResponse = repository.updateProfileAvatar(
ImageUtil.getImageForUpload(
uri,
"avatar"
)
)
Log.d("avatar_resonse", "$imageResponse")
} catch(e : Exception) {}
}
}
The problem is I have to initialize the viewmodel in Activity onCreate method so I cannot have the viewmodel instance in the onActivityResult. How do I make a network call from there ?
try this
if(requestCode==your code){
if(resultCode==Activity.RESULT_OK){
if(data!=null){
// your api and if you calling image from start activity result get it from data
}
}
}

How do you pass data from current activity to Previous activity

I am trying to pass a value from Activity 3 to Activity 2 but I am getting null Value. If I click Back button its going to previous Activity but value is null. Added the suggested Approach code below. but still not able to get the results.
Suggested Approach:
Activity :3
override fun onBackPressed() {
sendDataBackToPreviousActivity()
super.onBackPressed()
}
private fun sendDataBackToPreviousActivity()
{
val navBarTitle21=intent.getStringExtra(TestProjectMenuViewHolder.TEST_TITLE_NAME)
val intent=Intent().apply { putExtra("ReturnMessage",navBarTitle21)}
setResult(Activity.RESULT_OK,intent)
}
Activity:2
Main Class:
companion object {
const val START_ACTIVITY_3_REQUEST_CODE = 0
}
val intent=Intent(this,TestProjectMenuDetail::class.java)
startActivityForResult(intent, START_ACTIVITY_3_REQUEST_CODE)
Declared outside Main Class:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == START_ACTIVITY_3_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
val message = data!!.getStringExtra("ReturnMessage")
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
println("Message Value: $message")
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
First Approach:
Activity:3
val navBarTitle= intent.getStringExtra(TestMenuViewHolder.TEST_TITLE_KEY)
supportActionBar?.title=navBarTitle//Something Like "StackOverFlow". THis is for back Button
TestMenuDetail:
val navBarTitle2=intent.getStringExtra(TestMenuViewHolder.TEST_TITLE_NAME)
val TestVar=Intent(this#TestMenuDetail,TestMenuList::class.java)
intent.putExtra("TestVar2",navBarTitle2)
println("Test Value $navBarTitle2")//Test Value Hello
Activity:2
TestMenuList:
val navBarTitle3=intent.getStringExtra("TestVar2")
println("Helllo Test: $navBarTitle3")//Helllo Test: null
You should use startActivityForResult API to achieve your task.
Activity2.kt
class Activity2 : AppCompatActivity() {
companion object {
const val START_ACTIVITY_3_REQUEST_CODE = 0
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity2)
// Start Activity3
val intent = Intent(this, Activity3::class.java)
startActivityForResult(intent, START_ACTIVITY_3_REQUEST_CODE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == START_ACTIVITY_3_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
val message = data!!.getStringExtra("message")
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
}
Activity3.kt
class Activity3 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity3)
}
override fun onBackPressed() {
sendDataBackToPreviousActivity()
super.onBackPressed()
}
/**
* Send data back to previous activity which start this one, you can call this method when users press on back key
* or when users press on a view (button, image, etc) on this activity.
*/
private fun sendDataBackToPreviousActivity() {
val intent = Intent().apply {
putExtra("message", "This is a message from Activity3")
// Put your data here if you want.
}
setResult(Activity.RESULT_OK, intent)
}
}
Ok here is what I would do:
Override the onBackPressed method and then pass the variable value inside the method with an Intent. And in activity 2 receive the value from activity 3.
In activity 3
#override
public void onBackPressed (){
Intent intent = new Intent(getApplicationContext(), Activity2.class);
intent.put("value_key", value);
startActivity(intent);
}
Receive value in activity 2
getIntent.getValue("value_key");
Don't forget to check the syntax, I just wrote it from my phone. Hope it helps!
You can always use SharedPreferences, and then clear them, after receiving data in previous activity. It's 100% effective way. Put it:
val sharedPreference = getSharedPreferences("prefs name",Context.MODE_PRIVATE)
var editor = sharedPreference.edit()
editor.putString("your value name","value")
editor.commit()
and get it:
sharedPreference.getString("your value name","default value")
but of course you have to open preferences again in previous activity ;)
val sharedPreference = getSharedPreferences("prefs name",Context.MODE_PRIVATE)

Categories

Resources