How to handle back button click in a Dialog in Jetpack Compose? - android

I am showing a custom dialog when there is no internet connectivity. I want to do some handling when the user presses the back button while the dialog is visible.
BackHandler inside the parent screen nor within the dialog itself is working in this scenario.
Thank you

Use the onDismiss callback and disable automatic dismissal when the user taps outside the dialog. This way you can ensure that the dismiss request originated from a back press. This is a workaround, of sorts, since an out-of-the-box API is not yet bundled with Compose.

Just define the BackHandler function inside the Dialog:
val shouldShowDialog = remember { mutableStateOf(true) }
if (shouldShowDialog.value) {
Dialog(onDismissRequest = { shouldShowDialog.value = false }) {
Button(onClick = {shouldShowDialog.value = false}){
Text("Close")
}
BackHandler {
// your action
}
}
}

Related

JetpackCompose how to createChooser and listen for result

I want to show a system dialog to user to select from available applications for sharing text from my app. I can do this by using createChooser function from Intent class. But i also want to listen for the system dialog result, so that i can disable/enable my share button to prevent creating multiple system dialogs overlapping each other.
To do this i need to know whenever the dialog is dismissed or an app option is selected by the user. So i need the result of the chooser Dialog i have created.
I was able to get the selected app, but was not able to listen the dismiss event for the system dialog because Intent.ACTION_CLOSE_SYSTEM_DIALOGS event is deprecated for third party applications. So is there any other way on how to know when the system dialog is closed?
Thanks in advance.
I was able to listen the result using rememberLauncherForActivityResult Composable function by combining it with ActivityResultContracts.StartActivityForResult abstract class. you can see the usage example i have implemented below. Please share your opinions/corrections or alternatives for my problem.
var shareEnabled by remember { mutableStateOf(true) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
// you can use the ActivityResult(it) here
shareEnabled = true
}
Button(
onClick = {
shareEnabled = false
launcher.launch(getShareText().shareExternal())
},
enabled = shareEnabled
)
shareExternal is an extension function that creates and returns the chooser Intent;
fun String.shareExternal(): Intent {
val dataToShare = this
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, dataToShare)
type = "text/plain"
}
return Intent.createChooser(sendIntent, null)
}

recognize back-button on dialog-service

I'm using the Prism- dialog-service to show a dialog with a yes- and a no-button. This works fine. The user makes the choice and the calling page gets the dialogresult.
But it is also possible for the user to press the back-button if the dialog is present and the dialog disappears without any result to the calling page.
I know that I can override the OnBackButtonPressed-event in the MainActivity but this affects everywhere in my app.
It's important for me, that I can handle this behavior (suspend Back-Button or not) individually for each Dialog.
Therefore I'm looking for an event which will be fired if the back-button is pressed while the dialog is shown.
Similary with the OnBackButtonPressed-Event on a ContentPage.
forms project - pages:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
.
.
IsBusy="{Binding IsBusy}">
android project - MainActivity:
public override void OnBackPressed()
{
var allNotBusy = true;
foreach (var fragment in this.GetFragmentManager().Fragments)
{
var contentPage = fragment.GetType().GetProperty("Page")?.GetValue(fragment) as ContentPage;
if (contentPage.IsBusy)
{
allNotBusy = false;
}
}
if (allNotBusy)
{
base.OnBackPressed();
}
}

AlertDialog appears only late (weird behaviour) Android

