Updating an Android Fragment's ListView from the background - android

I've been searching for the past few days, but have had no success, so I'm posting this question in the hopes someone can answer it.
Setup (Using android-support-library-v4, revision 12):
I have 1 Main Activity with a ViewPager.
I have 1 Fragment Class, called DataFragmentClass which consists of a ListView.
This fragment class loads data off the internet asynchronously and displays it in the listview. However, this fragment class can load two types of data, called DataTypeA and DataTypeB.
What I do is create two instances of this fragment in my main activity like so:
Fragment[] mFragmentList = new Fragment[2];
mFragmentList[0] = DataFragmentClass.newInstance(Modes.DataTypeA);
mFragmentList[1] = DataFragmentClass.newInstance(Modes.DataTypeB);
I then pass this array to my ViewPager, which works fine.
After being created, the Fragments will begin to async load the data:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.genericlistview, container, false);
DataProvider mDataProvider = new DataProvider(this.getActivity());
mDataProvider.addListener(mDataResponseAdapter);
return view;
}
The first data load everything is fine - DataTypeA will load in Fragment1 and DataTypeB will load in Fragment2.
However, any subsequent loads result in the data 'spilling over'. All the data (Type A and B) is now loaded in Fragment2.
I've narrowed the problem down to the ListView updates.
What I do is:
Runnable updateListView = new Runnable(){
#Override
public void run() {
adapter.add(myNewData);
adapter.notifyDataSetChanged();
}
};
DataFragmentClass.this.getActivity().runOnUiThread(updateListView);
I think something goes wrong at these points:
When posting the runnable on the UI thread.
When the remote server returns the data.
Any suggestions at all would be really helpful. Thanks!

Related

Setting different adapter in single RecyclerView in Android

I have MenuActivity which has RecyclerView and ViewPager. This ViewPager has 3 pages which use three different Fragment.
FragmentOne,FragmentTwo, FragmentThree, All these Fragments use RecyclerView data.
I want to use three different ArrayList need to be set in RecyclerView Adapter, which is depends on what Fragment user is viewing.
One solution is to put RecyclerView in all three fragment and get update data in Fragment. I just want to know if it is possible to set Adapter data in MenuActivity based on what Fragment is called.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_menu, container, false);
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
itemList = new ArrayList<>();
prepareMenuData();
vegadapter = new MenuItemAdapter(getContext(),vegItemList);
nonvegadapter = new MenuItemAdapter(getContext(),nonvegItemList);
dessertAdapter = new MenuItemAdapter(getContext(),dessertItemList);
recyclerView.setAdapter(vegadapter);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
return rootView;
}
prepareMenuData();
for (MenuItem item:itemList)
{
if(item.getCategory().equals("VEG"))
{
vegItemList.add(item);
vegadapter.notifyDataSetChanged();
}
if(item.getCategory().equals("NONVEG"))
{
nonvegItemList.add(item);
nonvegadapter.notifyDataSetChanged();
}
if(item.getCategory().equals("DESSERTVEG"))
{
dessertItemList.add(item);
dessertAdapter.notifyDataSetChanged();
}
}
This code does not know when to set vegadapter,nonvegadapter etc.
Please suggest.
Thanks
Deepak
Technically yes you can, but whether thats good practice is another question.
You'll have to have the RecyclerView in each Fragment regardless but you can get the data for the adapter from the Activity. Fragments have access to their Activity, thats how the Android lifecycle works so from the Fragment you could call ((MenuActivity) getActivity()).getMenuList(); and then pass the result to that Fragments adapter. This will however couple the Fragment with the Activity which isn't good practice.
Given the fact that it looks like your data isn't dynamic what I would personally do is make your MenuItem class implement Parcelable, then when you create your Fragments you can pass through your ArrayList of MenuItems as an argument, this way your Fragment is independent of your Activity.

How to set different data onto a fragment/viewpager

