Fragment ListView doubles on return - java

I use a custom adapter for my ListView in a fragment. When I go to a second fragment and press the back key to go back, though, my list doubles (everything from my ArrayList is added again to the listView). This happens everytime I go to my second fragment and come back (the ArrayList gets appended to the back again). I tried checking if the adapter is null, but apparently it's always null when you go back to the fragment.
I tried moving my code into the onResume() and the onStart() functions from this post [https://stackoverflow.com/questions/21446361/android-listview-duplicates-itself-when-i-launch-a-new-activity-and-press-back], but that doesn't fix my code :(
In addition, I tried adding arr.clear() and notifyAll() in onPause but that just breaks my code (cannot transition into second fragment). Same with onStop()
public void onStart() {
super.onStart();
for (int i = 0; i < device_names.length; i++) {
arr.add(device_names[i]);
}
final ListView list = (ListView)getView().findViewById(R.id.list);
CustomListView customAdapter = new CustomListView(context, arr);
if (list.getAdapter() == null) {
Log.i("SAKEGA", String.valueOf(list.getAdapter()));
Log.i("SAKEGA", "it's null");
} else {
Log.i("SAKEGA", "it's not null waaaaaaaaaah");
list.setAdapter(null);
}
list.setAdapter(customAdapter);
Log.i("SAKEGA", "HMPH");
Button tv = (Button)getView().findViewById(R.id.add_devices_text);
ImageButton add = (ImageButton) getView().findViewById(R.id.add);
tv.setOnClickListener(add_device_click); // this click listener calls the 2nd fragment
add.setOnClickListener(add_device);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_scanneractivity, null);
return root;
}
What I would like to do is, once we go back to the fragment, I clear the ListView and repopulate the listView with the ArrayList.

Try to create an empty array list in the onStart() itself as you wrote your code there. what you are doing is you are persisting that array and every time you comeback its loading your array again making the entries again

Related

Navigation Architecture Component, Fragment containing list shown from start while coming back from another fragment

Scenario: Assume that the bottom navigation bar contains Home fragment which can open another fragment say Product Listing. From Product Listing fragment, user can open Product Detail fragment in order to see the details of any item.
Problem: Suppose user has scrolled the list in Product Listing fragment and reached at 100th item & tapped an item to see its detail in Product Detail fragment. Now when user presses back button in order to go back in Product Listing fragment, list is being shown from start.
Is there any way or work around to overcome this issue. I want to show the 100th item to user while coming back.
Overrided method Onstart(), OnCreateView(), OnViewCreated(),OnResume() of Product Listing fragment are executed while coming back from Product Detail fragment.
View view;
Unbinder unbinder;
#Override
public View onCreateView(#NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (view == null) {
view = inflater.inflate(R.layout.product_listing_screen, container, false);
return view;
} else {
return view;
}
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Timber.e("%s onViewCreated Called ", TAG);
unbinder = ButterKnife.bind(this, view);
}
#Override
public void onResume() {
super.onResume();
Timber.e("%s onResume Called ", TAG);
if (adapter == null || adapterList.size() == 0) {
updateProductListingCategory(itemId); // This is the method making a web request. And while coming back from detail fragment, it isn't called again.
}
}

Does fragment call the onCreate method of parent activity?