I am uploading data to a webserver. On my fragment I have a button to start the upload. There are two phases what I am trying to have the user notification done via a none-cancellable AlertDialog solution.
When I am pressing the upload button, preparation for the upload is starting I am setting up the AlertDialog and presenting it. Once the physical upload is starting, I am using the same AlertDialog, but changing the message in it to show the progress of the upload.
***** Now the issue is the following ******
When I setup the AlertDialog and call the Show method, it does not display the AlertDialog. But once the upload is started and the progress is updated I just call the setMessage method and at this point the AlertDialog appears.
The relevant codes are the followings:
The submitbutton.setOnClickLictener is in the onViewCreated()
submitbutton.setOnClickListener {
requireActivity().runOnUiThread {
SubmitAd()
}
}
I have tried here the run the SubmitAd() on the UIThread, if it helps, but it is the same without it.
SubmitAd is showing the Dialog. (Actually at this point nothing is shown.
fun SubmitAd() {
var addInApp: Boolean = false
ToBePurchased = 0
if (CheckCanUpload()) {
var AlertView = AlertDialog.Builder(requireActivity())
AlertView.setTitle("Hirdetés feltöltés")
AlertView.setMessage("A feltöltés előkészítése hosszabb ideig is eltarhat, kérjük várjon!")
AlertView.setCancelable(false)
DialogToShow = AlertView.create()
DialogToShow!!.show()
purchaseLoop = 0
UploadWithPurchase()
} else {
var AlertView = AlertDialog.Builder(requireActivity())
AlertView.setTitle("Hirdetés hiba")
AlertView.setMessage("A hirdetése hiányos. Kérjük töltse ki az összes mezőt és csatoljon fotót a hirdetéséhez!")
AlertView.setPositiveButton("Ok") { dialog, which ->
dialog.dismiss()
}
DialogToShow = AlertView.create()
DialogToShow!!.show()
}
}
In UploadWithPurchase() the Playstore purchase handling is done, but if there is no purchase at all, it is just going through a loop, which calls UploadWithPurchase() recursively until all possible purchases are checked, then it goes to the real Upload() which calls an Http request to upload the data and reports back via an interface the progress of the upload process.
The Webhelper returns the progress like this:
override fun WebHelperProgress(id: String, progress: Float) {
if (DialogToShow != null) {
DialogToShow!!.setMessage("Feltöltés folyamatban. Kérem várjon! ... ${progress.toInt()}%")
}
}
When this method is called, the AlertDialog appears.
Whatever I have tried, does not help. AlertDialog does not show up at the first call, but no clue why.
EDIT later: I have figured out that the AlertDialog is actually appears once it comes out from the recursive loop, but I do not know how to force it to be displayed before it starts the loop. That would be my aim to notify the user that a longer process is starting. It meaningless to start the process and the user does not know what is happening.
Finally I could solve it by putting the purchaseLoop to a separate Thread like this.
fun SubmitAd() {
var addInApp: Boolean = false
ToBePurchased = 0
if (CheckCanUpload()) {
var AlertView = AlertDialog.Builder(requireActivity())
AlertView.setTitle("Hirdetés feltöltés")
AlertView.setMessage("A feltöltés előkészítése hosszabb ideig is eltarhat, kérjük várjon!")
AlertView.setCancelable(false)
DialogToShow = AlertView.create()
DialogToShow!!.show()
purchaseLoop = 0
******** SOLUTION HERE ********
Thread {
UploadWithPurchase()
}.start()
*******************************
} else {
var AlertView = AlertDialog.Builder(requireActivity())
AlertView.setTitle("Hirdetés hiba")
AlertView.setMessage("A hirdetése hiányos. Kérjük töltse ki az összes mezőt és csatoljon fotót a hirdetéséhez!")
AlertView.setPositiveButton("Ok") { dialog, which ->
dialog.dismiss()
}
DialogToShow = AlertView.create()
DialogToShow!!.show()
}
}

How to add a page to navigation history to prevent app from exiting?

I'm still rather new to Ionic 4. I'm making an App that receives push notification. The navigation inside the app works like this:
Home page -> Detail page
Every time the user taps on the notification, the app will open and navigates to Detail page. The navigation works but since the navigation history is empty, if the user taps on the hardware back button, the app exits. I want it to redirect the user to Home page instead.
How do I achieve this in Ionic 4? is there any way to push a page to navigation history? I have read the documentation but couldn't find anything about this. The closest was probably NavCtrl.push() but it's no longer usable in Ionic 4.
Thank you.
There may be an easier way to do this but the following approach is a very flexible one because it'd allow you to run any custom logic when the user wants to go back from the page shown after a push notification or a deep link is opened.
Please take a look at this StackBlitz demo.
Please notice that in the demo, I'm redirecting to the DetailsPage as soon as the app is loaded because of the following code from the app-routing.module file:
{
path: "",
redirectTo: "/details/1?fromDeepLink=true", // <-- here!
pathMatch: "full"
}
Anyway, the important part happens in the DetailsPage. There, you need to handle what happens when the user tries to go back using a) the back button from the header and b) the physical back button from Android devices
The code is pretty self-explanatory, but basically in that page I'm looking for the fromDeepLink query string param, and if it's true, the app will register a custom action for both the back button from the header and for the physical back button from Android devices.
The custom action sets the HomePage as the root page, but sets the animationDirection parameter to be 'back'. That way it'd look like the user is going back to that page even if we're actually adding it to the navigation stack.
It's important to notice that this custom handler is being removed as soon as the user leaves the page so that we don't affect the default behaviour of the back button in any other pages.
import { Component, OnInit, ViewChild } from "#angular/core";
import { ActivatedRoute } from "#angular/router";
import { IonBackButtonDelegate, NavController, Platform, ViewDidEnter, ViewWillLeave } from "#ionic/angular";
import { Subscription } from "rxjs";
#Component({
selector: "app-details",
templateUrl: "./details.page.html",
styleUrls: ["./details.page.scss"]
})
export class DetailsPage implements OnInit, ViewDidEnter, ViewWillLeave {
#ViewChild(IonBackButtonDelegate, { static: false })
public backButton: IonBackButtonDelegate;
public itemId: number;
public fromDeepLink: boolean = false;
private unregisterBackButtonAction: Subscription;
constructor(
private platform: Platform,
private route: ActivatedRoute,
private navCtrl: NavController,
) {}
ngOnInit() {
const itemIdParam = this.route.snapshot.paramMap.get("itemId");
const fromDeepLinkParam = this.route.snapshot.queryParamMap.get('fromDeepLink');
this.itemId = +itemIdParam;
this.fromDeepLink = fromDeepLinkParam
? fromDeepLinkParam.toLocaleLowerCase() === 'true'
: false;
}
ionViewDidEnter() {
if(this.fromDeepLink) {
this.initializeCustomBackButtonAction()
}
}
ionViewWillLeave() {
if(this.fromDeepLink) {
this.removeCustomBackButtonAction();
}
}
private initializeCustomBackButtonAction(): void {
const leavingCallback = () => {
console.log('Using custom back button action');
this.navCtrl.navigateRoot('/home', { animationDirection: 'back' });
};
// Override the back button from the header
if (this.backButton) {
this.backButton.onClick = leavingCallback;
}
// Override the physical back button from Android devices
this.unregisterBackButtonAction = this.platform.backButton.subscribeWithPriority(10, leavingCallback);
}
private removeCustomBackButtonAction(): void {
this.unregisterBackButtonAction?.unsubscribe();
}
}
Please also notice that by default the ion-back-button is not shown if there's no page before the current page in the navigation stack, so in the demo I'm setting the defaultHref property like this:
<ion-back-button defaultHref></ion-back-button>
I'm leaving it empty because the component is actually going to override what this back button does with my custom back button action. But the defaultHref needs to be added to the template, otherwise the back button won't be shown.

