How to set different data onto a fragment/viewpager - android

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 :)

Related

Toggle Button State in ViewPager when a page is removed

I have a toggle button (in all pages) to allow users to like the contents of a page (of viewpager).
When user unlikes a page, the page gets removed from the viewpager.
Below is the cycle
User "Unlikes" -> Updates SQLite -> NotifyDatasetChanged() -> Fragments
Rebuilt -> Viewpager displayed
The issue is suppose I am in position 2 and I "unlike" - The page gets removed - in its place a new page is placed with the same toggle button state as that of the removed page, whereas I expect the togglebutton state to be set based on actual value returned by the Cursor.
Even though "isChecked()" status of the ToggleButton is "true" and is being returned in getItem() as a part of rootview - and is also being displayed in a mock TextView I created - Somehow the "Checked" State is retained from the removed page.
Adapter
public class CursorPagerAdapter extends FragmentStatePagerAdapter {
.
.
public Fragment getItem(int position) {
Fragment fragment = null;
if (mCursor.moveToPosition(position)) {
Bundle arguments = new Bundle();
fragment = new myDetailFragment();
fragment.setArguments(arguments);
}
return fragment;
}
}
Population
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ToggleButton addFav = (ToggleButton) rootView.findViewById(R.id.addFavorite);
.
.
if (item_status.equals("0")) {
addFav.setChecked(false);
afw.setBackgroundColor(Color.GRAY);
} else (item_status.equals("1")) {
addFav.setChecked(true);
afw.setBackgroundColor(Color.RED);
}
.
.
addFav.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final Context ctx = getActivity();
ContentValues values = new ContentValues(1);
SharedPreferences prefs = getActivity().getSharedPreferences("com.dap.qgit", Context.MODE_PRIVATE);
ToggleButton tb = (ToggleButton) v;
if (tb.isChecked()) {
values.put(DataProvider.COL_ITEM_STATUS, "1");
} else {
values.put(DataProvider.COL_ITEM_STATUS, "0");
}
String[] args = new String[1];
args[0] = "" + tb.getTag();
ctx.getContentResolver().update(DataProvider.CONTENT_URI_ITEM, values, "item_id=?", args);
getContext().getContentResolver().notifyChange(DataProvider.CONTENT_URI_TAG_ITEMS,null);
}
});
}
There are several things you need to consider when using FragmentStatePagerAdapter implementation with a dynamic data set like Cursor.
Issues
FragmentStatePagerAdapter from the support library maps its instantiated fragments to theirs corresponding positions not to theirs corresponding ids,
FragmentStatePagerAdapter by default does not support dynamically changing data set as you can see by implementation of getItemPosition(Object object) which always returns POSITION_UNCHANGED
Solutions
You need to use implementation of FragmentStatePagerAdapter which maps its instantiated fragments to theirs corresponding ids from the Cursor and is also capable to restore state of those fragments by theirs corresponding ids,
Such adapter implementation also needs to return proper position of a fragment according to position of its associated data in the Cursor via
getItemPosition(Object object) method.
Now, you may implement/port FragmentStatePagerAdapter from the support library, so such implementation provides both, mapping by ids and proper position resolving features or you may use some library which already provides such features for you, like this library which provides the first feature and the second one may be simply implemented by this GitHub Gist.
First please read "Martin Albedinsky" reply - Thanks to him for helping me isolate the issue.
I used the below in my layout XML to circumvent the issue:
<ToggleButton
.
.
.
android:saveEnabled="false"
.
.
.
>
Though I have not personally tried Martin's solution - which by his explanation appears to be a cleaner approach.
I am yet to ascertain if this solution that worked for me causes any performance/untoward issues.

Android fragment crashes when app comes from background

