Androidx OnBackPressedDispatcher - how to consume the back button press - androidx

I would like the onBackPressedDispatcher to absorb the back button press event. Sometimes but I don't see an option for it. Whats happening is that we are just today trying to upgrade to onBackPressedDispatcher in androidX but we have already overridden onBackPressd in activity. so when our onBackPressedDispatcher calls OnBackPressedCallback afterwards there is also a call to activities onBackPressed override. we don't want that. the onBackpressed should be consumed on the spot. here is what I have so far:
const val TAG = "MyTag"
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_first)
MyTester(this)
}
override fun onBackPressed() {
super.onBackPressed()
Log.v(TAG, "Activities class back button Pressed")
}
inner class MyTester(var activity: AppCompatActivity) {
init {
addBackCB()
}
fun addBackCB() {
var callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.v(TAG, "inner class MyTester back button")
}
}
activity.onBackPressedDispatcher.addCallback(activity, callback);
}
}
}
Which prints the following:
V/MyTag: inner class MyTester back button
V/MyTag: Activities class back button Pressed
If I don't call super.onBackPressed() then the dispatcher does not even work. it needs that call.

This is because the OnBackPressedDispatcher is called in ComponentActivity.onBackPressed(), if you look at the source code:
#Override
#MainThread
public void onBackPressed() {
mOnBackPressedDispatcher.onBackPressed();
}
So you'll never reach your callback if you override onBackPressed and never call the super method. I think the whole idea behind OnBackPressedDispatcher is that you shouldn't have to override Activity.onBackPressed if you want your fragments to intercept back presses. This is mentioned in one of the guides.
If you really want your activity to handle the back press, you can try something like this in your override:
if (onBackPressedDispatcher.hasEnabledCallbacks()) {
// There's an active callback; let the fragment handle it
super.onBackPressed()
} else {
// Do your activity's back press handling here
}
Be careful doing this, though. After trying it myself, you might find that hasEnabledCallbacks will return true even when you haven't added any callbacks yourself. This is because your FragmentManager has its own back press callback that's going to be enabled whenever backStackEntryCount > 0. I'm not sure if there's a quick workaround for that.

Related

Rg.Plugins.Popup on device back button click in Xamarin Forms

Once the Rg.Plugin.Popup is displayed, with
PopupNavigation.Instance.PushAsync(new PopupPage());
Once its displayed, and on pressing Device Back button. screen getting hanged.
No OnBackPressed in declared in the code.
Once its displayed, and on pressing Device Back button. screen getting hanged. No OnBackPressed in declared in the code.
From this article, for Android back button to work with the plugin, you should invoke Rg.Plugins.Popup.Popup.SendBackPressed in your MainActivity in override method OnBackPressed.
public async override void OnBackPressed()
{
if (Rg.Plugins.Popup.Popup.SendBackPressed(base.OnBackPressed))
{
// Do something if there are some pages in the `PopupStack`
await PopupNavigation.Instance.PopAsync();
}
else
{
// Do something if there are not any pages in the `PopupStack`
}
}

Why DisplayAlert does not work in constructor?

I have some problems in my Xamarin.Forms app related to invoking async method in the page constructor so when I test something trying to figure out the reason I just realized DisplayAlert method does not even work in the page constructor so I am wondering why is that happening?
Here is my code:
public MainPage ()
{
InitializeComponent ();
DisplayAlert("An alert", "Why I don't show up?", "Ok");
}
and I also tried to call async method that has DisplayAlert method but didn't work too, here is the code:
public MainPage ()
{
InitializeComponent ();
Async_Function_Has_DisplayAlert();
}
async void Async_Function_Has_DisplayAlert()
{
// I tried both and neither of them worked
await DisplayAlert("An alert", "Why I don't show up?", "Ok");
await Task.Run(()=> DisplayAlert("An alert", "Why I don't show up?", "Ok"));
}
So can someone explain why that is happening please?
Normally, you should not call an awaitable method like DisplayAlert() from the constructor.
What you can do is have a method that returns void (still not best practice) and call that method from your constructor.
Tweaking my recommendation after trying it out.
I used Device.Timer to delay the alert.
I think some components have not finished loading (in this case, the DisplayAlert) before trying to call it.
public MainPage()
{
InitializeComponent();
Device.StartTimer(TimeSpan.FromSeconds(4), () =>
{
ShowMessage();
return false; // True = Repeat again, False = Stop the timer
});
}
public async void ShowMessage()
{
await DisplayAlert("Alert", "I show here", "OK");
}
There seems to be a misconception on what is happening in the constructor.
The constructor simply creates a new Page class.
CustomMainPage mainpage = new CustomMainPage();
(App.Current as App).MainPage = new NavigationPage(mainpage);
So before we add the mainpage class to the NavigationPage, all that has happened is that the CustomMainPage class was initialized and is ready to be inserted into an appropriate container.
However after creating the new page, there is no actual UI on the screen, yet. For instance, the mainpage object wouldn't have width or height set, no layout has been done, etc...
If you run a UI related task, such as presenting an Alert, there isn't simply any foundation for it there, which could do anything resonable.
Of course you could already set members of the mainpage, such as labels or buttons to certain values, colors, styles or whatever you want, from within the constructor, but these wouldn't do anything at that point of time.
All of those values will be taken into account when the page is being layouted and presented but none of that will happen in the constructor.
However, back to your problem: You seemingly want to inform the user that something has gone wrong during the initialization.
I see two ways of adressing that issue:
Check the preconditions on the page or within the code before initializing your view and present the Alert from the page or class, which is initializing your page.
create a private variable in your page class, which you will set from within your page constructor if something goes wrong. This could be a simple bool flag, a string containing an error message, an enum or whatever suits your needs. Then override the OnAppearing() method, check that flag you set earlier and call DisplayAlert depending on the flag's value.
If you want any interactivity on your page, then you should consider Jason's comment to your question and implement it within OnAppearing, because this method will be called once your page has been fully layouted and is being presented on your screen.
Sample code for Jason's recommendation
public async void ShowMessage()
{
await DisplayAlert("Alert", "I show here", "OK");
}
protected override void OnAppearing()
{
ShowMessage();
}