How to make BiometricPrompt non-cancelable?

I am using BiometricPrompt in my application. It works well and shows the dialog when call the authenticate() method. But this dialog gets closing when I click outside the dialog. How to prevent it? How to make BiometricPrompt's dialog non-cancelable? Here is no method like biometricPrompt.setCancelable(false).
BiometricPrompt does not allow that. So you won't be able to make the system-provided biometric prompt non-cancelable. But you can detect whenever user cancels the dialog.
So an option would be, to show again the biometric prompt after user cancel it (which I think would be a bad user experience) or use alternate user authentication:
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
if (errorCode == BiometricConstants.ERROR_USER_CANCELED) {
// User canceled the operation
// you can either show the dialog again here
// or use alternate authentication (e.g. a password) - recommended way
}
}
check it out
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
supportFragmentManager.fragments.forEach {
if(it is DialogFragment) {
it.dialog?.setCanceledOnTouchOutside(false)
}
}
}
There are some devices that still have this issue. An work around will be to get root view and add an overlay view with clickable method set to false.
ViewGroup viewGroup = ((ViewGroup) yourActivity.findViewById(android.R.id.content)).getChildAt(0);
//create your view
Display display = mActivity.getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
View view = new View(yourActivity);
view.setId(R.id.overlay_view);
view.setLayoutParams(new ViewGroup.LayoutParams(size.x, size.y));
view.setBackgroundColor(ContextCompat.getColor(yourActivity, R.color.black));
view.setOnClickListener(v -> {
//do nothing prevent click under this overlay
});
//add your view on top of the screen
viewGroup.addView(view);
//call your biometric dialog
....
//on callbacks even if it is error or success call remove view
viewGroup.removeView(view);
You have to use the version 1.0.0-beta01 or later.
Now it is the default behavior:
Touches outside no longer cancel authentication. Back button cancel authentication still.
You can see the changelog:
Changed behavior to not allow BiometricPrompt to be cancelled by a touch event outside the prompt.
You can check also the rewiew report.
No new API.

Categories

Resources