ListFragment is inflated twice - android

I have a linear layout (layout_container in skeleton.xml) which is inflated with a ListFragment (menufragment) when the activity is created. Then, when the user performs a click, this layout is replaced with another ListFragment (albumsfragment).
The problem is that when I press the back button, I go back indeed to the menufragment but the list is twice longer because it has been filled up again with the same items. How could I avoid this ?
OnCreate method, inside the Activity:
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.skeleton);
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
MenuFragment menufragment = new MenuFragment();
fragmentTransaction.add(R.id.layout_container, menufragment, "menufragment");
fragmentTransaction.commit();
}
OnClick method, inside the Activity:
public void OnMenuClick() {
AlbumsFragment albumsfragment = new AlbumsFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.layout_container, albumsfragment, "albumsfragment");
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
My Fragment class code :
public class MenuFragment extends ListFragment{
ArrayList<MenuItem> m_parts = new ArrayList<MenuItem>();
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
MenuAdapter m_adapter;
m_parts.add(new MenuItem ("item1"));
m_parts.add(new MenuItem ("item2"));
m_parts.add(new MenuItem ("item3"));
m_parts.add(new MenuItem ("item4"));
m_adapter = new MenuAdapter(getActivity(), R.layout.menu_row, m_parts);
setListAdapter(m_adapter);
So each time I go back from AlbumFragment to MenuFragment, the list in MenuFragment grows as item1,item2,item3,item4 are added once again.
I've try to add the check if (savedInstanceState == null) before the transaction of the MenuFragment but doesn't change anything.
What should I do to make MenuFragment be popped up from the back Stack without being inflated again with these items ?
Thanks.

Take a look at the Fragment lifecycle. onActivityCreated() will be called everytime when returning to aFragment. Try, instead, to move your implementation to onCreate() and that should fix your duplication issue.

Probably you can also check for the size of the arraylist and then clear it onActivityCreated() before populating the arrayList.
if(m_parts != null && m_parts.size()>0){
//Clear the contents of the arrayList m_parts
m_parts.clear();
}
And then add the data to the arrayList.

Related

Why is onCreateView in Fragment called twice after device rotation in Android?

I have simple activity and fragment transaction. What i noticed that on configuration changes oncreateView of Fragment is called twice. Why is this happening?
Activity Code Here :
#Override
protected void onCreate(Bundle savedInstanceState) {
System.out.println("Activity created");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager manager = getSupportFragmentManager();
BlankFragment fragment = new BlankFragment();
addFragmentToActivity(manager,
fragment,
R.id.root_activity_create
);
}
public static void addFragmentToActivity (FragmentManager fragmentManager,
Fragment fragment,
int frameId)
{
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(frameId, fragment);
transaction.commit();
}
Fragment Code Here :
public class BlankFragment extends Fragment {
public BlankFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank, container, false);
}
}
On first load onCreateView() is called once
But onRotation onCreateView() is called twice
why ?
Because of this transaction.replace(frameId, fragment); Really? Yes,I mean because of fragment .You already have one fragment onFirst load, When you rotate onCreate() will be called once again, so now fragment manager has old fragment ,so it methods will execute(once),and next you are doing transaction replace() which will remove old fragment and replace it with new once and again(onCreateView() will be called for second time). This is repeating for every rotation.
If you use transaction.add(frameId, fragment,UNIQUE_TAG_FOR_EVERY_TRANSACTION) you would know the reason. for every rotatation, no.of onCreateView() calls will increase by 1. that means you are adding fragments while not removing old ones.
But solution is to use old fragments.
in onCreate()of activity
val fragment = fragmentmanager.findFrgmentByTag("tag")
val newFragment : BlankFragment
if(fragment==null){
newFragment = BlankFragment()
}else{
newFragment = fragment as BlankFragment()
}
//use newFragment
Hope this solves confusion
Android automatically restores the state of its views after rotation. You don't have to call addFragmentToActivity again after rotation. The fragment will automatically be restored for you!
In your case, it happens twice because:
1. Android restores the fragment, its onCreateView is called
2. You replace the restored fragment with your own fragment, the oncreateview from that fragment is called too
do this:
if (savedInstanceState == null)
{
addFragmentToActivity(manager, fragment, R.id.test);
}