how to invoke fragmentB method in another fragmentA

I have a fragment_A with tabs, consider tabs as fragment_B and C. And am implementing custom keypad with "Done" key in it. In my main Activity iam calling the listener to press the done button
// Used when "Done" button pressed in keyboard
#Override
public void keylisten() {
((Housing) fragmentStack.lastElement()).whenokkeypressed();
}
Now i want to call a method from fragment_B which goes into the whenokkeypressed() of the fragment_A;
There are 2 things you should do
Make whenonkeypressed() static and call it from class name of the fragment like Fragment_A.whenonkeypressed()
(optional) instead of using keylisten of done in mainActivity you should prefer an anonymous inner class like
editText.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
whenonkeypressed();
} });

Difference between Click, Touch and Gesture in Android?

I am new to Android developing and there it is not very clear to me the difference between the Click, Touch and Gesture classes in Android.
Is on the generalization of the other?
If you're talking about specific classes its always good to include the fully qualified name so as to avoid ambiguity.
Click is pretty generic so I assume your talking about
android.view.View.OnClickListener. This is an interface your widget class can implement to use the call back method onClick(). Any code inside the onClick() method is executed when you press that view (button).
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
}
});
Touch android.view.View.OnTouchListener
The onTouchListener is an interface that exposes the onTouch() callback method and gives you access to the android.view.MotionEvent members like ACTION_BUTTON_RELEASE. The MotionEvent class is very powerful for movement related behaviour.
Below example is from thread https://stackoverflow.com/a/11690679/1005142
imageButton.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP){
// Do what you want
return true;
}
return false;
}
});
Gesture android.view.GestureDetector.OnGestureListener
This class is used to pick up how the user gestures their finger with your UI. There is already a lot of information on the Android dev site in the gesture section http://developer.android.com/training/gestures/detector.html.
An example of using this class would be if you were writing fluidly with your finger on the keyboard where you need to listen for touch, movement and acceleration.

CaliburnMicro StackOverflowException when ActivateItem function is invoked

I have two VM - View (inherited from Screen) and Edit (inherited from Screen). View is used to display grid with data and Edit - add/edit new items into grid.
In my ShellViewModel I have the following code to activate View.
public void WorkstationView()
{
this.ActivateItem(ServiceLocator.Current.GetInstance<WorkstationViewModel>());
}
In WorkstationViewModel when user clicks on the Create button the following code is invoked
public void CreateAction()
{
EditableObject = new WorkstationDto();
TryClose(true);
}
And there is a listener to Deactivated event property, see code below (InitViewModels is invoked in ShellViewModel constructor).
private void InitViewModels()
{
#region Init
WorkstationViewModel = ServiceLocator.Current.GetInstance<WorkstationViewModel>();
WorkstationEditViewModel = ServiceLocator.Current.GetInstance<WorkstationEditViewModel>();
#endregion
#region Logic
WorkstationViewModel.Deactivated += (o, args) =>
{
if (WorkstationViewModel.EditableObject == null)
{
return;
}
WorkstationEditViewModel.EditableObject = WorkstationViewModel.EditableObject;
ActivateItem(WorkstationEditViewModel);
};
#endregion
}
The problem here is a StackOverflow exception when I close Edit view (see create action).
“Since the Conductor does not maintain a “screen collection,” the activation of each new item causes both the deactivation and close of the previously active item.” Caliburn.Micro documentation
If you are using Conductor<T>, then ActivateItem(WorkstationEditViewModel); inside of the Deactivated handler is implicitly re-triggering the deactivation of the previous viewmodel - giving you an infinite loop. Try changing your conductor to inherit from Conductor<IScreen>.Collection.OneActive instead. However, you will still have two deactivations: the one from the original TryClose operation, and a second one when you activate the new screen. Overriding DetermineNextItemToActivate can help you avoid that.

Resources