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()
}
}
Related
I have a checbox. If the user tries to uncheck it, a popup should appear to get approval. How can I show a popup and wait for its response?
This code is in viewmodel:
fun stateChanged(check: Boolean){
if(check)
{
//make it checked
}
else{
//popup should appear to get approval then uncheck the checkbox.
}
}
I think I should create the popup in the fragment. But how can I observe the result from viewmodel?
I am new to the Mockito Android Test framework. I have written one method in the activity and which shows the AlertDialog and I am writing test cases for that method.
Here are the functions of the activity which shows the alert dialog box:
fun showDialogBox(
shortMessage: String?,
longMessage: String?,
progress: Int,
status: String?
) {
runOnUiThread {
if (cdfwExtractionStatusAlertDialog == null) {
val mDialogView = LayoutInflater.from(this).inflate(
R.layout.cdfw_extraction_status_layout,
null
)
val mBuilder = AlertDialog.Builder(this)
.setView(mDialogView)
.setCancelable(false)
shortMessageTv = mDialogView.findViewById(R.id.short_message_tv)
longMessageTv = mDialogView.findViewById(R.id.long_message_tv)
closeButton = mDialogView.findViewById(R.id.close_button)
cdfwProcessBar = mDialogView.findViewById(R.id.progress_bar)
cdfwProcessBar.progress = progress
shortMessageTv.text = shortMessage
longMessageTv.text = longMessage
closeButton.setOnClickListener {
cdfwExtractionStatusAlertDialog?.dismiss()
}
checkStatus(status)
cdfwExtractionStatusAlertDialog = mBuilder.show()
} else {
shortMessageTv.text = longMessage
longMessageTv.text = longMessage
cdfwProcessBar.progress = progress
checkStatus(status)
}
}
}
Here are my mockito test cases for the same function:
#Test
public void testCDFWExtractionStatusNoZipFound(){
MainActivity activity = Mockito.mock(MainActivity.class);
activity.showDialogBox("","CDFW Required.",0,"FAILED_NO_ZIP_FOUND");
Mockito.matches("CDFW Extraction Status");
Mockito.matches("CDFW Required.");
Mockito.matches("Close");
}
#Test
public void testCDFWExtractionStatusInProgress(){
MainActivity activity = Mockito.mock(MainActivity.class);
activity.showDialogBox("","CDFW extraction ongoing",0,"IN_PROGRESS");
Mockito.matches("CDFW Extraction Status");
Mockito.matches("CDFW extraction ongoing");
}
It shows the test passed successfully but I am not sure whether this is the right way to write test cases for the same function if this is the wrong method then how do I write a test case for the same function.
When you are testing your UI it is better to use instrumented testing.
Instrumentation testing is testing that runs on Physical devices, so, in your case, the test will run in a real device or emulator to check if the flow is right.
For that, you can use Espresso, which is a framework provided by AndroidX.
For more detail, please check this out:
Test UI for a single app
Now, why not use Mockito for UI testing?
When you are testing the UI, you have to ensure the right flows to the user and good interaction. You can mock the scenario, but you can't test the whole flow because you don't know how it will behave when, for example, a click is made in a different place than the one you are testing on.
If you want to test for example that your Alert Dialog is shown, you can use:
#Test
fun testCDFWExtractionStatusNoZipFound() {
//..
onView(withId(R.id.dialogId)).check(matches(allOf(withText("CDFW Required"), isDisplayed()));
//..
}
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.
I have a button in my activity. Once clicked, it checks user registration status. If registered then shows a dialog otherwise starts registration activity.
I set required flags that says user has registered. I perform click and expect the dialog must be created but after debug I see this is null :(
This is my code:
#Test
public void testSignupButton()
{
PreferenceUtils.setSessionId(activity, "sessionId");
assertTrue(PreferenceUtils.isActivated(activity));
btnSignUp.performClick();
Dialog dialog = ShadowDialog.getLatestDialog(); // << dialog is null
ShadowDialog loginDialogFragment = Shadows.shadowOf(dialog); // Test fails here since dialog is null
assertThat(loginDialogFragment.getTitle().toString(), equalTo("TestDialogFragment"));
}
Any idea, would be appreciated, thanks.
omg, I found where was my problem. I had activity = Robolectric.buildActivity(HitchWelcomeActivity.class).create().get(); in my setupView method which is wrong. In order to get real activity I should have visible() method too.
So, I changed above code to following and my problem fixed.
#Before
public void setUp()
{
activity = Robolectric.buildActivity(WelcomeActivity.class)
.create()
.start()
.resume()
.visible()
.get();
btnSignUp = (Button) activity.findViewById(R.id.dialog_welcome_sign_up);
btnSkip = (TextView) activity.findViewById(R.id.dialog_welcome_next_time);
}
I'm trying to implement a check box in a MaterialDialog using this library, and the check box asks the user if they don't want to see that dialog again. The dialog appears if the user's phone has NFC, but it is deactivated.
If the user presses the positive button in the dialog and has the box ticked, then it accesses a Realm object with a Boolean attribute named "NfcStatus", and sets that to true. If they press the negative button with the box ticked, then that Realm object's NfcStatus is set to false.
Here's the code of the MaterialDialog:
new MaterialDialog.Builder(context)
.title("NFC")
.content("NFC is disabled. Would you like to activate it?")
.items(R.array.checkbox) //this just has one string in it which says "Please don't show me this again"
.itemsCallbackMultiChoice(null, new MaterialDialog.ListCallbackMultiChoice() {
#Override
public boolean onSelection(MaterialDialog dialog, Integer[] which, CharSequence[] text) {
/**
* If you use alwaysCallMultiChoiceCallback(), which is discussed below,
* returning false here won't allow the newly selected check box to actually be selected.
* See the limited multi choice dialog example in the sample project for details.
**/
checkboxIsChecked = true; //TODO: checkboxIsChecked isn't being passed into onPositive or onNegative
return true;
}
})
.positiveText(R.string.accept)
.positiveColorRes(R.color.main_theme_color)
.negativeText(R.string.decline)
.negativeColorRes(R.color.main_theme_color)
.callback(new MaterialDialog.ButtonCallback() {
#Override
public void onPositive(MaterialDialog dialog) {
//this was how I was checking if checkboxIsChecked was true or false
Log.d("checkboxIsChecked", checkboxIsChecked?"true":"false"); }
if (checkboxIsChecked) {begins
if (ks.contains(KEY_NAME)) {
realmKey = ks.get(KEY_NAME);
}
realm = Realm.getInstance(context, realmKey);
RealmPhone realmPhone = realm.where(RealmPhone.class).findFirst(); realmPhone.setNfcStatus(true);
}
activity.finish();
startNfcSettingsActivity();
Toast.makeText(context, R.string.nfc_disabled_message, Toast.LENGTH_LONG).show();
}
#Override
public void onNegative(MaterialDialog dialog) {
if (checkboxIsChecked) {
if (ks.contains(KEY_NAME)) {
realmKey = ks.get(KEY_NAME);
}
realm = Realm.getInstance(context, realmKey);
RealmPhone realmPhone = realm.where(RealmPhone.class).findFirst();
realmPhone.setNfcStatus(false);
}
}
})
.cancelable(false)
.show();
The problem was that even if the check box was ticked, the checkboxIsChecked variable was still false when using it in onPositive or onNegative, so it was never being written to the Realm object. Am I doing this the wrong way?
For changing and saving the RealmObject, you need to use transactions. Related documents can be found here.
In you case, it would be something like:
realm = Realm.getInstance(context, realmKey);
// I'm not quite sure how did you create the realmPhone at the first time,
// just assume you have one realmPhone in the Realm.
RealmPhone realmPhone = realm.where(RealmPhone.class).findFirst();
realm.beginTransaction();
realmPhone.setNfcStatus(false);
realm.commitTransaction();
// Close the realm instance after using it is very important! To avoid leaks.
realm.close();
BTW, it seems code:
RealmPhone realmPhone = realm.where(RealmPhone.class).findFirst();
realmPhone.setNfcStatus(false);
is not called. If it does, a IllegalStateException should be thrown since you didn't call it in a Realm transaction. Or maybe RealmPhone is not inherited from RealmObject?