I have a fragment which should not be navigated back to when the Back button is pressed.
My Activity inherits from MvxCachingFragmentCompatActivity
My Fragment inherits from MvxFragment and it has the AddToBackStack set to false in the MvxFragmentAttribute above the class as follows:
[MvxFragment(typeof(MainViewModel), Resource.Id.main_frame_layout, false)]
[Register("myproject.droid.fragments.MyFragment ")]
public class MyFragment : MvxFragment<MyViewModel>
{
However when I navigate to it and then to my next frament and press the back button then it navigates back to it even though AddToBackStack is false.
The code for the MvxCachingFragmentCompatActivity can be found here: https://github.com/MvvmCross/MvvmCross-AndroidSupport/blob/master/MvvmCross.Droid.Support.V7.AppCompat/MvxCachingFragmentCompatActivity.cs
It appears to do the right thing in the ShowFragment method (Line 234):
if ((currentFragment != null && fragInfo.AddToBackStack) || forceAddToBackStack)
{
ft.AddToBackStack(fragInfo.Tag);
}
ShowFragment is not called with forceAddToBackStack set to true.
How can I prevent my fragment being added to the back stack?
If the fragment you are navigating to after the First Fragment has it's AddToBackStack set to true, you will experience this behaviour. While the First Fragment is not getting added to the backstage the activity that is holding it is getting added.
Therefore when you navigate to the Second Fragment you actual have 2 items in your backstack, the Activity and the Second Fragment. Navigating back from the Second Fragment will take you back to the previous shown fragment within the Activity which in this case is your First Fragment.
To solve this issue you can set AddToBackStack to false on the Second Fragment which would mean that it's back navigation will fall under that of the Activity.
Related
I am building a Xamarin Forms mobile app that runs in Android on a Zebra scanner. I flip 2 different StackLayouts to IsVisble true/false to display different stuff in the UI. (StackLayout1 and StackLayout2)
The customer wants the user to be able to use the app entirely from the hardware keyboard on the scanner. So I have used the device Settings so that it never displays the virtual keyboard (I don’t think that matters for the issue I am having.)
I am overriding DispatchKeyEvent in a PageRenderer in the Android project and everything is working great … except.
The problem case:
StackLayout1 is displayed
the user taps an Entry control, putting the focus there
the user taps a button in the UI
the app displays StackLayout2
at this point the DispatchKeyEvent never fires no matter what key I press on the device keyboard
If an Entry box does NOT get the focus (step #2 above) the DispatchKeyEvent always fires in StackLayout2 and the StackLayouts display as expected.
If I programatically put the focus in an Entry box in StackLayout2 at step #3 above the DispatchKeyEvent fires fine.
That is not an OK solution. I have tried to progamatically put the focus on StackLayout2, and that code seems to do what is expected but DispatchKeyEvent does not fire.
Maybe I need to do something in the Android-project PageRenderer so that it is aware of StackLayout2 when it is made IsVisible = true.
Update 2: I found that I did NOT need custom StackLayouts. The solution which I posted below does not include any of this stuff I am describing in Update 1 (sorry, if that's confusing).
Update 1:
I added a ViewRenderer for both StackLayouts, and the code is hitting the OnElementChanged event when StackLayout2's IsVisible property flips to true, just great. Although the problem case is the same: DispatchKeyEvent does not fire once StackLayout2 is displayed, if an EntryBox had the focus in StackLayout1
Here is the OnElementChanged part of the new StackLayout ViewRenders
async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "IsVisible":
if (Element.IsVisible)
{
if (sender is StackLayout)
{
this.FocusableViewAvailable(this); // if I comment these 2 lines out I get the same bad result
this.Focusable = true; // if I comment these 2 lines out I get the same bad result
this.FocusableInTouchMode = true;
var dd = this.RequestFocus(); // this is always false
var ee = this.IsFocused; // this is always false
}
}
break;
}
}
Also, as I am pointing out in the comments ^ there, IsFocused is always false.
Ideas?
My hunch, "Maybe I need to do something in the Android project PageRenderer" was correct. In the DispatchKeyEvent I had to make the MainPage have the focus when the keypress was handled.
Here is what the DispatchKeyEvent looks like now (notice the comments):
public override bool DispatchKeyEvent(KeyEvent ke)
{
// MainPage.ReceiveKeyPress(e); is the method that this method returns to
bool KeyPressWasHandled = false;
KeyPressWasHandled = (Element as MainPage).ReceiveKeyPress(ke);
if (KeyPressWasHandled)
{
// this next block seems to be needed so that this class
// continues to receive the keypress event after an Entry box has had the focus
this.Focusable = true;
this.FocusableInTouchMode = true;
this.RequestFocus();
return true; // returning true tells the parent class that the keypress has been handled
} else
{
try
{
return base.DispatchKeyEvent(ke);
}
Now the "problem case" in my initial post is no longer a problem.
NOTE: I found that I did NOT need the custom ViewRenderers that I had made for the StackLayouts.
My main page uses a TabbedPage to group existing news into different lists. The tabs aren't fixed; they're built from a data binding operations against a collection that's retried through a web service call.
I'd like to persist the selected tab across activity restarts, but it seems like I'm missing something. Since there's no selected tab property (which can be set through data binding), I've tried to handle the PageChanged and the CurrentPageChangedCommand events. I'm using the PageChanged to set the selected tab to the previous selected tab and the CurrentPageChangedCommand is being used to update the persisted selected tab (I'm using the Application.Properties to make sure the selected tab survives app restarts).
Unfortunately, the events generated by the tab will always set tab 0 as the selected tab! Here's what I'm seeing (let's assume that my app was killed white tab 3 was active):
When data is bound to the TabbedPage.ItemsSource property, the tab will automatically fire the CurrentPageChangedCommand, passing the first tab (tab at position 0).
My code handles the event and updates the current persisted selected tab by changing the selected tab in the Properties dictionary. So now, instead of 3 (which was the value persisted when my app was killed), it will have 0.
Then the tab will fire the PagesChanged
When my code handles this event, it will try to update the selected tab. However, when it access the selected tab from the Properties dictionary, it will get the default tab (0) and not 3. This happens because the CurrentPageChangedCommand was fired before the PagesChanged event (step 2), completely overriding the previously persisted tab index.
This default behaviour will also give a bad user experience when the user refreshes the current list (pull to refresh) because he always ends up seeing tab 0 list.
So, any clues on how to solve this? How have you guys solved this?
Thanks.
It seems it can't be achieved using MVVM as CurrentPage is not a bindable property and CurrentPageChanged is an event.
However, there's no need to handle the PagesChanged event. You could record the index in the changed event like:
private void MyTabbedPage_CurrentPageChanged(object sender, EventArgs e)
{
Application.Current.Properties["index"] = this.Children.IndexOf(CurrentPage);
Application.Current.SavePropertiesAsync();
}
Then you could set your tabbed page's current page after you have loaded all the tabs:
object index;
Application.Current.Properties.TryGetValue("index", out index);
if (index != null)
{
CurrentPage = Children[int.Parse(index.ToString())];
}
// Subscribe the event
CurrentPageChanged += MyTabbedPage_CurrentPageChanged;
I placed the code above in the custom tabbed page's constructor and it could change the selected tab at initial time.
Update:
If you want to change the tabbed page's children dynamically, you could define a property to avoid the event being fired when you change the children:
bool shouldChangeIndex = true;
private void MyTabbedPage_CurrentPageChanged(object sender, EventArgs e)
{
if (shouldChangeIndex)
{
var index = this.Children.IndexOf(CurrentPage);
Application.Current.Properties["index"] = index;
Application.Current.SavePropertiesAsync();
}
}
// Simulate the adjusting
shouldChangeIndex = false;
Children.Clear();
Children.Add(new MainPage());
Children.Add(new SecondPage());
shouldChangeIndex = true;
object index;
Application.Current.Properties.TryGetValue("index", out index);
if (index != null)
{
CurrentPage = Children[int.Parse(index.ToString())];
}
Unfortunately, I had to abandon the MVVM approach...In the end, I had to resort to code and a couple of flags to control when the generated tab events should be handled.
I have manually/programmatically set up an up button in my toolbar for a fragment page with the following code in onCreateOptionsMenu in the fragment:
(activity as AppCompatActivity).setSupportActionBar?.setDisplayHomeAsUpEnabled(true)
Tapping the system back button will take the user back to the previous fragment but without an up button (that works) I think some users may get lost.
I am having difficulty trying to work out how to catch and handle the up button to pop the fragment off the back stack.
This SO post shows how to catch the the up button click however it is in Java and doesn't go on to explain how you would navigate up.
I think it needs to look something like the code below but there are errors everywhere:
The case android.R.id.home is showing an 'Incompatible types:Int and MenuItem' error?
onBackPressed() is showing an 'Unresolved reference' error.
Bad code:
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item) {
android.R.id.home -> {
onBackPressed()
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
UPDATE:
A comment to another SO post has a potential solution in Kotlin. It catches the click on the up button and goes back to the previous fragment page but then the up button doesn't go away. So the up button now persists even on the top level fragment destinations in my app (the pages corresponding to each tab in the BottomNavigationView).
I think this might have to do with the fact that there is only one activity in my app and the way that I have set up the up button in the fragment as mentioned above? If so, is there a workaround or other way to set up the up button by referencing the fragment instead of the whole activity?
If it helps, this is the code in the RecyclerView inner ViewHolder class in the adapter.kt file that navigates to the fragment page in question:
class AdapterListItemDetails(val items: List<ItemsList>) : RecyclerView.Adapter<AdapterListItemDeatils.ItemsViewHolder>() {
//overrides for OnCreateViewHolder, getItemCount, onBindViewHolder
inner class ItemsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var currentItem: ItemsList? = null
var currentPosition: Int = 0
init {
itemView.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.goto_details, null))
}
fun setData(itemsList: ItemsList, position: Int) {
itemView.tview_Keys.text = itemsList!!.nameText
this.currentItem = itemsList
this.currentPosition = position
}
}
}
You have to override onBackPressed() method in activity and handle the fragment transactions with your manual code. If you could share some snippet of activity and fragment transactions will help me to give some proper solution.
Hi this is what i usually do:
in an activity find the navController from your navHostFragment
val navController = this.findNavController(R.id.myNavHostFragment)
Make sure it's connected to the ActionBar
NavigationUI.setupActionBarWithNavController(this, navController)
Then simply override onSupportNavigateUp, find your navController then navigate up
override fun onSupportNavigateUp(): Boolean{
val navController = this.findNavController(R.id.myNavHostFragment)
return navController.navigateUp()
}
Problem:
Have tabPane tabs OK.
In the first tab there is a text field. I am able to get focus on this field when starting the application.
After changing the tabs and coming back to the first tab I want focus to be on this textfield (barcodereader should be active in this field) without having to select the field with the mouse.
I am able to catch event from tabs with
tp.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>()
{ etc
(could not post with code)
and I am able to trigger en event for the first tab.
But field.requestFocus(); does not work. Probably because this method comes before rendering the textfield.
So here is my question:
How do you set focus on a control after clicking tabs in TabPane?
If you handle the mouse release event, it works: (The doFocus enables the requestFocus handling only when a tab selection changed before, otherwise it kicks in every time you click somewhere in the TabPane.)
final SimpleBooleanProperty doFocus = new SimpleBooleanProperty(false);
tabPane.setOnMouseReleased(new EventHandler<Event>() {
#Override
public void handle(Event event) {
if (!doFocus.get()) {
return;
}
doFocus.set(false);
switch (tabPane.selectionModelProperty().getValue().selectedIndexProperty().intValue()) {
case 0: tf1b.requestFocus(); break;
case 1: tf2a.requestFocus(); break;
default: break;
}
}
});
tabPane.selectionModelProperty().getValue().selectedIndexProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
doFocus.set(true);
}
});
When the TabPane has focus, one can change tab selection with the cursor keys and there the TextFields also won't get the focus with selection based approach. This probably should be handled too, if you need it.
(Recently I had a similar problem. I noticed, that the TabPane switches tabs immediately when you press the mouse button. My guess would be, that the selection based approach requests focus on the TextField right after mouse down, but the continued mouse down steals the focus back to the TabPane. Or maybe even the single mouse down event which changes selection causes the focus to go back to TabPane. However, my assumptions regarding the reasons may not be correct, as I am a newbie to JavaFX.)
EDIT: That handling certainly is not optimal. For instance, if you change tabs with the keys, the doFocus will be enabled and then clicking anywhere in the TabPane will trigger the requestFocus call. I thought this should be mentioned.
Also, take a look at my solution for setting focus on TextArea, when user changes selected tab(using mouse or keyboard) https://stackoverflow.com/a/19046535/2791746
I need to programmatically remove an alert.
This is why:
My application uses BrowserManager to enable deep linking based off of the content in the #hash part of the url. If an alert is currently up, and the user hits the back button, the application will revert back to its previous state. But the Alert will still be up, and in many cases irrelevant at that point.
So is there a way to programmatically remove the Alert? so when the hash fragment changes I can remove it.
Thanks!
It turns out the Alert.show function returns an Alert reference and then just uses PopUpManager to add it to the display list. so if you capture the return reference when you call Alert.show you can tell PopUpManager to remove it. :)
You can do this by keeping the Alert object as member data, and then setting its visible property to false when you're done with it. Next time you need to show an Alert, don't create a new one - grab the one you've already created and set its properties, then set visible to true again.
private var myAlert : Alert;
public void showAlert( message: String, title : String ) : void
{
hideAlert();
myAlert = Alert.show( message, title, Alert.OK | Alert.NONMODAL );
}
public void hideAlert() : void
{
if( myAlert != null && myAlert.visible ) {
myAlert.visible = false;
}
}
I don't think that is possible.
You can create your own alert component subclassing TitleWindow and then use PopupManager to show/hide them.