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()
}
)
Related
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()
I have the following function that can take an activity as an argument and when I call it from an Activity it works perfectly. Now that I want to call this function from a fragment but I can see there is an error in the editor saying 'Incompatible types: CargoFragment and Activity'. I tried replacing activity: Activity with context: Context.
The error I have is at 'is CargoFragment'
fun getProductList(activity: Activity) {
mFireStore.collection("abc")
.get()
.addOnSuccessListener {
.....
.....
.....
productList.add(product)
}
when (activity) {
is CargoActivity -> {
activity.success(productList)
}
is CheckoutActivity -> {
activity.success(productList)
}
is CargoFragment -> {
activity.success(productList)
}
}
}
.addOnFailureListener { e ->
Log.d("CheckTag", e.message!!)
when (activity) {
is CargoActivity -> {
activity.hideProgressDialog()
}
is CheckoutActivity -> {
activity.hideProgressDialog()
}
}
}
}
Don't pass activity to getProductList. As far as I understand, you are passing activity to execute some code when you get a response (success or failure). A better way to implement this is to expose callback lambdas.
Consider this approach:
fun getProductList(onSuccess: (List<Product>) -> Unit, onFailure:() -> Unit) {
mFireStore.collection("abc")
.get()
.addOnSuccessListener {
...
productList.add(product)
}
onSuccess(productList)
}
.addOnFailureListener { e ->
...
onFailure()
}
}
Usage (in your activity and fragment):
getProductList(
onSuccess = { list ->
success(list) // whatever you want to do on success
},
onFailure = {
hideProgressBar() // whatever you want to do on failure
}
)
if this is your viewModel, then u should NEVER have refernce to any context/activity/fragment.
Best approach would be to to have a liveData to hold progress states and let UI (activity or fragment) observe this.
No need to change anything in function parameter.
Call it from activity
getProductList(this)
and call it from fragment
getProductList(getActivity()).
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.
The problem with this as I see it is that you have to guarantee that registerForActivityResult() is called before your own activity's OnCreate() completes. OnCreate() is obviously not a suspending function, so I can't wrap registerForActivityResult() and ActivityResultLauncher.launch() in a suspendCoroutine{} to wait for the callback, as I can't launch the suspendCoroutine from OnCreate and wait for it to finish before letting OnCreate complete...
...which I did think I might be able to do using runBlocking{}, but I have found that invoking runBlocking inside OnCreate causes the app to hang forever without ever running the code inside the runBlocking{} block.
So my question is whether runBlocking{} is the correct answer but I am using it wrong, or whether there is some other way to use registerForActivityResult() in a coroutine, or whether it is simply not possible at all.
You can do something like this.
Please refer to the implementation below.
class RequestPermission(activity: ComponentActivity) {
private var requestPermissionContinuation: CancellableContinuation<Boolean>? = null
#SuppressLint("MissingPermission")
private val requestFineLocationPermissionLauncher =
activity.registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
requestPermissionContinuation?.resumeWith(Result.success(isGranted))
}
suspend operator fun invoke(permission: String) = suspendCancellableCoroutine<Boolean> { continuation ->
requestPermissionContinuation = continuation
requestFineLocationPermissionLauncher.launch(permission)
continuation.invokeOnCancellation {
requestPermissionContinuation = null
}
}
}
Make sure you initialize this class before onStart of the activity. registerForActivityResult API should be called before onStart of the activity. Refer to the sample below
class SampleActivity : AppCompatActivity() {
val requestPermission: RequestPermission = RequestPermission(this)
override fun onResume() {
super.onResume()
lifecycleScope.launch {
val isGranted = requestPermission(Manifest.permission.ACCESS_FINE_LOCATION)
//Do your actions here
}
}
}
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
}
}