Implementing registerForActivityResult from Fragment to dismiss Activity class - android

I have a calling like
There is a
-> FragmentMain which calls ActivityA
-> ActivityA consists of ActivityA_FragmentA
-> ActivityA_FragmentA calls ActivityB
-> ActivityB consists of ActivityB_FragmentA , ActivityB_FragmentB , ActivityB_FragmentC
ActivityB_FragmentC is final fragment on whose Ok button , I need to dismiss and return to FragmentMain
Current Implementation:
-> On dismiss of ActivityB_FragmentC updates LiveData to which is observed on ActivityB
-> This LiveData on ActivityB Used setResult(1000) in ActivityB and then finishActivity
-> Using onActivityResult() in ActivityA (result code used to compare 1000) {if result code is 1000 then dismiss ActivityA to show FragmentMain }
How to implement this using . registerForActivityResult()
I checked the document for it Doc Link
It says to use Observer and has explanation only for camera fetch .
I needed a simple call where I can pass result code back .. How can I achieve this ?
I want to use registerForActivityResult() in place of onActivityResult() in ActivityA
Trial 1 :
class MyLifecycleObserver(private val registry :
ActivityResultRegistry)
: DefaultLifecycleObserver {
lateinit var getContent : ActivityResultLauncher
override fun onCreate(owner: LifecycleOwner) {
getContent = registry.register("key", owner, ActivityResultContracts.GetContent()) { uri ->
// Handle the returned Uri
}
}
fun selectImage() {
getContent.launch("image/*")
} }
Am trying to pass result/ call from ActivityA_FragmentA to ActivityA but am able to do it using
ActivityResultContracts.StartActivityForResult()

Related

How to finish an activity inside onSuccess Realm callback?

I'm trying to call the finish() method inside the onSuccess() callback of a Realm transaction. The MainActivity opens another activity for the user to enter the data and create an object, this after clicking a button, my code looks something like this:
addButton.setOnClickListener {
val newObject = MyObject()
newObject.name = "Name"
realmThread.executeTransactionAsync(
{ transaction -> transaction.insert(newObject) },
{ _ -> finish() }
)
}
the transaction is made, but the activity doesn't close.
*Make a method to call after a response from AsyncTasc and use in it finish().
I guess it doesn't work for you because you call it in a separate thread *
So the solution was to specify that I'm implementing the onSuccess callback:
realmThread.executeTransactionAsync(
{ transaction ->
transaction.insert(newObject)
},
Realm.Transaction.OnSuccess {
finish()
}
)

Android Kotlin App crash after leaving a fragment that receives a callback from an API, when the callback didn't finish

I'm using retrofit2 and coroutines, I have a fragment A and B. I made the callback to fill a recyclerView at fragment B but if the user goes back to fragment A before the callback is finished, the app crashes because there is no B fragment to receive the data.
Of course, I can disable the back button until I receive the data from the callback, but I'm sure there is a better solution.
I found a navController class removeOnDestinationChangedListener, but I'm not sure how to use it or if it may help me with this problem.
Any Ideas?
main declarations inside the Fragment:
private lateinit var binding: FragmentShowCustomerBinding
var process: Job? = null
var productHistory: ArrayList<ProductHistory> = arrayListOf()
val producthistoryAdapter: ProductHistoryAdapter = ProductHistoryAdapter(arrayListOf())
This is how I call the service:
private fun loadCustomerActivity() {
process =
CoroutineScope(Dispatchers.IO).launch {
MainService.getCustomerOrders(customer.documentNumber, 1)
{ customerActivity ->
productHistory = customerActivity
CoroutineScope(Dispatchers.Main).launch {
binding.progressBarCustomerPurchaseHistory.visibility = View.INVISIBLE
binding.recyclerViewProductHistory.let {
producthistoryAdapter.loadCustomerhistory(productHistory)
producthistoryAdapter.notifyDataSetChanged()
}
}
}
}
}
and this how I cancel it just in case the user get out from the fragment before I get the customer history:
override fun onStop() {
super.onStop()
process?.cancel()
}

How to Pass finish() from Activity class as a function parameter (So the code can be reused in Kotlin)?

