Hi Xamarin/MvvmCross devs,
I assume any of the Android MvvmCross devs have at some point dealt with what I’m struggling to do since yesterday. I’m dealing with this backstack problem
All I want to do is ALWAYS, and I literally mean ALWAYS skip a certain view on back pressed. I know it sounds pretty simple (and I hope it is) because I’ve been struggling with this quite a bit. Since the AddToBackstack behaviour “doesn’t work” as I expect it to I don’t understand it properly. Because of that I’ve overridden OnBackPressed in my container view and had done numerous checks, all which fail at some point.
My current "solution" is: I get the top fragment, then I find its position in the SupportFragmentManager.Fragments (using IndexOf(topFragment)), go down the stack to check if the next “Real Fragment” is the Fragment I want to skip. I say real fragment because there are null fragment entries as well as SupportRequestManagerFragment entries which cause some havok of its own.
That worked fine until I realized that sometimes the order of SupportFragmentManager.Fragments is not correct. When using the same view twice and having the SupportRequestManagerFragment on there it places the fragment in a weird position, so my solution became obsolete because the view I wanted to skip appears above the view that's currently on screen in the fragment stack.
Any help would be appreciated. Any pointers on the fragment backstack, maybe a more reliable way to see the order of the fragments without the nulls and the SupportRequestManagerFragment from glide on there.
Thank you
Related
I have built a Xamarin Android app that presents the user with a series of data entry forms, like a wizard. The wizard has a bottom navigation bar with previous and next buttons, and a menu button that when pressed displays a list of all forms in the wizard and allows the user to jump to any given form.
The desired functionality is to preserve the linear navigation, so that when the user jumps to the middle of the wizard, they can still use the previous and next buttons to page through the various forms in order. They should also be able to use the hardware back button to view the previous form in the wizard.
I suspect my implementation is not MvvmCross friendly because I'm seeing some bugs with it, specifically my viewmodels are not destroyed when I clear the fragment backstack (wizard hosted in an Activity, each form is a Fragment).
How should I implement this?
Have you tried using this overload of PopBackStackImmediate? This one will pop all of the fragments until the one you specify in the string (if inclusive flag is passed, then that fragment is also popped) so you spare iterating all over the fragment backstack.
Activity.SupportFragmentManager.PopBackStackImmediate("myViewTag", (int)PopBackStackFlags.Inclusive);
where "myViewTag" is the UniqueImmutableCacheTag of your View
There has to be something unconventional about my implementation, but with a deadline and little help from the community, what do you do to fix your hack, but to hack it more?
My solution was twofold:
1) instead of using FragmentManager.PopBackstackImmediate(), I implemented a while loop with the condition: activity.SupportFragmentManager.BackStackEntryCount > 0, calling Close(fragment.ViewModel) in the body. This should have fixed the bug, but it didn't.
2) The ViewModels I was requesting to Close were still not being disposed, so I had to resolve the current IMvxMultipleViewModelCache and call GetAndClear on it with the expected parameters. This forced my ViewModels to be disposed so they will be recreated on the next viewing of its Fragment.
This feels like a hack. Closing a ViewModel should dispose of it whether it's associated with a Fragment or Activity, but for some reason it wasn't. That's the key to this bug, but like I said, deadline, hack on hack.
New to Android. Trying to understand the technical downside of a design.
I have an ItemSelectorView which has both an availableItems collection as well as a selectedItem property. The latter is displayed at runtime along with a chevron letting the user know they can change it.
Functionality-wise, it's similar to a spinner except rather than showing a pop-up with the choices, it launches a second activity called ItemSelectorActivity which shows all of the available items from the ItemSelectorView which launched it, as well as helpful information about what each option means as well as the ability to search and filter.
When a user selects one, their choice is then returned back to the originating ItemSelectorView via a trip through MainActivity's onActivityResult override. The override then figures out which ItemSelectorView it's responding to and forwards it along.
However, I really didn't like the way MainActivity had to insert itself into the process just to get the result from ItemSelectorActivity. Plus, if ItemSelectorView was used in a fragment, things become more complex trying to forward the information to the correct view.
Because of this, I instead changed ItemSelectorView to ItemSelectorFragment which can override its own onActivityResult making it, not its owning activity, responsible for updating itself with the selected result. This means it's essentially completely self-contained now.
While this seems to work wonderfully--with this change a user only needs to place the ItemSelectorFragment somewhere in their layout and set the available and selected items--a veteran Android developer here said using a fragment like that was wrong and I should have stayed with a view because of the overhead of fragments.
But going back to a view means I'm back to having to get MainActivity involved again, making this much more complex to actually use.
So what is the technical down-side for using fragments like this? I thought that was the entire point of why they exist. And if I am supposed to use a view, is there an easier way to get a result from ItemSelectorActivity?
If I understand you correctly, your view is starting an activity (ItemSelectorView starts ItemSelectorActivity). But views should be dump in Android. They should display, nothing more.
So a view should never start an activity. A view can contain a list of items maybe, but these items should be views and nothing else. Usually we use an Adapter for that: a middle man between the items to be displayed and the views that are created to represent them on the screen.
You could look into MVP to do this in a proper way. Or maybe MVVM and android architecture, if you like that better.
To answer your question, using a fragment for your use case is a better approach then a view and there is not that much overhead at all.
Hi Xamarin/MvvmCross devs,
I am having a problem with the MvxCachingFragmentCompatActivity, or I think it's a problem. I don't know if it's expected behaviour or not.
In my app I use fragments inside a container view. I'm having a problem with the FragmentBackStack when the activity is being restored after destruction. If I go three levels in from my initial fragment, go into the background and come back to the foreground only the last two views are restored. Note this is with "Don't keep activities" switched on in developer options.
Is there any specific setup/indication I have to give to MvvmCross to indicate too them that I want them to handle my viewmodel save/restore functionality. I don't see any in the samples I went through.
I use to use a manual approach with older versions of MvvmCross where I stored a reference to all my viewmodels in the ContainerViewModel and then just read that up when it's restored. But this doesn't work particularly well when you can have multiple of the same view on the backstack.
I have nothing special in the app, it's base usage of MvxFragment for all the views. I do have a custom presenter, but I only override ShowFragment so I can show popup views instead of replacing. None of the views not being restored uses this functionality, so it's not interfering with anything.
Any help would be grately appreciated.
Dane
Update 1
Keep in mind that views in this case are MvxFragments.
while researching this I found some weird backstack behaviour associated with the MvxCachingFragmentActivity. While doing the restore functionality I got fragment tag not found in cache errors, so I added this
FragmentCacheConfiguration.RegisterFragmentToCache<View, ViewModel>(typeof(ViewModel).FullName);
When I add this for my view that's displayed second (after the first view), the first view is never added too the backstack. Causing a weird back navigation bug where back would close on the second view even if AddToBackstack is set too true for the first view we navigated from.
When I remove that line the navigation works completely fine. I have no idea why that is.
There were two reasons the MvxCachingFragment was not restoring the fragment backstack properly and had the weird navigation issues after coming back from the backgroun.
I did not set RetainInstance = true; for the fragments. Since I had many I created a base fragment using MvxFragment and set Retain Instance true in the constructor.
I did not have MvvmCross-Json-Plugin installed. Which is used for deserializing the stored viewmodel information from the Bundle.
So, I'm still new on Android Fragments, but if I got it correctly, when you remove a Fragment and stack it to the back stack with addBackStack (according to Android Developer's Fragment lifecycle) you get this portion of view destroyed and when it pops up to the screen again it has to reload all it's components.
I'm specially concerned with that because one of my fragments contains a map, which is a lot of data to reload (specially if using 3G).
In that case, is it a better practice to implement hide and show to all my fragments when manipulating them?
I just see a little problem there because I'd have to create my own stack to know the order they were called and have to be shown again.
Well, it's now a huge problem, but I wanted to hear from someone if it is worth putting effort on it to implement this approach instead of the existing back stack.
Thanks in advance!
Using the ´attach´ and ´detach´ operations are another way around to your problem. The fragment instances will still exist, but deactivated and not visible, and you can recover them using the FragmentManager
If one of your fragments contain google map, it's better to use show/hide operations with the fragment views in order to avoid google map black flickering bug. Check my answer: https://stackoverflow.com/a/25206078/2999943
I have a special flow of fragments in my app, so I need to be able to switch to any fragment, while keeping fragments in memory whenever possible (so if it's tight on memory, it's ok that the fragment will be released).
So far, I've succeeded doing a replace of the current fragment, but the thing is that the previous fragment is always being destroyed, so if I go back to it (using the action bar, for example), it's re-created and that takes some time.
The reason I use fragments instead of activities is the nice usage of the action bar, the ability to put multiple fragments inside the same container, the non-flexible activities-intents usage, etc...
The reason why I don't use the "Back" stack is that I wish to go to any fragment from any fragment, since the flow can change.
Here's a snippet of my code:
Fragment fragment=... ; //get the fragment from cache or create if not available yet...
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.mainActivity_fragmentContainer, fragment).commit();
I have also tried to use ViewPager, but since I don't want to have the sliding effect (which allows you to slide to other fragments) and since one of the fragments already include a viewPager, it's an obstacle. Disabling the sliding effect on the main viewPager somehow disables it on the other one.
BTW, I'm using the android support library for the fragments and not the native API.
my question is:
how can i achieve full control of which fragment to go to , while maximizing speed and avoiding too much memory being used for fragments ?
EDIT:
for now , i use the next workaround :
for the onDestroyView , i take the parent of the created view and remove the created view from there .
for the onCreateView , if i already have the created view from before , i return it .
however , i think it's a very risky thing to do , since i'm not sure of how fragments managing work , so it might cause weird problems . plus ,i'm not sure what will happen if android decides that it has low memory - will it destroy unused fragments (which is good) or will it cause out-of-memory exceptions (which is bad) .
I agree with your comment regarding "it's very risky". Since the consequences of your approach are not really clear, I would not recommend using this.
To better understand your issue and to give you some indications:
what do you consider to be slow? I keep switching fragments "like crazy" too, and it works OK on a tablet, however on some low-end phones it takes much longer - but is still acceptable
How much work do your fragments need to be created? ie what kind of underlying data needs to be preapared each time? (again, I have several list fragments with more than 1k entries, they get prepared each time, and it's quite fast)
how do you exaclty replace fragments? In particular do you call getFragmentManager().executePendingTransactions();
One way to accomplish your goal is to use FragmentTransaction.show and FragmentTransaction.hide. You can use Fragment.isAdded to determine whether to show or to call FragmentTranscation.add the first time. The downside is that you won't get Pause/Resume events when fragments are shown and hidden; you will need to override onHiddenChanged. You will also have to manage your own stack if you want to support back navigation.