I'm having a problem instantiating Fragments in my program using the Support Library implementation. Here's a brief description of the task I'm attempting to perform and some of my attempts which haven't yet borne fruit:
The UI of my application is subject to change to meet user preferences. In order to do this, I'm using a Fragment for each different layout and replacing the active Fragment in the UI at a given time as per the user's instructions. Here are some ways I've tried (and failed) to do this:
I've tried adding the Fragments as non-static inner classes in my Activity. This approach worked so long as the user did not rotate the device. As soon as the user rotated the device, the application crashed (this is true for Portrait -> Landscape rotation and for Landscape -> Portrait rotation). Upon checking the issue using the emulator, I was getting an InstantiationException. I checked SO for some help, which led me to:
Implement the Fragment as a static inner class. When the Fragment initiates, it will expand its layout, and then from later in the control flow of the Activity, I can do stuff to the Fragment's subviews (in particular, add listeners to the buttons). Unfortunately this didn't work because I couldn't refer to the Fragment's subviews using [frag_name].getView().findViewById(). Something about referencing static objects in a non-static context. Once again, I checked SO, which led me to:
Implement the Fragment as a separate class altogether from the Activity. This seems to be what the Dev docs on developer.android.com recommend. Upon doing this, everything seems to compile fine, but when I try to refer to the Fragment's subviews (once again, using [frag_name].getView().findViewById()), I get a NullPointerException. When I add System.out.println() statements across my code to find out exactly what is happening, I find that the print statement inside onCreateView in the fragment is never getting fired, which implies that onCreateView is never getting triggered.
So now, I'm stuck. What am I doing wrong? The precise implementation of this isn't as important as learning something from the experience so I can get better at Android development, so if seperate classes are better than static classes or vice-versa, I don't really care which I use.
Thanks.
Figured it out. Turns out that in order to do what I wanted, I had to register the Activity as a Listener to each of the Fragments and pass "ready to enable buttons" messages back and forth between the two. To anyone using this question for further research, the guide on how to do that is located on the Android Developer guide, here: http://developer.android.com/training/basics/fragments/communicating.html
Related
While peer reviewing a colleague's code I noticed she created a new Activity and all functionality is just there without a Fragment.
In the old days of Android, this is what we did, but the last few years I and my peers always took the approach that every Activity should have at least one Fragment and no actual code should be written in the Activity apart from loading the Fragment of-course and maybe some higher end procedures.
I want to argue for always using at least one Fragment in every Activity, but I couldn't find compelling arguments about why it is better than a no-fragment Activity.
The out of the box argument I can think of is that it will be easier adding new fragments if needed, but if we know this will never be a necessity, why bother with a single fragment Activity?
Fragment is easier to extend and test, if you are writing another new feature, it is helpful for separating code. And you can also move your fragment code to another place easy.
Of cource, if you are sure that your code is very simple and stable, like demo or temp test code, you can also use Activity without fragment.
I need to understand how to use MvxCachingFragmentCompatActivity. I have asked this question before previous question, but I got a piece of example code, which is helpful but not what I needed. What I need is an understanding of how to use it.
First of all I have one activity and all my views are fragments.
My big assumption here is that using MvxCachingFragmentCompatActivity will enable me to restore my application navigation hierarchy if my activity is torn down and needs to be restored. Can someone confirm if this is correct.
If this is correct how do I use it. For example
Do I need to implement Save and Restore state in the view models? Is
there anything else the developer needs to do?
What does the MvxFragmentAttribute parameter IsCacheableFragment
actually do as regards caching fragments?
What performs the action of recreating my fragment hierarchy when an
activity is restored?
It would be great if there was some documentation around this.
I need to know this as my Activity is being torn down and then restored after I use another Activity for a camera feature. When the Activity restores itself the ViewModel for my fragments are null.Also I am finding Close(this) does not work in my view model. I'm sure I am not doing everything I need to do to make this work, but I need guidance on how it is supposed to be used.
Any help would be appreciated, maybe someone from the MvvmCross team. I'm really stuck here. I would prefer a description of the behaviour rather than point to a sample, but both would be great.
[Updated]
So I built a debug version of the V4 and V7 MvvmCross libraries and set about debugging. As far as I can tell as long as you add the following attributes to your fragment class this should set about caching your fragments.
[MvxFragment(typeof(MainActivityViewModel), Resource.Id.contentFrame, AddToBackStack = true, IsCacheableFragment = true)]
[Register("com.dummynamespace.MyFragment")]
Note the lowercase namespace is important, your class name can be mixed case.
However I am still seeing problems after my activity is destroyed and re-created. In my case I am actually seeing my activity destroyed and recreated more than once in quick succession. One example is that I cannot close the view after the activity destroyed and recreated. This seems to be due to the fact that the code in GetFragmentInfoByTag (MvxCachingFragmentCompatActivity class) is returning the wrong information needed to close the view. The close functionality needs the ContentId from the returned IMvxCachedFragmentInfo, however this is returning it as 0. Also the AddToBackStack property is set to false. Below I have listed what is returned in the fragment info
AddToBackStack = false
CacheFragment = true
CachedFragment = null
ContentId = 0
FragmentType = This is set to the correct fragment type
Tag = This is set to the corresponding view model for the fragment
Before the activity is destroyed and recreated the the fragment info is correct.
I am using MvvmCross 4.2.3. Has anyone else experience this?
Update 02/03/2017
I found out that my activity was being destroyed and recreated not due to memory but due to the camera orientation. We found it only failed when we held the camera in landscape mode.
The issue regarding the ContentId being set to 0 was caused by my app not being able to resolve the IMvxJsonConverter implemenation. This occurs when the MvvmCross Json plugin is not installed. Also you have to add the following to your App.cs file so it can be registered
Mvx.RegisterType<IMvxJsonConverter, MvxJsonConverter>();
If this is not done, then the Try.Resolve fails and the code that uses it is skipped over. Sometimes it is done silently other times it outputs a log. IT would seem to me that this should probably be fatal if you expect your app to survive the activoty being torn down and reconstructed.
Also one the MvvmCross Json plugin is installed you have to implement the Save and Restore state pattern in your view models save-Restore
Update new problem 08/03/2017
I am testing the restore of every view in my app. I am doing this by allowing the orientation to be changed which destroys my MvxCachingFragmentCompatActivity and then re-creates it.
When the activity is destroyed my fragment is also destroyed. At this point I tidy up my view model to ensure it will be free'd up and will not cause a memory leak.
However I have hit a problem where when OnCreate is called. It seems to do two things
Get a view model from the MvxFragmentExtensions OnCreate method by
calling into the view model cache
Then calls RestoreViewModelsFromBundle
The problem is that the call to MvxFragmentExtensions OnCreate (1) calls into the view model cache and returns a view model which has not been Started e.g Start() called on it, but this is used to set the DataContext.
After RestoreViewModelsFromBundle (2) is called the DataContext is not set again event though it has gone through the Constructor->Init->RestoreState->Start set up. So I now have a view model which is not setup properly and so my view does not work.
When I took out my code to tidy the view models, I got a bit further as the cached view model set by (1) now had the correct data. But I am hitting other problems because it is attempting to create a new view model due to the call to RestoreViewModelsFromBundle (2). As a short term fix is there anyway I can force the view model created as part of the restore process to be set as the ViewModel
Can someone from the MvvmCross team please help out with some information as to what is happening here and why?
For my current project, I will be using this SlidingUpPanel library.
The sliding up panel will host a fragment that will contain application/global level information. This panel will be in every activity in my application. This will be very similar to the way Google Play music works.
My question is, what is the best way to persist the sliding up panel fragment throughout my application? I see myself going about this in two ways...
Storing the fragment inside the Application class and loading it at the start of every activity (somehow, not even sure if this is a possibility).
Storing the data that it will display in the Application class & loading a new instance of the fragment, passing in the persisted data.
Which one of these two ways would be the best? Is 1. even possible? Are there any coding landmines with these approaches?
Storing a fragment to persist throughout the application lifecycle would be pretty unorthodox, mainly because the fragment class should be able to follow it's normal lifecycle events (in this case, mainly onPause and onResume) instead of being stuck somewhere in the application class.
It is definitely common practice to store the data and load it each time you display the fragment. If you want to enable some sort of cacheing or singleton pattern to access the data, it should most likely be with another object that the fragment can access but is not a member within the fragment.
There is a good video from google about leaking views and it touches briefly on the pitfalls of doing some similar to what you're proposing in bullet #1.
I think the structure of your app looks like it should be a single activity where that bar is in it, then the main content is a Fragment that you replace and use addToBackStack on in order to maintain the use of the back button. Otherwise, you are going to have a lot of repeated code with solution 2 (which means a lot of repeated work in the case of bugs etc., not very maintainable), or leak views using solution 1.
More info on providing a proper back implementation
I'm using the viewpagerindicator library (http://viewpagerindicator.com/) to create some sort of wizard for my android app. It works fine and does exactly what I want.
I would like to "extend" the functionality a bit by having "previous"/"next" buttons in my ActionBar - pretty much as in Android's "Done Bar" tutorial - to step through the wizard. Works like a charm, too.
HOWEVER:
I would like to display information about the "next" & "previous" fragment in the ActionBar's buttons. Information I pass to the fragments that live in the ViewPager at the time of their "creation" (actually at the time of their object instantiation - using the classical "newInstance(...)" approach to create the instance of my fragment, store the parameters in a Bundle and extract them in the fragment's "onCreate" method). The same way the template does it, when you create a new fragment for your project.
So, this information is the thing I actually want to display in my wizards button to know what fragment is next and which was last.
The type of this information is not important for my problem. It could be a String or an icon or an int or ... anything else you want.
However, wherever I've tried to access my fragments data, the fragment has not yet been fully initialized (meaning its "onCreate" method has not been called yet).
I've tried it in the host fragment's "onViewCreated" method, because I thought that's where all its subviews should be initialized already (at least their "onCreate" method should have been called, I thought), but it seems that this is handled differently for ViewPager to retain only the number of fragments in memory that was set by setOffscreenPageLimit.
So, what I'm looking for (and probably just missing) is the correct callback method here. One that is called when the ViewPager's next Fragments have been loaded and initialized. If such a callback exists, I could place my little piece of code there to update the text in my "previous"/"next" buttons within the ActionBar.
Any help, comments, ideas are highly appreciated. If needed, I can also try to attach some code sample to better explain my setup, but I think it should be easy enough to understand what my problem is.
Thanks in advance!
P.S.: I also tried to do this by using EventBus to send "onFragmentInitialized" messages from my fragments within in the ViewPager and the hosting fragment. It actually worked, but it does not seems the proper way to do this.
When a Fragment's onCreate Method is called, its already preparing to be displayed, and practically its past the point where its considered a Next or Previous fragment instead its considered current.
A fragment's onCreateViews method is called after committing a transaction in the FragmentManager. which takes less than 1 sec to bring it in front of the user (depending on the device and runtime environment)
But in your case, your data should be initalized outside the Fragment that uses it, and displayed where ever you want by passing the data itself then displaying whatever you want form it.
decouple your data from android objects (Fragment, Activity ...) and you should be able to load, maintain, access it cleanly and without worrying about their callbacks.
The Fragment's arguments can be read and loaded in its onAttach callback rather than onCreate, the Activity will then (after onAttach is complete) get a onAttachFragment callback with the Fragment as a parameter. However, I doubt onAttachFragment will be called when switching between already loaded pages in the view pager.
If not, you could have the fragment notify the activity (through an interface) that it is now active during its onActivityCreated, onViewCreated or similar method.
But it sounds more like the activity should register as a page changed listener to the ViewPager itself, and update its state depending on the page rather than which fragment is active.
As a side note, ViewPagerIndicator is quite old now (hasn't been updated in 3 years), a more modern approach is the SlidingTabs example from Google, which has been built into a library available here: https://github.com/nispok/slidingtabs
The android developer tutorials recommend me using the host activities of fragments to pass around data and whatnot, so why is there a set/get target fragment method?
My application thus far contains a host activity, and a fragment, which has a button that launches a DialogFragment, in which there is a button that launches ANOTHER DialogFragment. Using setTargetFragment/getTargetFragment has made this whole ordeal somewhat confusing though, so I am thinking of reimplementing to let my main activity handle DialogFragment.show methods through my main fragment's custom interface.
Is this the right way of thinking? Or is there no harm in using setTargetFragment? Can anyone provide good and bad examples of using it?
Also, you may end up with exception of no target fragment found in fragment manager. This happens if after rotation (or other config change) your target fragment will not be readded to the fragment manager by the time when caller fragment will be adding.
Imagine you have some sort of Confirmation fragment which you add from MainFragment as so:
ConfirmationFragment frag = new ConfirmationFragment();
frag.setTargetFragment(this, 0);
getFragmentManager().beginFragmentTransaction().add(R.id.container, frag).commit();
Now on some confirmation button click you invoke a method from MainFragment by calling:
((MainFragment)this.getTargetFragment()).onUserSelectedGoodButton();
This is pretty and simple, but if you will rotate the screen and for some reason ConfirmationFragment will be added to FragmentManager before MainFragment, exception will be thrown, stating that target fragment is not found in the fragment manager
I don't think there is implicit harm in using setTargetFragment, however, I would only use it in very specific circumstances. For example, if the target fragment is only going to ever be used by the fragment (taking into account object reuse and designing your classes to be reusable when possible) and even then, sparingly.
By using them too much, you will end up with what you're seeing - confusing code that is hard to follow and maintain. On the outset, by marshaling everything through your activity you maintain a "flat" hierarchy that is simple to follow and maintain.
I think the decision to use setTargetFragment or not is a coding-style/philosophical one that, with wisdom and experience, it "feels" right or wrong. Maybe on your case, by evidence that you are questioning your older code, you are gaining that wisdom :)