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;
}
Related
I am trying to write a test app which contains an activity. There are two fragments inside this activity, which are defined as LeftFragment and RightFragment. I used getFragmentManager().findFragmentById() to get connection from each other fragments. By using that methode I am able to get an LeftFragment object from RightFragment, but not RightFragment object from LeftFragment. It just works only oneway. I am doing this, because I want to call some operations from other fragment, that return some values. I was thinking about using EventBus but I failed too. How can I achive that?
Here is my LeftFragment
public class LeftFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
RightFragment rightFragment = (RightFragment) getFragmentManager().findFragmentById(R.id.rightFragment);
if (rightFragment != null){
makeToast(rightFragment.getMessageFromRight());
}else {
makeToast("does not found rightFragment");
}
return inflater.inflate(R.layout.fragment_left, container, false);
}
public String getMessageFromLeft(){
return "Hi! Im left";
}
private void makeToast(String text){
Toast.makeText(getContext(),text,Toast.LENGTH_SHORT).show();
}
}
And here is my RightFragment
public class RightFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
LeftFragment leftFragment = (LeftFragment) getFragmentManager().findFragmentById(R.id.leftFragment);
if (leftFragment != null){
makeToast(leftFragment.getMessageFromLeft());
}else {
makeToast("does not found leftFragment");
}
return inflater.inflate(R.layout.fragment_right, container, false);
}
public String getMessageFromRight(){
return "Hi! Im right!";
}
private void makeToast(String text){
Toast.makeText(getContext(),text,Toast.LENGTH_SHORT).show();
}
}
There are many ways to communicate between 2 fragments . If 2 fragments loaded at the same time. I usaually use one of 2 ways below to do it.
You can use this link using obserable pattern to communication 2 fragments.
you can use EventBus lib for communication, it 's very simple
Your issue:
By using that methode I am able to get an LeftFragment object from
RightFragment, but not RightFragment object from LeftFragment
I think your problem is LeftFragment is intitialized previous, so you can find it from RightFragment. Your solution is ok, using EventBus. YOu need to review your codes to find the issue. You can test by creating other methods, after 2 fragment was initialized.
For ex: click button in LeftFragment, toast a message in RightFragment.
Probably what is happening is that the Left Fragment is getting the OnCreateView() call first, at which point the Right Fragment has not been inflated yet (therefore it can't be "found" by findFragmentbyId()).
I would suggest moving the code that gets the references to the other fragments into onStart(), and only inflate the fragments in onCreateView().
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;
}
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 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.
in my app I'm using one activity and two fragments. The app uses a layout with a container so the fragments are added via transactions. The first fragment contains a listview and the other fragment a detail view for the listview items.
Both fragments use setRetainInstance(true). The fragments are added via a replace transaction and addToBackStack(null) is set. The listfragment contains an instance variable which holds some infos for the list. Now I'm changing to detail and press back and the instance variable is null. I read about setRetainInstance and addToBackStack and removed addToBackStack, but even then the instance variable is null.
Does anyone know what I might be doing wrong?
regards,
Thomas
setRetainInstance(true) will tell the FragmentManager to keep the fragment around when the containing Activity is killed and rebuilt for some reason. It doesn't guarantee that the Fragment instance will stick around after a transaction to add or replace. It sounds like your adapter is being garbage collected and you're not creating a new one.
A more generally easy solution would be to make a viewless Fragment to retain your ListAdapter. The way you do this is to create the Fragment, set the retain instance to true, and return null in the method onCreateView(). To add it, just called addFragment(Fragment, String) via the FragmentTransaction. You never remove or replace it, so it will always stay in memory for the length of the app. Screen rotations won't kill it.
Whenever your ListFragment is created, in onCreateView() get the FragmentManager and use either the method findFragmentById() or FindFragmentByTag() to retrieve your retained fragment from memory. Then get the adapter from that fragment and set it as your adapter for the list.
public class ViewlessFragment extends Fragment {
public final static string TAG = "ViewlessFragment";
private ListAdapter mAdapter;
#Override
public ViewlessFragment() {
mAdapter = createAdater();
setRetainInstance(true);
}
#Override
public void onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return null;
}
public ListAdapter getAdapter() {
return mAdapter;
}
}
public class MyListFragment extends ListFragment {
final public static String TAG = "MyListFragment";
#Override
public void onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View returnView = getMyView();
final ViewlessFragment adapterFragment = (ViewlessFragment) getFragmentManager().findFragmentByTag(ViewlessFragment.TAG);
setListAdapter(ViewlessFragment.getAdapter());
return returnView;
}
}
public class MainActivity extends FragmentActivity {
#Override
public void onCreate(Bundle icicle) {
// ... setup code...
final FragmentManager fm = getSupportFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
ViewlessFragment adapterFragment = fm.findFragmentByTag(ViewlessFragment.TAG);
if(adapterFragment == null) {
ft.add(new ViewlessFragment(), ViewlessFragment.TAG);
}
ft.add(R.id.fragmentContainer, new MyListFragment(), MyListFragment.TAG);
ft.commit();
}
}