I've looked around but couldn't find any solutions, so this is my last resort.
I'm working on a Xamarin-Android project and I've got a viewpager with one fragment. The trick with this fragment is that even though it's only one fragment, it loads many instances of this one fragment, depending on how many I need but the problem is that each fragment needs to load one object (one set of data). The problem I have is that when I iterate through the list of returned items (loaded from a file), it obviously loops through everything and sets the last set of returned data onto my fragment. This causes me to have many fragments with the same data. What I need is to load one set of data onto each fragment instead of it loading the last set onto my fragment. So in essence, I have one fragment which loads many instances and each instance needs to show one object's data. How do I do this?
Thanks in advance for any assistance.
Okay, please see below - This is the fragment class. I've left out the OnCreateView of the fragment, as it only inflates the fragment resource and gets the textviews etc. Let me know if you need the FragmentPagerAdapter code as well. This one fragment has many instances, which is set in the FragmentPagerAdapter class in the Count and GetItem overidden methods. Count returns the number of instances required and GetItem which does "return ThisFragment.newInstance(position);"
EDIT: Code updated with solution
private int mNum;
private string code, status;
TextView textviewMyObjectCode, textviewMyObjectStatus;
public static ThisFragment newInstance(int num)
{
ThisFragment myFragment = new ThisFragment();
MyObject myObject = new MyObject();
List<MyObject> myObjectList = MyObjectIO.LoadMyObjectsFromFile();
myObject.MyObjectNumber = myObjectList[num].MyObjectNumber;
myObject.MyObjectStatus = myObjectList[num].MyObjectStatus;
args.PutInt("num", num);
args.PutString("objectCode", myObject.MyObjectNumber);
args.PutString("objectStatus", myObject.MyObjectStatus);
myFragment.Arguments = args;
return thisFragment;
}
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
mNum = Arguments != null ? Arguments.GetInt("num") : 1;
code = Arguments.GetString("objectCode");
status = Arguments.GetString("objectStatus");
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
if (container == null)
{
return null;
}
View thisView = inflater.Inflate(Resource.Layout.object_fragment, container, false);
textviewObjectStatus = thisView.FindViewById<TextView>(Resource.Id.textviewObjectStatus);
textviewObjectCode = thisView.FindViewById<TextView>(Resource.Id.textviewObjectCode);
textviewObjectCode.Text = code;
textviewObjectStatus.Text = status;
return thisView;
}
It should be the responsibility of the 'FragmentPagerAdapter' to create new Fragments. You seem to have delegated this responsibility to a Fragment class which is not the right approach. Here you are setting text of 'textviewMyObjectCode' and 'textviewMyObjectStatus' again and again in a loop so these values will get over-ridden in every iteration of the loop.
Ideally you should access 'MyObjectList' in 'newInstance' and set the values in Bundle objects as per the index passed in 'num'. Also 'newInstance' should be part of 'FragmentPagerAdapter' and 'MyObjectList' should be available to 'FragmentPagerAdapter'.
If 'ThisFragment' is the Fragment which is needed to be part of ViewPager then that fragment should just do the job of retrieving its data from the Bundle and set the needed data to its resources.
I managed to figure it out. Since I am using the newInstance method, which contains a position variable called "num" and because I get a list of objects from the LoadObjectsFromFile method, I can assign a specific object to a specific fragment. Example: I can load object[0] onto fragment[0]. I do this in the newInstance method, set the values to a Bundle and then retrieve it later in OnCreate. Then in OnCreateView, I can place the values onto my textviews. #Jay, I now realize what you were saying all along. It didn't click initially :)

Updating adapter with AsyncTaskLoader in a regular (support) Fragment not updating ListView

I'm having trouble using the AsyncTaskLoader callbacks to update an adapter in a regular Fragment. I'm using the fragment support library, version 19.1.0.
In my onLoadFinished() callback I simply do an mAdapter.addAll(data). I used the debugger to verify that the callback is called and that all of the data is added to the adapter. The problem is that the list view simply won't update.
I've already tried notifyDataSetChanged() and invalidateViews() - no effect.
All examples or tutorials I've seen so far use a ListFragment or ListActivity without a custom layout; I've tried this and it works fine. In particular, I've tried the sample program from Android Design Patterns loader tutorial. Unfortunately, I can't use ListFragment because I'm using the StickyListHeaders library (but even trying this with a regular ListView doesn't work).
I get the same results when modifying the above sample program to use a Fragment instead of ListFragment by removing the ListFragment-specific method calls and inflating a custom view. The relevant code boils down to this:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAdapter = new AppListAdapter(getActivity());
getLoaderManager().initLoader(LOADER_ID, null, this);
}
#Override
public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.list_layout, container, false);
ListView listView = (ListView) v.findViewById(android.R.id.list);
listView.setAdapter(mAdapter);
return v;
}
#Override
public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) {
mAdapter.setData(data);
}
As I said before, the adapter gets updated but the list view simply won't redraw, even if you add in notifyDataSetChanged and call invalidateViews on the listView. I'm finding it hard to figure out what's going wrong here.
Turns out that the problem was initializing the adapter in onActivityCreated, as it's called after onCreateView - so setAdapter was being passed a null value (and it doesn't complain about that). This wasn't a problem in the ListFragment and ListActivity implementations I'd looked at as you can use their setListAdapter method to associate the adapter with the list view, without keeping your own explicit reference to it. A silly mistake :\
I've run into similar problems before. The root of mine seemed to be not calling notifyDataSetChanged from the ui thread. Your problem may be similar, try:
yourContext.runOnUiThread(new Runnable() {
public void run() {
mAdapter.notifyDataSetChanged();
}
});
And let me know if it works for you.

Keep ListView scroll position and data when popping fragment stack