I have to use this peice of code twice in two different places in two different activites. No good programmer would willingly want to use same code in multiple places without reusing it.
//when back key is pressed
override fun onBackPressed() {
dialog.setContentView(twoBtnDialog.root)
twoBtnDialog.title.text = getString(R.string.warning)
twoBtnDialog.msgDialog.text = getString(R.string.backPressWarning)
twoBtnDialog.ok.text = getString(R.string.exit)
twoBtnDialog.cancel.text = getString(R.string.cancel)
twoBtnDialog.ok.setOnClickListener {
//do nav back
finish()
dialog.dismiss()
}
twoBtnDialog.cancel.setOnClickListener {
dialog.dismiss() //just do nothing
}
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog.show()
}
I can move it to one place, but the problem is I have to pass in the finish() function from Activity Class to close the calling activity.
My simple question is how can I resue it ? Or How can I pass this function (finish()) to a different class (which is in some other file).
Take a function type parameter in your method.
fun doBackPress(finish: () -> Unit) {
// you need to invoke the finish method when necessary
finish.invoke()
}
Then you need to call the method and have to pass the finish() method from any other activity or fragment method like bellow.
override fun onBackPressed() {
doBackPress { finish() }
}
You could make an interface and extension function, which I think is less messy than trying to pass everything you need as parameters to a function, because it communicates intent better and makes it harder to do something wrong.
interface MyDialogOwner {
val dialog: Dialog
val twoBtnDialog: MyDialogBinding
fun Activity.handleBackPress() {
//the exact same content you have in your function now.
}
}
// In Activity:
override fun onBackPressed() = handleBackPress()
Your Activities should implement the interface, using your existing properties for dialog and twoBtnDialog (just add override in front of their declarations).
I'm assuming twoBtnDialog is a view binding.

registerForActivityResult not working when second activity orientation changes

I am calling activity B from activity A using the ActivityResultLauncher and setting the result from activity B when the task is done. This works perfectly if orientation is not changed. The problem is when I change orientation from activity B and then setting the result, then registerForActivityResult of activity A is not called. Could someone let me know, what could be the issue?
Note: I do not face this issue if I am using startActivityForResult and onActivityResult. I changed this to ActivityResultLauncher as startActivityForResult became deprecated.
activity A sample code:
private lateinit var launcher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(layout)
setLauncherResult()
}
private fun setLauncherResult() {
launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
//Do operations here
}
}
//On button click starting activity B using launcher
val intent = Intent(activityA, activityB)
launcher.launch(intent)
}
activity B sample code:
//setting result
val intent = Intent()
//set intent extras
setResult(Activity.RESULT_OK, intent)
finish()
You can save the value in onSaveInstanceState() and use setResult in onRestoreInstanceState().
Try moving the registerForActivityResult assignment of launcher to onCreateView() (override it if you haven't already). Putting it here, I'm not getting the "is attempting to register while current state is STARTED" RuntimeException in OnCreateView() as it would with onResume() or later. The advantage of OnCreateView() over OnCreate() seems to be that the launcher variable will get re-set on theme day/night changes as well as just orientation changes.
Also see the Configuration change & Process death section here for more discussion.
I know the official dox say it's safe to set it before anything else in the class, but theme changes broke .launch(), and this location appears to work for me.
Everytime orientation changes it calls onCreate and it resets your views & Other variables like launcher are re-assigned. So you can follow the example shown in official document here: https://developer.android.com/training/basics/intents/result#launch
And pull your launcher assignment out of onCreate as following (This worked for me):
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
//do your stuff here
binding.tv.text = result.data?.getStringExtra("text")
}
}
companion object {
var number : Int = 0
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tv.setOnClickListener {
val intent = Intent(this, ActivityB::class.java)
startForResult.launch(intent)
}
}
}
If you don't want that onCreate should be called again when orientation changes. Then in your Manifest you can add configChanges:
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">

FragmentScenario of DialogFragment, onCreateDialog not called

I want to test DialogFragment using androidx.fragment:fragment-testing lib.
I call launchFragmentInContainer and moveToState(Lifecycle.State.RESUMED), but onCreateDialog is not called in this fragment.
#Test
fun `submit search - presenter state is changed`() {
val p: PinCatsPresenter = F.presenter(PinCatsPresenter.COMPONENT_ID)!!
launchFragmentInContainer<PinCatsDialog>().let { scenario ->
scenario
.moveToState(Lifecycle.State.RESUMED)
.onFragment { fragment ->
assertFalse(p.state.isFiltered)
fragment.dialog!!.findViewById<SearchView>(R.id.search_field).let {
it.isIconified = false
it.setQuery("ea", true)
}
awaitUi()
assertTrue(p.state.isFiltered)
assertEquals(3, p.state.count)
}
}
}
I debug the app, and ensured that onCreateDialog is called earlier than onResume, but in this test scenario onCreateDialog is not called, so fragment.dialog is null.
What should I call onFragmentScenario so my dialog would be created?
This is described in the official documentation. We need to call launchFragment instead of launchFragmentInContainer:
launchFragment<PinCatsDialog>().let { scenario ->
scenario
.moveToState(Lifecycle.State.RESUMED)
.onFragment { fragment ->
// Code here
}
}

Categories

Resources