I'm having a problem that is starting to give me head-hakes.
My application is basically a FragmentActivity with a navigation drawer and each button of the navigation drawer loads a new fragment.
I'm using android.support.v4 for almost every component in the project.
My issue lies every time my app goes to background and comes back to foreground the oncreate view loads the view again and most of the variables that I use to create the view are null and my app crashes because of that.
Can anyone point me in the right direction to solve this problem? would it be because of the OnsavedInstanceState() method, the onCreateView() doing the variable instantiation, or anything else?
Here's my error log:
java.lang.RuntimeException: Unable to start activity ComponentInfo{pt.gema.welcomeangola/pt.gema.welcomeangola.activities.MainActivity}: java.lang.NullPointerException
Caused by: java.lang.NullPointerException
at java.util.ArrayList.<init>(ArrayList.java:93)
at pt.gema.welcomeangola.activities.ListViewExampleFragment.onCreateView(ListViewExampleFragment.java:103)
One of my fragments OnCreateView() Code
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
lvef = this;
View rootView = inflater.inflate(R.layout.activity_items_list, container, false);
getActivity().setTitle("ListViewExample");
btnSearch = (ToggleButton) rootView.findViewById(R.id.btn_search);
btnSearchText = (ImageButton) rootView.findViewById(R.id.btn_search_string);
btnAZ = (ToggleButton) rootView.findViewById(R.id.btn_az);
searchText = (EditText) rootView.findViewById(R.id.search_text);
layoutSearch = (RelativeLayout) rootView.findViewById(R.id.search_layout);
pBar = (RelativeLayout) rootView.findViewById(R.id.progress_bar_layout);
List<Integer> filter= new ArrayList<Integer>();
filter.add(id_type);
OriginalObjectsLocality= new ArrayList<ListPlaceInfo>(Objects);
listview = (ListView) rootView.findViewById(R.id.list_item);
adapter = new WAListAdapter(getActivity().getApplicationContext(),Objects,OriginalObjectsLocality,lvef);
listview.setAdapter(adapter);
return rootView;
}
EDIT
Although the problem was not only in the Arraylist Objects, but the instantiation of another class that I tried to access. laalto answer helped me to find the problem, therefore I consider it the right answer.
From comments:
#Szymon I receive that arraylist in the fragment constructor, and i thisnk my problem is here... public ListViewExampleFragment(ArrayList objects) { super(); this.Objects = objects; this.listPlaceAZ=azListing(new ArrayList(objects)); }
You set up a member variable Objects in a constructor that takes an arraylist param.
Fragments must have a parameterless constructor and the framework will create the fragment calling that empty constructor. So your parameter-taking constructor is not called and Objects is left null, causing a NPE here:
OriginalObjectsLocality= new ArrayList<ListPlaceInfo>(Objects);
If you need to pass parameters to your fragments, use a Bundle set with setArguments() and accessed with getArguments().
But passing an arraylist as a Bundle requires a Parceble and that requires me to change almost all my code, is there any other option?
You could make the member variable static so it survives fragment recreation. But I wouldn't recommend that as it will create a whole set of other problems, such as memory leaks due to incollectible objects.
It's better to rethink the design. For example, you probably don't need to pass an array of objects around. You could just pass an array of identifiers as parameter instead, and query the objects by id when needed.

Updating an Android Fragment's ListView from the background

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!

Can a ListView contain Fragments