I have an activity with a viewPager inside of it, and a static ArrayList of integers that I am shuffling using Collections.shuffle(list) in the activity's onCreate method, this viewPager's fragments are using the ArrayList in parent activity.
The problem is that whenever a new fragment instantiated of the viewPager the onCreate method of parent activity is called, and I don't want that to happen because I want the list to have the same data in all fragments and not reshuffled. Do fragments call the onCreate method of their parent activities everytime there is a new instance? if Yes how can I work around this to keep the list from shuffling every time?
CODE:
Activity Code:
public static final ArrayList<Integer> IDs = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
IDs.add(0);
IDs.add(1);
IDs.add(2);
Collections.shuffle(IDs);
setContentView(R.layout.activity_walkthrough);
pager = (ViewPager) findViewById(R.id.pager);
adapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
pager.setAdapter(adapter);
Fragment Code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = (View) inflater.inflate(
R.layout.fragment_walkthrough, container, false);
final TypedArray imgs = getResources().obtainTypedArray(R.array.walkthrough_images);
ImageView imageView = (ImageView) v.findViewById(R.id.image);
if (page == 0) {
imageView.setImageResource(imgs.getResourceId(Walkthrough.IDs.get(0), 0));
} else if (page == 2) {
imageView.setImageResource(imgs.getResourceId(Walkthrough.IDs.get(1), 0));
} else {
imageView.setImageResource(imgs.getResourceId(Walkthrough.IDs.get(2), 0));
}
return v;
}
Now I want the ArrayList "IDs" to always have the same data and order when ever I instantiate a new fragment but it is not working, every time I create a new fragment the method onCreate gets recalled and a reshuffle happens!
Fragments are added to activity and therefore fragments get affected by activity.
Activity can cause calling any fragment callback method, but fragment can't
The lifecycle of the activity in which the fragment lives directly affects the lifecycle of the fragment.
For example, when the activity receives onPause(), each fragment in the activity receives onPause().
Fragments have a few extra lifecycle callbacks, however, that handle unique interaction with the activity in order to perform actions such as build and destroy the fragment's UI.
These additional callback methods are like onAttach(), onCreateView(), etc.
It'll clears the somewhat relation between fragment and activity.
Thanks

Getting text from previous activity and adding it another activities list view

How do I add items to a list dynamically when button is clicked? The text is added to the listView. At the moment it only adds one item to the list view, and when you try to add another it just changes the already added item in the list.
Here is my code:
This is the button on click for activity A that gets the text:
schedule.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
AddSchedule();
Intent i = new Intent(ScheduleActivity.this, MainActivity.class);
String str = event_name.getText().toString();
i.putExtra("myExtra", str);
startActivity(i);
}
});
Then in activity B it gets the text from activity B but only adds the
Final ListView listView = (ListView) findViewById(R.id.listView);
adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, list);
listView.setAdapter(adapter);
Intent i = getIntent();
if (i.hasExtra("myExtra")) {
list.add(i.getStringExtra("myExtra"));
adapter.notifyDataSetChanged();
}
You only see one item in the new activty's list, since that Activity is created each time via startActivity(Intent) (I suppose). So each time open Activity B, it's a new instance. The arraylist is empty.
You need to keep track of the items clicked in Activity A and send the whole list to Activity B, every time. Activity B should get the complete list in the intent and create the gui-list.
Make your class implement Parcelable or Serializable (slower).
Inside activity B you should include the following:
#Override
public void onSaveInstanceState(Bundle out){
super.onSaveInstanceState(out);
out.putStringArrayList(key, list);
}
"key" is a final String you have defined in Activity's B member variables.
This will make sure your list is saved before being destroyed (For more, read about Activity Lifecycle. Now, you also need to restore in whenever the activity is created. You can do that in onCreateView like so:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//restore the contents of your list
if (savedInstanceState!=null)
list=savedInstanceState.getStringArrayList(key);
/* initialize the adapter and set it to the list */
//also add the new item from activity A
Intent i = getIntent();
if (i.hasExtra("myExtra")) {
list.add(i.getStringExtra("myExtra"));
adapter.notifyDataSetChanged();
}
}
General note, instead of adding to the list and afterwards notifying the adapter, you can instead add your items directly to the adapter. The adapter will automatically update your list.
Edit: Yes, your list should be a member variable because it needs to be accessed in 2 methods.

Use same fragment in ViewPager but fragment will have different layout each time