Here's my situation, I have 1 Activity, and 2 Fragments. When the Activity is started it loads a Fragment, let's call it list, into the view (in a FrameLayout filling the screen). List simply lists blogs posts which are retreived via an async HTTP call to out web api. When a blog is clicked in list is runs this method in the Activity
public void loadBlog(FragmentBlog.Blog theBlog) {
blog = theBlog;
FragmentBlogDetails d = new FragmentBlogDetails();
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_BLOG, theBlog);
d.setArguments(bundle);
fragmentManager.beginTransaction().replace(R.id.container, d).addToBackStack("Detail").commit();
}
Then the blog loads up and is displayed fine. Now, once I press the back key, my list Fragment calls onCreateView again and I have to run the async call again to populate my list, which reset the scroll to the top too. How can I keep my data for needing to be fetched again, and keep the view so the scroll stays where it is?
Here's my onCreateView method in the list Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d("FragmentBlog", "onCreateView");
View view = inflater.inflate(R.layout.fragment_blog, container, false);
list = (ListView) view.findViewById(R.id.the_list);
list.setOnItemClickListener(this);
if (baa != null) {
list.setAdapter(baa);
}
text = (TextView) view.findViewById(R.id.my_text_view);
pb = (ProgressBar) view.findViewById(R.id.progressBar);
asyncForBlogs();
return view;
}
If you need any other bits of code let me know and I'll post them.
You need to save index of last item user clicked. Define a index variable in your fragment and when users clicked on an item store item index in it. Then save data in onSaveInstanceState Call back. You can save other data like this. But make sure the data you put inside bundle is not so big. Do not put any images or videos here!
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("index",index);
super.onSaveInstanceState(outState);
}
after saving your data you can access it in onCreate method. like this
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
index = savedInstanceState.getInt("index");
listView.smoothScrollToPosition(index);
}
}
smoothScrollToPosition scroll your list view to the last place you were been.

Retaining list in list fragment on orientation change

I have an app using fragments, all of which are contained in a single activity. The activity starts with a fragment containing a menu of buttons, all of which cause various listfragments to replace the original button/menu fragment.
My problem is that upon an orientation change, if the activity is displaying one of the listviews, it goes away and the button menu returns. I understand why this is happening... the activity is destroyed and re-created, but not how to work around it and maintain the list view/current fragment through the orientation change.
I've found setRetainInstance and the example of use here, but I can't figure out how to apply it to my situation with the button menu or the possibility that the fragment I want to retain could be one of several different ones.
Below is code simplified to show the main activity and one of the listfragments.
Any pointers in what to add where to make it so that the list fragment will be retained would be greatly appreciated.
Activity
public class Main extends FragmentActivity {
private MainMenuFragment menu;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
menu = new MainMenuFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.pane, menu).commit();
}
}
ListFragment
public class ItemListFragment extends ListFragment {
private TextView header;
private TextView empty;
private Button add;
public static Cursor itemCursor;
private GroceryDB mDbHelper;
public static long mRowId;
public static CheckCursorAdapter lists;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.common_list, container, false);
header = (TextView) v.findViewById(R.id.header);
empty = (TextView) v.findViewById(android.R.id.empty);
header.setText(R.string.header_item);
empty.setText(R.string.empty_items);
return v;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mRowId=0;
mDbHelper = new GroceryDB(getActivity());
mDbHelper.open();
itemCursor = mDbHelper.fetchAllItems();
getActivity().startManagingCursor(itemCursor);
String[] from = new String[] { GroceryDB.ITEM_NAME };
int[] to = new int[] { R.id.ListItem };
lists = new CheckCursorAdapter(getActivity(),
R.layout.listlayout_itemlist, itemCursor, from, to);
setListAdapter(lists);
}
}
how to work around it and maintain the list view/current fragment through the orientation change
You are blindly replacing the fragment every time onCreate() is called. Instead, only add/replace the fragment if savedInstanceState() is null. If it is not null, you are coming back from a configuration change, and your existing fragments will be recreated (or, if they were retained, they are already there).
setRetainInstance(true) means that the fragment itself will be retained across configuration changes, instead of being destroyed/recreated like the activity is. However, it will still be called with onCreateView(). In your code, that means that your data members of ItemListFragment would stick around, but you would still need to call setListAdapter() even if you do not requery the database.
I know that this has been resolved a long time ago, but for the sake of people searching for a solution who have as much issues as I've (repeatedly) had with retaining lists during an orientation change I would like to add that you could also use a custom class which holds the list of data for your listadapter.
This way it keeps the data when recreating the activity (and listfragment) and you can just test to see if it has any data in your oncreate. If the list == null or the list.size < 0 you proceed as usual and get the data whatever way you normally get it. Otherwise you just set your listadapter with the data it already has.
To me this is a lot easier, and seeing as Eclipse automatically creates a similar DummyContent class for your data when creating an android master/detail flow project it basically only requires a change of the oncreate of your listfragment.

Categories

Resources