As in, can the ELEMENTS of a ListView be Fragments. I know that you can assign a TextView XML to a ListView to change the way it looks, but can you add Fragments into a ListView.
For instance: I have a Fragment. The XML for said Fragment contains an ImageView, a couple of large-style TextViews, and a small-style TextView. The Fragment class code receives a Bundle, then based on the contents populates the TextViews and ImageView accordingly. Both the Fragment XML and the Fragment code work without issue
(I can display an individual Fragment just fine). I have a FragmentActivity in which I want to display the aforementioned list of Fragments. Here is the code I'm using to try to populate the ListView inside of the FragmentActivity's View:
ArrayList<Fragment> fragList = new ArrayList<Fragment>();
Fragment fragment = Fragment.instantiate(this, TileItem.class.getName());
Bundle bundle = new Bundle();
bundle.putInt("key", 0);
fragment.setArguments(bundle);
fragList.add(fragment);
ArrayAdapter<Fragment> adapter = new ArrayAdapter<Fragment>(this, R.layout.tile_item, fragList);
listItems.setAdapter(adapter);
Here's my mode of thinking on this. I make an ArrayList of Fragments to hold all of my instantiated Views. I then create a Fragment, create a Bundle, add data to the Bundle (so that the Fragment can marshal data into it's Views correctly), add the Bundle to the Fragment, then finally add the Fragment to the ArrayList. After that, I make an ArrayAdapter, add the element layout I want to use, and the list of Fragments I've made; then set the ListView to read from my adapter.
Anyone running this code will likely get the NPE # instantiating the ArrayAdapter. What gives? Is this even possible? Before I keep racking my brain on this can someone tell me if I'm just wasting my time? Is there a better way? I've been thinking of using a ScrollView, but so much of the functionality of a ListView would need to re-implemented and I hate-hate-hate reinventing the wheel when it's not necessary.
Thanks to anyone reading, and especially thank you for your thoughts if you decide to leave them. I've tried searching around for an established answer to this but all I seem to find are questions/web pages concerning using a ListView INSIDE of a Fragment; not using Fragments AS THE ELEMENTS of a ListView
Edit: I took the suggestions below and started investigating more. From the way things appear I should be able to use a custom adapter that inflates fragments instead of just flat out building from XML (for lack of a better way to describe the process) However, my current implementation is throwing an NPE when trying to set the adapter.
Here is my custom adapter code (shortened for brevity):
public class AdapterItem extends ArrayAdapter<Fragment> {
Context c;
List<Fragment> f;
public AdapterItem(Context c, List<Fragment> f) {
super(c, R.layout.tile_item, f);
this.c = c;
this.f = f;
}
#Override
public View getView(int pos, View v, ViewGroup vg) {
LayoutInflater i = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return i.inflate(R.layout.tile_item, vg, false);
}
}
and here is how I'm implementing it:
ArrayList<Fragment> fragList = new ArrayList<Fragment>();
Fragment fragment = Fragment.instantiate(this, TileItem.class.getName());
Bundle bundle = new Bundle();
bundle.putInt("key", 0);
fragment.setArguments(bundle);
fragList.add(fragment);
AdapterItem adapter = new AdapterItem(this, fragList);
adapter.add(fragment);
listItems.setAdapter(adapter);
So it's been a few days and I'm pretty sure this thread has been buried. However, I thought I would add one last update just in case someone wants to try this and a google search brings them here. So in my implementation I'm getting an NPE when the ListView is given the adapter. It doesn't take a rocket surgeon to figure out that it's certainly the adapter and not the ListView throwing the error. For the life of me I can't figure out why though...
At any rate, I think I have some idea though. First, a little back story: A while back I was trying to make FragmentTransactions inside of a FragmentDialog. Everytime I attempted to do so, I would get an NPE. Eventually, through much research, I discovered that the reason pertained to the way that Fragments are instanced. When a Fragment is called it needs the context from it's parent. Since a Dialog's parent is the Activity that started it, the Dialog itself didn't meet the criteria necessary. I believe, that when attempting to add fragments to a ListView, this is also the case. Since the ListView doesn't meet the agreement with instancing a Fragment it throws the NPE and thus, leaves me hanging and going back to conventions. D#mn...I had really hoped I would be able to do this. Using Fragments instead of simple XML would have made it so much easier to organize/search through the list. Oh well... guess it can't be done in case anyone is wondering.
I'd say this is not possible to do as putting a fragment in a ListView would mean the fragment can be multiplied across multiple containers. When you use the FragmentManager to create a fragment, it is tagged with an identifier, making it simple to reload and rearrange on orientation and other configuration changes. It also encourages uses across multiple device configs.
A Fragment is really a subset of an Activity. Would you ever have an Activity as part of a list? Definitely not (should be the answer!)!!!
Moreover, it is not very useful to attach() and detach() a fragment continuously as they move in and out of view (cells get recycled). These are all expensive operations that a ListView shouldn't deal with. Lists should scroll quickly.
From the conversation on the comments, I can see you want to achieve nice code with a good separation of view setup code and adapter in the Activity. Do so with either:
Override the View class and do your custom drawing and setup there.
Create a new class, in which you supply a context and data set required for it to get you back the view a list needs to show - this is what I usually do.
Have a Utils class to build your video elsewhere (silly).
Just don't use Fragments in Lists. Not the use case they are aiming for. HTH.
It turns out that you can create a ListView where each item in the listView is a Fragment. The trick is wrapping the Fragment in a FrameLayout.
UPDATE 9/16/2014
Even though it is possible to create a ListView that contain Fragments, it doesn't look like it's a good idea. This seems to definitely be a corner case in the Android world and there be dragons. For a simple fragment like the one in the example below everything works beautifully, but if you have a complex project with a lot going on in it then this is probably not the way to go. My new approach is to pull all of the GUI related code into a View that extends FrameLayout, and insert that into a the ListView -- this works MUCH BETTER and is more in line with how Android expects to be used. If you need the functionality of a Fragment in other parts of your code, you can simply use this new View there too.
Back to the original answer...
I've added a new ManyFragments example to my AnDevCon 14 Fragments example app if you want to try it out. Essentially it comes down the the BaseAdapter, which in my example looks like this:
BaseAdapter adapter = new BaseAdapter() {
#Override public int getCount() { return 10000; }
#Override public Object getItem(int i) { return new Integer(i); }
#Override public long getItemId(int i) { return i; }
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
if (view!=null){
ManyListItemFragment fragment = (ManyListItemFragment) view.getTag();
fragment.setCount(i);
} else {
FrameLayout layout = new FrameLayout(getActivity());
layout.setLayoutParams(frameLayoutParams);
int id = generateViewId();
layout.setId(id);
ManyListItemFragment fragment = new ManyListItemFragment();
fragment.setCount(i);
getChildFragmentManager()
.beginTransaction()
.replace(id,fragment)
.commit();
view = layout;
view.setTag(fragment);
}
return view;
}
};
In case you're curious here's generateViewId():
#TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static int generateViewId() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
for (;;) {
final int result = sNextGeneratedId.get();
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.compareAndSet(result, newValue)) {
return result;
}
}
} else {
return View.generateViewId();
}
}
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
You don't need to use Fragments.
Write a custom ViewAdapter and have it inflate a more complex layout (or maybe several more complex layouts if you need to get really fancy) then populate the fields of the layout as necessary.
[Aside: to the people who answered in comments -- please use answers rather than comments if you are actually answering the question! If only because you get more reputation points that way!]

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