I want to keep my application thin.
Problem: I would like to reuse my Fragment class code to create 3 different instances in the ViewPager which will have 3 pages. Each Fragment will have a different ImageView or background Drawable. What are best practices regarding this? I noticed that using factory methods like here seem to be good, any other alternatives?
I have one Fragment which has the following methods:
Fragment.java
public static Fragment newInstance(Context context) {
FragmentTutorial f = new FragmentTutorial();
Bundle args = new Bundle();
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment, null);
return root;
}
I have a ViewPagerAdapter class which has the following methods:
ViewPagerAdapter.java
public ViewPagerAdapter(Context context, FragmentManager fm) {
super(fm);
mContext = context;
}
#Override
public Fragment getItem(int position) {
return new FragmentTutorial().newInstance(mContext);
}
#Override
public int getCount() {
return totalPage;
}
What I've found is the "best" way to do it (in my opinion, of course) is to do the following:
Have the fragment contain methods to set the customizable data (background, text, etc)
Note: Be careful of trying to load the data in when first creating the fragment. You may be able to set the data before onCreateView() even runs, or at other times it may run after onCreateView(). I personally use a boolean to check if the data has been set. Inside onCreateView() [or onActivityCreated()], I check if the data has been set already. If it has, load in the data. Alternatively, while setting the data, I check if the views have been created/cached already. This is done by simply having variables to cache the data, say private ImageView mBackgroundView. If the view is not null, then I safely set the data on the views.
The above is also an alternative to using newInstance, although both methods work pretty well. However, for more flexibility, I only use newInstance if a) the data is already known before the fragment has to be inserted and b) the data doesn't need to change according to input from elsewhere much.
Let the ViewPager handle all the data
Pass in all the data - a list of ImageViews, a array of Strings, define where all the data is in Resources, etc - at the very beginning [say, in the constructor]
Have the ViewPager create an ArrayList of the fragments- set up each fragment as early as possible (say when first getting all the data) and add it to the list
Let getCount() just use the size of the list
Let getItem() just get the item in the list at the position
Note: If you have any dynamic data, set it up in the getItem() method. Furthermore, you can always add more data+fragments during runtime as well [just notify the adapter that the dataset has been changed]
Essentially, the fragment is like a simple servant- it does simply the least work necessary. If it doesn't have to handle choosing the data, all the better. It'll thus be far more flexible. Just give methods to set the data/views appropriately on the fragment. Now, the ArrayAdapter can do all the grimy hard work with managing the data and giving it to the appropriate fragment. Take advantage of that.
Now, note that this is assuming you want to use a single layout but want to change different aspects of that layout (texts, background, etc). If you want to make a master fragment class that can use any sort of defined layout, you can but note that it decreases the runtime flexibility (how can you change the text or background to something you get from the internet? You simply can't if you only can define and choose from pre-set layouts).
Either way, the ArrayAdapter should take care of all the different data while the fragment simply does as it's designed to do, in a more flexible manner preferably.
Edit:
Here is the project where I most recently implemented this sort of pattern. Note that it has far more to it, so I'll replace it with some not-so-pseudo pseudo-code in the morning/afternoon.
ViewPager [a bit sloppy with all the different things I was trying to do, including extending from a FragmentStatePagerAdapter without actually using any of the specific features of a StatePagerAdapter. In other words, I still need to work on the lifecycle implementations everywhere]
Fragment [Also may be a bit sloppy but shows the pattern still]
The object (actually another fragment) that uses the ViewPager [it's actually a "VerticalViewpager" from a library, but other than the animations and direction to change the current fragment, it's exactly the same- particularly code-wise]
Edit2:
Here is a more (if overly) simplified example of the pattern described above.
Disclaimer: The following code has absolutely no lifecycle management implementations and is older code that has been untouched since around August '14
Fragment simply allows the user of the fragment to set the background color and the text of the single TextView
Link to BaseFragment
Link to layout file
The adapter creates three instances of the fragment and sets the background color and text of each. Each fragment's text, color, and total fragments is hard coded.
Link to Activity+adapter
Link to layout file
Now, here are the exact relevant portions of the code:
BaseFragment
// Note: Found out later can extend normal Fragments but must use v13 adapter
public class BaseFragment extends android.support.v4.app.Fragment {
FrameLayout mMainLayout; // The parent layout
int mNewColor = 0; // The new bg color, set from activity
String mNewText = ""; // The new text, set from activity
TextView mMainText; // The only textview in this fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the fragment's layout
View view = inflater.inflate(R.layout.fragment_base,container,false);
// Save the textview for further editing
mMainText = (TextView) view.findViewById(R.id.textView);
// Save the framelayout to change background color later
mMainLayout = (FrameLayout) view.findViewById(R.id.mainLayout);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// If there is new text or color assigned, set em
if(mNewText != ""){
mMainText.setText(mNewText);
}
if(mNewColor != 0){
mMainLayout.setBackgroundColor(mNewColor);
}
}
#Override
public void onStart(){
super.onStart();
}
// Simply indicate to change the text of the fragment
public void changeText(String newText){
mNewText=newText;
}
// Simply indicate to change the background color of the fragment
public void changeBG(int color) {
// If no color was passed, then set background to white
if(color == 0)
{
mNewColor=getResources().getColor(R.color.white);
}
// else set the color to what was passed in
else{
mNewColor=color;
}
}
}
MyAdapter
class MyAdapter extends FragmentPagerAdapter{
// Three simple fragments
BaseFragment fragA;
BaseFragment fragB;
BaseFragment fragC;
public MyAdapter(FragmentManager fm) {
super(fm);
}
public void setFragments(Context c){
// Set up the simple base fragments
fragA = new BaseFragment();
fragB = new BaseFragment();
fragC = new BaseFragment();
Resources res = c.getResources();
fragA.changeText("This is Fragment A!");
fragB.changeText("This is Fragment B!");
fragC.changeText("This is Fragment C!");
fragA.changeBG(res.getColor(R.color.dev_blue));
fragB.changeBG(res.getColor(R.color.dev_green));
fragC.changeBG(res.getColor(R.color.dev_orange));
}
#Override
public Fragment getItem(int position) {
// TODO: Make this more efficient, use a list or such, also comment more
Fragment frag = null;
if(position == 0){
frag = fragA;
}
else if(position == 1){
frag = fragB;
}
else if(position == 2){
frag = fragC;
}
return frag;
}
#Override
public int getCount() {
return 3;
}
}
You need to pass some sort of id along with newInstance() while creating instance. And according to that id you can use if..else to choose layout file.
See my reference code below:
int id;
public static Fragment newInstance(Context context, int id) {
FragmentTutorial f = new FragmentTutorial();
Bundle args = new Bundle();
this.id = id;
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(id == 1)
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment1, null);
else
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment2, null);
return root;
}
Can't you just introduce fields to the Fragment class to account for the variances in background, etc. and add them to its constructor? Then in getItem instantiate the Fragment class with different values depending on the value of position.