Show fragment from Array Adapter

I have application which uses several fragments for showing different tabs.
The first fragment is List View with custom cell. Each cell has its own set of buttons. I want to show different fragment when a button is clicked. To know when the desired button is clicked I added an onclick event in the array adapter for my list. Is there a way to show the new fragment from Array Adapter. What I tried so far and it didn`t worked :
final FragmentManager fragmentManager = ((Activity)mContext)getSupportFragmentManager();
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
PostFragment postFr = new PostFragment();
fragmentTransaction.replace(R.id.detail_container, postFr);
fragmentTransaction.commit();
try this code:
final FragmentManager fragmentManager = ((FragmentActivity)mContext).getSupportFragmentManager();
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
PostFragment postFr = new PostFragment();
fragmentTransaction.replace(R.id.detail_container, postFr);
fragmentTransaction.commit();
getSupportFragmentManager() is only defined for the class FragmentActivity, not for Activity
what specifically went wrong when you tried to do that?
This is the method I use to go to a new fragment. I put it in an abstract BaseActivity class (extends AppCompatActivity) so that all of my implementing activities have use of the method.
The method takes the fragment you want to go to, and a boolean variable to indicate whether or not to add the transaction to the backstack.
The activity must have a FrameLayout declared in its XML resource file, with an id of "container". This is where the fragments will be placed.
public void gotoFragment(Fragment fragment, boolean addToBackStack) {
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, fragment);
if (addToBackStack) {
transaction.addToBackStack(null);
}
transaction.commit();
}
In order to call this method from your ArrayAdapter class, pass an instance of the BaseActivity into the constructor of your Adapter.
For the Context parameter of the adapter, pass getActivity() from the fragment.
If all of your activity classes extend the BaseActivity class, you can safely say in your adapter:
public class CustomArrayAdapter extends ArrayAdapter {
private BaseActivity mActivity;
public CustomArrayAdapter(Context pContext, int pLayoutResource, List<Object> pItems) {
super(pContext, pLayoutResource);
mActivity = (BaseActivity) pContext;
}
#Override
public void onItemClick(int position) {
if (mActivity != null) {
mActivity.goToFragment(new PostFragment());
}
}

Android - Viewpager and fragments, methods not working

I have a ViewPager with two Fragments which I instantiate in onCreate of my FragmentActivity.
private List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this,Frag_1.class.getName()));
fragments.add(Fragment.instantiate(this,Frag_2.class.getName()));
this.vPagerAdapter = new Adapt(super.getSupportFragmentManager(),fragments);
vPager = (ViewPager) super.findViewById(R.id.pager);
vPager.setAdapter(vPagerAdapter);
My second Fragment has a method inside that I call to update my ListView - refreshList():
public class Frag_2 extends Fragment {
private ListView list;
private ArrayList<data> data;
private boolean firstCreation=true;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setRetainInstance(false);
}
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
super.onAttach(activity);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout, container, false);
list = (ListView) view.findViewById(R.id.lst);
//this.setRetainInstance(true);
return view;
}
public void refreshList(ArrayList <data> data){
if(data!=null){
ArrayAdapter<data> adapter = new Item_data_adapter(getActivity(),data);
list.setAdapter(adapter);}
}
}
Called from my FragmentActivity
//Something
Frag_2 fr = (Frag_2) vPagerAdapter.getItem(1);
if (fr.getView() != null) {
fr.refreshList(data);
}
It works fine until I change the orientation of the screen. Correct me if I'm wrong, but I was searching for hours and I didn't find a solution or a good explanation, the FragmentActivity is created only one time and the Fragments are attached to it but the Fragments recreate on configuration changes.
Now, when the orientation changes I don't get the View from onCreateso when I try to get the View from the Fragment it returns null and my refreshList() method isn't called. How can I fix this?
I fixed the problem this way:
In the onCreate of the FragmentActivity
if(savedInstanceState!=null){
frag1 = (frag_1) getSupportFragmentManager().getFragment(savedInstanceState, frag_1.class.getName());
frag2 = (frag_2) getSupportFragmentManager().getFragment(savedInstanceState, frag_2.class.getName());
}
else{
frag1 = (frag_1) Fragment.instantiate(this,frag_1.class.getName());
frag2 = (frag_2) Fragment.instantiate(this,frag_2.class.getName());
}
fragments.add(frag1);
fragments.add(frag2);
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, frag_1.class.getName(), frag1);
getSupportFragmentManager().putFragment(outState, frag_2.class.getName(), frag2);
}
Maybe it's not the best solution in the universe, but it looks like it works...
When u want to refresh the List do something like this :
public void setView() {
Frag_2 fr = (Frag_2) vPagerAdapter.getItem(1);
fragmentManager.beginTransaction().detach(fr).commit();
fragmentManager.beginTransaction().attach(fr).commit();
}
If you are using a dynamic fragment, you need to test first to prevent creating a second instance of a fragment.
To test whether the system is re-creating the activity, check whether the Bundle argument passed to your activity’s
onCreate() is null.
If it is non-null, the system is re-creating the activity. In this case, the activity automatically re-instantiates existing
fragments.
If it's null you can safely instantiate your dynamic fragment. For example:
public void onCreate(Bundle savedInstanceState) {
// ...
if (savedInstanceState != null) {
FragmentManager fragmentManager = getFragmentManager()
// Or: FragmentManager fragmentManager = getSupportFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}
}
The Fragment class supports the onSaveInstanceState(Bundle) method (but not the onRestoreInstanceState() method) in much the same way as the Activity class.
The default implementation saves the state of all the fragment’s views that have IDs.
You can override this method to store additional fragment state information.
If the system is re-creating the fragment from a previous saved state, it provides a reference to the Bundle containing that state to the onCreate(), onCreateView(), and onActivityCreated() methods; otherwise, the
argument is set to null.
If you want a detailed info, here's a good talk by Ken Jones of Marakana

Passing an Object from an Activity to a Fragment

I have an Activity which uses a Fragment. I simply want to pass an object from this Activity to the Fragment.
How could I do it?
All the tutorials I've seen so far where retrieving data from resources.
EDIT :
Let's be a bit more precise:
My Activity has a ListView on the left part. When you click on it, the idea is to load a Fragment on the right part.
When I enter this Activity, an Object Category is given through the Intent. This Object contains a List of other Objects Questions (which contains a List of String). These Questions objects are displayed on the ListView.
When I click on one item from the ListView, I want to display the List of String into the Fragment (into a ListView).
To do that, I call the setContentView() from my Activity with a layout. In this layout is defined the Fragment with the correct class to call.
When I call this setContentView(), the onCreateView() of my Fragment is called but at this time, the getArguments() returns null.
How could I manage to have it filled before the call of onCreateView() ?
(tell me if I'm not clear enough)
Thanks
Create a static method in the Fragment and then get it using getArguments().
Example:
public class CommentsFragment extends Fragment {
private static final String DESCRIBABLE_KEY = "describable_key";
private Describable mDescribable;
public static CommentsFragment newInstance(Describable describable) {
CommentsFragment fragment = new CommentsFragment();
Bundle bundle = new Bundle();
bundle.putSerializable(DESCRIBABLE_KEY, describable);
fragment.setArguments(bundle);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
mDescribable = (Describable) getArguments().getSerializable(
DESCRIBABLE_KEY);
// The rest of your code
}
You can afterwards call it from the Activity doing something like:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
Fragment fragment = CommentsFragment.newInstance(mDescribable);
ft.replace(R.id.comments_fragment, fragment);
ft.commit();
In your activity class:
public class BasicActivity extends Activity {
private ComplexObject co;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_page);
co=new ComplexObject();
getIntent().putExtra("complexObject", co);
FragmentManager fragmentManager = getFragmentManager();
Fragment1 f1 = new Fragment1();
fragmentManager.beginTransaction()
.replace(R.id.frameLayout, f1).commit();
}
Note: Your object should implement Serializable interface
Then in your fragment :
public class Fragment1 extends Fragment {
ComplexObject co;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Intent i = getActivity().getIntent();
co = (ComplexObject) i.getSerializableExtra("complexObject");
View view = inflater.inflate(R.layout.test_page, container, false);
TextView textView = (TextView) view.findViewById(R.id.DENEME);
textView.setText(co.getName());
return view;
}
}
You should create a method within your fragment that accepts the type of object you wish to pass into it. In this case i named it "setObject" (creative huh? :) ) That method can then perform whatever action you need with that object.
MyFragment fragment;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) {
fragment = new MyFragment();
getSupportFragmentManager().beginTransaction().add(android.R.id.content, detailsFragment)
.commit();
} else {
fragment = (MyFragment) getSupportFragmentManager().findFragmentById(
android.R.id.content);
}
fragment.setObject(yourObject); //create a method like this in your class "MyFragment"
}
Note that i'm using the support library and calls to getSupportFragmentManager() might be just getFragmentManager() for you depending on what you're working with
Get reference from the following example.
1. In fragment:
Create a reference variable for the class whose object you want in the fragment. Simply create a setter method for the reference variable and call the setter before replacing fragment from the activity.
MyEmployee myEmp;
public void setEmployee(MyEmployee myEmp)
{
this.myEmp = myEmp;
}
2. In activity:
//we need to pass object myEmp to fragment myFragment
MyEmployee myEmp = new MyEmployee();
MyFragment myFragment = new MyFragment();
myFragment.setEmployee(myEmp);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.main_layout, myFragment);
ft.commit();
Passing arguments by bundle is restricted to some data types. But you can transfer any data to your fragment this way:
In your fragment create a public method like this
public void passData(Context context, List<LexItem> list, int pos) {
mContext = context;
mLexItemList = list;
mIndex = pos;
}
and in your activity call passData() with all your needed data types after instantiating the fragment
WebViewFragment myFragment = new WebViewFragment();
myFragment.passData(getApplicationContext(), mLexItemList, index);
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.my_fragment_container, myFragment);
ft.addToBackStack(null);
ft.commit();
Remark: My fragment extends "android.support.v4.app.Fragment", therefore I have to use "getSupportFragmentManager()". Of course, this principle will work also with a fragment class extending "Fragment", but then you have to use "getFragmentManager()".
To pass an object to a fragment, do the following:
First store the objects in Bundle, don't forget to put implements serializable in class.
CategoryRowFragment fragment = new CategoryRowFragment();
// pass arguments to fragment
Bundle bundle = new Bundle();
// event list we want to populate
bundle.putSerializable("eventsList", eventsList);
// the description of the row
bundle.putSerializable("categoryRow", categoryRow);
fragment.setArguments(bundle);
Then retrieve bundles in Fragment
// events that will be populated in this row
mEventsList = (ArrayList<Event>)getArguments().getSerializable("eventsList");
// description of events to be populated in this row
mCategoryRow = (CategoryRow)getArguments().getSerializable("categoryRow");
If the data should survive throughout the application lifecycle and shared among multiple fragments or activities, a Model class might come into consideration, which has got less serialization overhead.
Check this design example
This one worked for me:
In Activity:
User user;
public User getUser(){ return this.user;}
In Fragment's onCreateView method:
User user = ((MainActivity)getActivity()).getUser();
Replace the MainActivity with your Activity Name.

Retain the Fragment object while rotating

I have developed an app in Honeycomb and I am using fragments.
This is my app
I have an Activity (Say A1) and in that there is a fragment
Initially this fragment hold the object one fragment object say (F1)
Then depending on the user actions it may change to other objects F2,F3 ....
What my problem is
When The user rotate the device the activity is recreated and which make F1 as the fragment object even though before rotating it wasn't
What is the way to retain the fragment object while rotating?
I used setRetainInstance(true); but it didn't work for me
And I have added the fragment by code in my onCreate function like this
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
Fragment homeFragment = new Home();
fragmentTransaction.add(R.id.mainFragement, homeFragment);
fragmentTransaction.commit();
}
By default Android will retain the fragment objects. In your code you are setting the homeFragment in your onCreate function. That is why it is allways some homeFragment or fl what ever that you set in onCreate.
Because whenever you rotate, the onCreate will execute and set your fragment object to the first one
So the easy solution for you is check whether savedInstanceState bundle is null or not and set the fragment object
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(null == savedInstanceState) {
// set you initial fragment object
}
}
You need to give your Fragment a unique tag, and check whether this Fragment is already added to your Activity already or not.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String tag = "my_fragment";
FragmentManager fragmentManager = getFragmentManager();
if(fragmentManager.findFragmentByTag(tag) == null) {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
Fragment homeFragment = new Home();
fragmentTransaction.add(R.id.mainFragement, homeFragment, tag);
fragmentTransaction.commit();
}
}
Checking whether savedInstanceState is null is not a safe way to check whether your fragment is already set - it will work in most cases, but in some cases (such as when the device is on low memory), Android may kill your Activity, which could break your application.
To see this in action, tick "Don't keep activities" in the device's development options (the setting is available in Android 4.0+, not sure about earlier versions). When you open a new activity, your first activity is destroyed. When you return to it (by pressing back), it is created again, and savedInstanceState is not null. However, your fragment is not in the activity anymore, and you have to add it again.
EDIT - Showing the original principle but with SupportFragmentManager
public class ActivityAwesome extends AppCompatActivity
{
private final String TAG = getClass().getSimpleName();
private FragmentHome mHomeFragment;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(TAG);
if(fragment == null)
{
// Create the detail fragment and add it to the activity using a fragment transaction.
mHomeFragment = new FragmentHome();
fragmentManager.beginTransaction()
.add(R.id.fragment_container, mHomeFragment, TAG)
.commit();
}
else
{
// get our old fragment back !
mHomeFragment = (FragmentHome)fragment;
}
}
}
this comes in especially useful if you want to manipulate the fragment (in this case mHomeFragment) after rotating your device
Use onAttachFragment() in your Activity to reassign the object:
#Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof MyFragment)
this.myFragment = (MyFragment) fragment;
}
I defined a Fragment in activity's layout, onSaveInstanceState in the Fragment does get called, but the savedInstanceState Bundle in the Fragment's onCreatView comes as null.
The reason was that my Fragment did not have a ID in XML:
android:id="#+id/compass_fragment" ...
just rewiring #Ralf answer to be more dynamic, no need to specify a certain fragment to retain, but in case you want to specify, it is also possible :
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//set Home/Main/default fragment
changeFragmentTo(HomeFragment.newInstance(), FRAGMENT_TAG_HOME_FRAGMENT);
if (getCurrentFragment() != null) {
//if screen rotated retain Fragment
changeFragmentTo(getCurrentFragment(), getCurrentFragment().getTag());
}
}
private Fragment getCurrentFragment() {
//fl_main_container is FarmeLayout where I load my Fragments
return getSupportFragmentManager().findFragmentById(R.id
.fl_main_container);
}
/**
* changeFragmentTo(Fragment fragmentToLoad, String fragmentTag)
*
* #param fragmentToLoad : dataType > v4.app.Fragment :: the object of the fragment you want to load in form of MyFragment() or MyFragment().newInstance()
* #param fragmentTag : dataType > String :: a String which identify the "tag" of the fragment in form of "FRAGMENT_TAG_MY_FRAGMENT", Value must be stored in {#link models.MyConstants}
*/
public void changeFragmentTo(Fragment fragmentToLoad, String fragmentTag) {
if (getSupportFragmentManager().findFragmentByTag(fragmentTag) == null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fl_main_container, fragmentToLoad, fragmentTag)
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.addToBackStack(fragmentTag)
.commit();
} else {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fl_main_container, fragmentToLoad, fragmentTag)
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit();
}
}
}
You can simply set the RetainInstance property inside OnCreate of the fragment class.
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
RetainInstance = true;
}
Retain the Fragment object while rotating

Categories

Resources