I have a very simple ToDo list app with multiple categories the user can add an item to. The architecture looks something like this:
http://i.imgur.com/WA3dtcU.png
I am using a ViewPager and each view is a fragment that I instantiate in the ViewPagerAdapater:
public Fragment getItem(int i) {
ToDoCategoryModel cat = categories().get(i);
ToDoCategoryView view = ToDoCategoryView.newInstance(cat);
return view;
}
Within each of these category the user can add new Item Fragments to a Linear Layout.
A very strange thing happens though. I start off on Category 1 (C1) Which currently has 1 Item in the list. Then scroll to C2 then scroll again to C3. Then I scroll back to C2. The ViewPager asks my ViewPagerAdapter to re-create C1 as it was destroyed after having scrolled 2 pages away from it. This is fine, I just create a brand new fragment (as the code above shows). However, when I now scroll back to C1, instead of 1 Item being shown (as you would expect) there are 2 items shown. They are a duplicate of each other.
The code I use to add the Item fragment to the Category fragment is:
public void addToDoItem(ToDoItemModel model) {
ToDoItemView newItem = ToDoItemView.newInstance(model, this);
getChildFragmentManager().beginTransaction().add(_linearLayout_items.getId(), newItem, "sameTag"+count++).commit();
}
If I change this code to add new TextView instead of fragments:
public void addToDoItem(ToDoItemModel model) {
ToDoItemView newItem = ToDoItemView.newInstance(model, this);
TextView tv = new TextView(getActivity());
tv.setText(newItem.getModel().getName());
_linearLayout_items.addView(tv);
}
It works fine! There are no duplicate items shown.
So what is wrong with adding fragments dynamically using the child fragment manager within a view pager?
Thanks.
Related
Scenario - I am working on an app that shows events respective to particular dates in a viewpager, where each page represents a day(i.e 24 hrs in a vertical manner, similar to google calendar). Each page/fragment contains a vertical scrollview(it has a framelayout inside it) and based on list of events i am dynamically creating custom-views(view position and dimension is based on the corresponding event timing and duration) and adding it to the scrollview. User can drag and drop events between dates.
Issue - I have successfully achieved the view creation part, now the issue is with performance. Sometimes vewpager(using FragmentStatePagerAdapter) lags while swiping through pages.
Someone please suggest me how to reduce the lag or any better ways to achieve this
Yes I had a similar performance with viewpager and FragmentStatePagerAdapter, the problem with viewpager is that pre creates the views either side of the current view to speed up the swipe to next view.
This works well for static views but for views with dynamic data the pre-created view was usually out of date and needed to be regenerated when the user swiped to it.
Thus it was having to call onCreateView on 3 views while the user swiped between views leading to lag sometimes.
I thought of 2 improvements for performance, though only used one.
1) The view holder/model pattern e.g. https://www.androidcode.ninja/android-viewholder-pattern-example/ and https://developer.android.com/topic/libraries/architecture/viewmodel where the inflation and finding items re-used / separated from onCreateView activities.
I did not use this method
2) Create bare bones views for the dynamic ones (The first view was static and then there were 2 dynamic ones at positions 1 and 2 that contained listviews of different aspects of the dynamic data.)
The listviews were inflated and had an adapter set to an empty list in onCreateView of these views thus they were fast to create.
Then I added an on OnPageChangeListener which notified the adapter backing the ViewPager that the pages had changed
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
if (position == 1 || position == 2)
{
mSectionsPagerAdapter.notifyDataSetChanged();
}
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// Code goes here
}
#Override
public void onPageScrollStateChanged(int state) {
// Code goes here
}
});
The notifyDataSetChanged causes the viewPager to call getItemPosition in the FragmentStatePagerAdapter class to work out if the page position has changed and it if need to re-create it in the a new position.
Then in FragmentStatePagerAdapter extended class I overrode getItemPosition with
#Override
public int getItemPosition(Object object) {
if (object instanceof LogFragment) {
LogFragment f = (LogFragment) object;
if (f != null) {
f.update();
}
} else if (object instanceof SummaryFragment){
SummaryFragment f = (SummaryFragment) object;
if (f != null) {
f.update();
}
} else {
return POSITION_UNCHANGED;
}
return super.getItemPosition(object);
}
This allowed me to get the Fragment object and then call the update method on it but still returning POSITION_UNCHANGED so the viewpager did not try and re-create the Fragments.
Then in the update method of the Fragment I get the listview adapter and update the data.
public void update(){
adapter.clear();
adapter.addAll(datasource.getData());
}
Thus the more costly getting and display the dynamic data is only done when the page is actually display, NOT when it is pre-created by viewPager (because that pre-created view would be old out of date data anyway and would need to be updated)
There is one downside to this approach, the screen shown during the swipe is still the old data (either empty or data from when that page was last updated), this was acceptable to me.
I know this is possible, but I can't seem to figure it out. I have an Android app that shows a grid of squares which is created using grid adapters, viewgroups, and a fragment. When the user clicks a block in the grid, it shows a new fragment with additional related information.
I was able to get a transition to occur when I click the first block, but it just flips the entire grid rather than doing a flip transition with the new fragment. I would also like to ask how you create a zoom transition?
GridViewFragment - The following is code snippets from one of the documents that create the grid.
#AfterViews
void AfterViews() {
context = getActivity().getBaseContext();
gridAdapter.setDataResponse(dashboard.resp);
gridview.setAdapter(gridAdapter);
gridview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
DashboardGridViewItem item = (DashboardGridViewItem) v;
flipCard(app.getGridInfo()[position], item.getImageJSON());
}
});
/*mGoogleApiClient.connect();*/
}
#UiThread(propagation = UiThread.Propagation.REUSE)
void flipCard(DataObject data, ResponseEntity<String> image) {
String fragmentType = data.getTitle();
dashboard.fragment = Fragment_.builder().build();
dashboard.fragment.setmData(data);
dashboard.fragment.setImageJSON(image);
FragmentManager fragmentManager = dashboard.getSupportFragmentManager();
transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.card_flip_left_in, R.anim.card_flip_left_out);
transaction.replace(dashboard.fragmentContainer.getId(), dashboard.summaryFragment);
transaction.addToBackStack(null);
transaction.commit();
.... (the rest of the function is just switch cases for different grid blocks, so it looks like what's pasted above)
This is where the fragment container is declared on the dashboard (which is accessed in the code above).
#ViewById
public FrameLayout fragmentContainer;
As I mentioned above, the issue is that the transition is happening after the block is clicked, but it controls the entire grid and then shows the new fragment once the animation is over. I want the animation to happen to the fragment.
I have a ListView in Fragment, in which I want to add new item. DialogFragment is used to enter new item and on pressing "Yes" the item is dynamically added to the ListView. I have tried many solution but I cannot get it to work.
I will answer my own question.
In your Activity that implements interface defined in your DialogFragment
public void onCreateMatchDialogPositiveClick(DialogFragment dialog , String itemsToAdd) {
//Your Fragment Class containing listview to be updated
MyListViewFragmentClass fragment = (MyListViewFragmentClass) getSupportFragmentManager().findFragmentById(R.id.myListview);
if(fragment != null){
//fragment is available
fragment.addItemToListView(itemsToAdd);
}
}
Then in your fragment containing listview to add item dynamically
public void addItemToListView(itemToAdd){
matchList.add(itemToAdd); //Add item to your dataset
adapter.notifyDataSetChanged();//Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh
}
I am having an ExpandableListView inside a fragment which is part of a tab.
So inside - onCreateView(...) of fragment class I do :
ExpandableListView expandableList = (ExpandableListView) view
.findViewById(R.id.groupList);
parent.add(getString(Group1));
parent.add(getString(Group2));
ArrayList<MyObject> groupList1 = new ArrayList<MyObject>();
ArrayList<MyObject> groupList2 = new ArrayList<MyObject>();
childs.add(groupList1);
childs.add(groupList2);
MyAdapter adapter = new MyAdapter(getActivity(), parent,
childs);
Now when I open both the groups and then switch to tab by doing :
transaction.replace(R.id.realtabcontent, new MyFragment(),
"MyFragment");
And again come back to this tab by doing the same, the last group gets added with groups with child item.
Note : This only happens when we switch tab using replace. When we open for the first time launching the activity this works fine.
Kindly let me know what might be going wrong here. Let me know if I need to provide more code. Thanks in advance!
I'm facing a very strange phenomenon. I'm using a ViewPager from the compatibility package to display profiles.
Every profile is just a ListView with custom elements in it. Every element has two states:
There is data - display it
There's no data - display place holder
Every time I swipe between profiles I reset the data within the item objects. When I remove the REST-Calls and just let the profile item empty - which should display the placeholder - all items keep blank.
What could be the problem?
EDIT:/
That's how I set the current item in the listview. The PageViewAdapter seems to be OK, if I use static content everything works perfect.
final TextView lblTitle = (TextView)convertView.findViewById(R.id.lblTitle);
final LinearLayout llContent = (LinearLayout)convertView.findViewById(R.id.llContent);
final ProfileItem item = getItem(userPosition);
String title = item.getTitle();
if(title == null || title.equals("")) {
lblTitle.setVisibility(View.GONE);
} else {
lblTitle.setText(item.getTitle());
}
if(item.getContent().getParent() != null) {
((ViewGroup)item.getContent().getParent()).removeAllViews();
}
llContent.removeAllViews();
llContent.addView(item.getContent());
The Items look all very similar like that:
#Override
public View getContent() {
if(isTextEmpty()) {
return noTextTherePlaceholder;
} else {
return view;
}
}
The effect keeps the same even if I return just view or noTextTherePlaceholder.
If I would instate new views like a TextView in the getView method everything works as expected.
With view pager you have to get all your data in your Activity.onCreate and then use that data in your ViewPagers adapter's onInstantiateItem.