android RadioButton text is not updated when RadioButton is selected in RadioGroup, and Fragment is recreated from backstack

I have a radiogroup where I programmatically add radiobuttons. This is all done in onCreateView() method, so should be redone every time fragment is recreated from the backstack. The problem I have is when I programmatically preselect the radiobutton at the start by using RadioGroup.check(id), RadioButton text no longer updates on subsequent calls to onCreateView() method. Any ideas?
Project is using compatibility library (v4) if this makes any difference.
Here is the code:
public static String x; //(x changes every time)
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = nflater.inflate(R.layout.right, container, false);
RadioGroup rg = (RadioGroup) rootView.findViewById(R.id.radioGroup1);
rg.removeAllViews();
Log.d("Cild count", Integer.toString(rg.getChildCount()); // Always 0
RadioButton.rb = new RadioButton(getActivity());
// Always works first time. Does not work with 1 and 2 when fragment is recreated from back stack.
rb.setText(x);
rb.setId(99099); // 1 Atbitrary int for id
rg.addView(rb);
rg.check(99099); // 2 Selection of radiobutton
return rootView;
}
The problem was solved by not using a backstack, but instead manually handling onBackPressed.
Fragment fr = getSupportFragmentManager().getFragmentByTag("Fragment name");
#Override
public void onBackPressed() {
if (fr != null)
{
getSupportFragmentManager().beginTransaction().replace(container, new PreviousFragment, "Tag").commit();
}
else
{
super.onBackPressed();
}
}

Categories