The main page of my application has a FrameLayout.
I'm instantiating two fragments when the activity starts, and I'm trying to use a menu button to swap between the fragment.
scanHistoryFrag = new HistoryFragment();
scanFrag = new ScanFragment();
I never replace these objects - I use the same ones throughout the lifecycle of the application. However, when I swap them in my FrameLayout...
private void ChangeFragment(Android.Support.V4.App.Fragment fragment)
{
Android.Support.V4.App.FragmentTransaction ft = SupportFragmentManager.BeginTransaction();
ft.Replace(Resource.Id.fragmentContainer, fragment);
ft.Commit();
}
OnCreate and OnCreateView are called on the Fragment again... which means any adjustments I made post creation on that fragment are overwritten with initial values again. I can't seem to find any explanation for why this is happening or how I might avoid it.
The ChangeFragment method is being called by OnOptionsItemSelected, as I'm using a menu button to toggle them.
I never replace these objects - I use the same ones throughout the lifecycle of the application.
Initialization of a subclass of Fragment just create a instance of this class object, the constructor of this class will be called, but it will not go through the lifecycle of Fragment unless this Fragment is added, for more information, you can refer to Fragments. To understand it easier, I personal think the instance saves the data state of this Fragment class, but the events of lifecycle handle the view state of this Fragment.
which means any adjustments I made post creation on that fragment are overwritten with initial values again.
Yes, you're right. To avoid overwritting with initial values again, we can cache the fragment's view in OnCreateView for example like this:
private View rootView;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Use this to return your custom view for this Fragment
// return inflater.Inflate(Resource.Layout.YourFragment, container, false);
if (rootView == null)
{
//first time creating this fragment view
rootView = inflater.Inflate(Resource.Layout.fragmentlayout1, container, false);
//Initialization
//TODO:
}
else
{
//not first time creating this fragment view
ViewGroup parent = (ViewGroup)rootView.Parent;
if (parent != null)
{
parent.RemoveView(rootView);
}
}
return rootView;
}
Related
I have five fragments a user can switch between. One of these fragments loads a list of users from the server to populate the UI list on the fragment. I need the list information to persist if a user swipes to a different fragment and then swipes back to the original. I do not want the fragment to reload the users every time a user leaves the fragment and goes back.
I am looking at setRetainInsance(true) and was wondering if this is possible solution? What would be the best way for the fragment to retain the information without being created from scratch each time.
I am using this to switch between fragements -getSupportFragmentManager().beginTransaction().replace(R.id.searchLayout, ratingFragment).commit();
A Fragment is Just like any other object.
on Fragment transaction , the Fragment does not call OnCreate() method instead it starts from onCreateView , therefore , load your users and save it an instance variable and assign it in onCreate()
Example
class MyFragment extends Fragment{
List<users> userList;
void onCreate(){
userList = getUserList();}
//the list is loaded during Oncreate();
now imagine you have replaced the Fragment
now According to Andorid Framework , onCreate() is not Called again
instead onCreateView() is called
void onCreateView(){
//you can check whether instance Variable is initialised or not
if(userList != null) {
listview.setAdapter(new Myadapter(this,userList);
Replace the fragment by adding it to backstack.
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.addToBackStack(tag);
fragmentTransaction.replace(container, fragment, tag);
fragmentTransaction.commit();
Also create object of View and return it if it's not null.
private void View view ;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
if (view != null)
return view;
view = inflater.inflate(R.layout.fragment_browse_recipe, container, false);
//initialize layout views
return view;
}
Here's the problem
I have a Fragment class DisplayFragment and I already have one show in the content frame, then I do
DisplayFragment a = DisplayFragment.newInstance();
getSupportFragmentManager().beginTransaction()
.replace(R.id.contentFrame, DisplayFragment)
.commit();
Then I want to get the view of fragment a using View v = a.getView();, but it return a null view.
Can anyone tell me why? Cause I have to change some view setting of the new Fragment.
onCreateView() in DisplayFragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_display, container, false);
//Some TextView setup
Button button = (Button) view.fineViewById(R.id.button);
return view; }
You need to use delay or put your View v = a.getView(); in loop which will watch first that your fragment is successfully attached, created and added in your activity or not. For this you can check with this isAdded() and isInLayout() if both return true then only call getView()
Now why this, as you add/replace fragment with commit with will fragment will class will be execute first it'll be start Fragment life cycle that is onAttach(), onCreate(), onCreateView() and so on. Now you will getting null from getView() just because your view is not created still. Fragment Life Cycle. If you doubt regarding this let me know.
I assume that you call View v = a.getView(); right after commit?
Because getView method only return not null value after onCreateView returned.
In your case, after called commit(), it take time to complete all the lifecyle callback of DisplayFragment a (from onCreate -> onCreatedView,..).
So that, right after commit(), the getView method still return null.
Declare DisplayFragment a; as Class Reference. Because after commit you want to just access View v = a.getView(); it unavailable due to fragment Life Cycle. You can get after execute block of code.
Make sure you have to mentioned OnCreateView() Method on class DisplayFragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (container == null) {
// We have different layouts, and in one of them this
// fragment's containing frame doesn't exist. The fragment
// may still be created from its saved state, but there is
// no reason to try to create its view hierarchy because it
// won't be displayed. Note this is not needed -- we could
// just run the code below, where we would create and return
// the view hierarchy; it would just never be used.
return null;
}
Log.i("Right", "onCreateView()");
return inflater.inflate(R.layout.right, container, false);
}
I had same problem as like you.
As the transaction of fragment did not takes place immediately. Thats why sometime the findViewById() does not work properly. But we can execute the pending transaction by using executePendingTransactions() method. I have used following code in my project to execute the pedingTransactions.
Fragment f = (Fragment)(FragmentClass.class).newInstance();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.your_fragment_container_name,f).commit();
fm.executePendingTransactions(); //Notice the FragmentManager Class object
I am having a hard time understanding how the fragment lifecycle relates to switching between fragments in the back stack. Please bear with me if my question exposes more than one misconception.
Here is my code:
public class SomeFragment extends Fragment {
private SomeCustomView customView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.some_fragment, container, false);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Create the child view
customView = (SomeCustomView) getView().findViewById(R.id.some_fragment_child_view);
customView.initializeMyCustomView();
}
}
As you can see, my fragment has a child view. The child view is a custom one. Here's code:
public class SomeCustomView extends SurfaceView implements SurfaceHolder.Callback {
private boolean aVariableWhichMustPersistForLifetimeOfApplication;
}
Whenever this fragment is added to the back stack and then later restored, the variable customView is recreated, and so I loose the value of aVariableWhichMustPersistForLifetimeOfApplication. This is creating all sorts of problems for me.
The application started out using an Activity that only displayed SomeCustomView and there were no fragments. Now I have to add functionality and so I have turned the custom view into a fragment, and thus I arrive at this problem.
I found an answer which works for me. The FragmentTransaction class has a number of methods which allow you to switch fragments in/out. (Android documentation for FragmentTransaction is here and a great StackOverflow explanation is here.)
In my case, I wanted SomeFragment to never loose the data contained in its view. To do this, use this code:
SomeFragment fragment = new SomeFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(R.id.activity_fragment_placeholder, fragment, "some_fragment");
transaction.commit();
and then later:
getFragmentManager().beginTransaction().hide(fragment).commit();
You can now add/attach a different fragment to R.id.activity_fragment_placeholder. Notice that I'm using hide() rather than replace(), that's the key difference that keeps the view from being destroyed. When you want the fragment back, you can use show() or Android will do this automatically when the user clicks "Back" if you use addToBackStack() when adding/attaching your other fragment.
I am creating a Fragment Activity with tabs. Now whenever i select a tab, corresponding fragment gets recreated and its
onCreateView()
method is called.
But what i want is to reuse the view of the fragment so that whenever a tab is selected system does not call
onCreateView()
of fragment instead it shows the previous view of the fragment if it exists.
Please reply soon.
You may want to show and hide the fragments instead of adding and removing them when selecting a tab.
For example:
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (frag.isAdded()) {
transaction.show(R.id.layout, frag);
} else {
transaction.add(R.id.layout, frag);
}
transaction.commit();
setOffScreenPageLimit() method can be used to set how many fragments you want to keep alive even if they are not visible to the user.
Try this, it works for screen rotation and should work for your situation as well:
1) When you add fragment for the first time, create it with name parameter:
getSupportFragmentManager()
.beginTransaction()
.replace(<containerID>, <fragment>, YOUR_FRAGMENT_NAME_CONST)
.commit();
2) When you need to find it:
YourFragmentType fragment;
fragment = (YourFragmentType) getSupportFragmentManager()
.findFragmentByTag(YOUR_FRAGMENT_NAME_CONST);
if (fragment != null){
//TODO set data to the existing fragment
}
else{
//TODO create and initialize your fragment
}
Edit:
You should differentiate Fragment object creation and onCreateView() being called.
It's right that you should avoid unnecessary object creation and so reuse fragments.On the other hand (as for my experience) it's better to adhere Android's onCreateView() politics to guarantee consistent user experience.
If you really want to save CPU time and avoid re-inflating complicated view (and settle all issues yourselves) - you may just check it is null like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// View
if (mView == null) {
mView = inflater.inflate(R.layout.your_fragment, container, false);
...
}
...
}
I have an app with hierarchy like this:
FragmentTabHost (Main Activity)
- Fragment (tab 1 content - splitter view)
- Fragment (lhs, list)
- Framment (rhs, content view)
- Fragment (tab 2 content)
- Fragment (tab 2 content)
All fragment views are being inflated from resources.
When the app starts everything appears and looks fine. When I switch from the first tab to another tab and back again I get inflate exceptions trying to recreate tab 1's views.
Digging a little deeper, this is what's happening:
On the first load, inflating the splitter view causes its two child fragments to be added to the fragment manager.
On switching away from the first tab, it's view is destroyed but it's child fragments are left in the fragment manager
On switching back to the first tab, the view is re-inflated and since the old child fragments are still in the fragment manager an exception is thrown when the new child fragments are instantiated (by inflation)
I've worked around this by removing the child fragments from the fragment manager (I'm using Mono) and now I can switch tabs without the exception.
public override void OnDestroyView()
{
var ft = FragmentManager.BeginTransaction();
ft.Remove(FragmentManager.FindFragmentById(Resource.Id.ListFragment));
ft.Remove(FragmentManager.FindFragmentById(Resource.Id.ContentFragment));
ft.Commit();
base.OnDestroyView();
}
So I have a few questions:
Is the above the correct way to do this?
If not, how should I be doing it?
Either way, how does saving instance state tie into all of this so that I don't lose view state when switching tabs?
I'm not sure how to do this in Mono, but to add child fragments to another fragment, you can't use the FragmentManager of the Activity. Instead, you have to use the ChildFragmentManager of the hosting Fragment:
http://developer.android.com/reference/android/app/Fragment.html#getChildFragmentManager()
http://developer.android.com/reference/android/support/v4/app/Fragment.html#getChildFragmentManager()
The main FragmentManager of the Activity handles your tabs.
The ChildFragmentManager of tab1 handles the split views.
OK, I finally figured this out:
As suggested above, first I changed the fragment creation to be done programatically and had them added to the child fragment manager, like so:
public override View OnCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstance)
{
var view = inflater.Inflate(Resource.Layout.MyView, viewGroup, false);
// Add fragments to the child fragment manager
// DONT DO THIS, SEE BELOW
var tx = ChildFragmentManager.BeginTransaction();
tx.Add(Resource.Id.lhs_fragment_frame, new LhsFragment());
tx.Add(Resource.Id.rhs_fragment_frame, new RhsFragment());
tx.Commit();
return view;
}
As expected, each time I switch tabs, an extra instance of Lhs/RhsFragment would be created, but I noticed that the old Lhs/RhsFragment's OnCreateView would also get called. So after each tab switch, there would be one more call to OnCreateView. Switch tabs 10 times = 11 calls to OnCreateView. This is obviously wrong.
Looking at the source code for FragmentTabHost, I can see that it simply detaches and re-attaches the tab's content fragment when switching tabs. It seems the parent Fragment's ChildFragmentManager is keeping the child fragments around and automatically recreating their views when the parent fragment is re-attached.
So, I moved the creation of fragments to OnCreate, and only if we're not loading from saved state:
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
if (savedInstanceState == null)
{
var tx = ChildFragmentManager.BeginTransaction();
tx.Add(Resource.Id.lhs_fragment_frame, new LhsFragment());
tx.Add(Resource.Id.rhs_fragment_frame, new RhsFragment());
tx.Commit();
}
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstance)
{
// Don't instatiate child fragments here
return inflater.Inflate(Resource.Layout.MyView, viewGroup, false);
}
This fixed the creation of the additional views and switching tab's basically worked now.
The next question was saving and restoring view state. In the child fragments I need to save and restore the currently selected item. Originally I had something like this (this is the child fragment's OnCreateView)
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance)
{
var view = inflater.Inflate(Resource.Layout.CentresList, container, false);
// ... other code ommitted ...
// DONT DO THIS, SEE BELOW
if (savedInstance != null)
{
// Restore selection
_selection = savedInstance.GetString(KEY_SELECTION);
}
else
{
// Select first item
_selection =_items[0];
}
return view;
}
The problem with this is that the tab host doesn't call OnSaveInstanceState when switching tabs. Rather the child fragment is kept alive and it's _selection variable can be just left alone.
So I moved the code to manage selection to OnCreate:
public override void OnCreate(Bundle savedInstance)
{
base.OnCreate(savedInstance);
if (savedInstance != null)
{
// Restore Selection
_selection = savedInstance.GetString(BK_SELECTION);
}
else
{
// Select first item
_selection = _items[0];
}
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance)
{
// Don't restore/init _selection here
return inflater.Inflate(Resource.Layout.CentresList, container, false);
}
Now it all seems to be working perfectly, both when switching tabs and changing orientation.