Object is not updated in a fragment - android

I am doing an android project, trying to work with fragment
one of my fragments has a spinner which I need a separate listener for it
when I set the listener I send an object to the constructor as I need this object I don't know why I get null all the time when I call it in the fragment while it's updated in the listener
maybe I have to use another way to pass objects, but I didnt found any
I will be appreciated if you can help me to know why [chosenTrackable] is null in the fragment all the time ?
and if there is a better way to call object between fragments and listeners
here is my code :
public class AddTracking extends Fragment {
Spinner trackableSpinner;
Context context;
HashMap <Integer, MyTrackable> myTrackables ;
MyTrackable chosenTrackable;
private static final String TAG = "AddTracking";
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
context = container.getContext();
View view = inflater.inflate(R.layout.fragment_add_tracking, container, false);
myTrackables= new Model(view.getContext()).getTrackables();
//initilizing
trackableSpinner = view.findViewById(R.id.trackable_spinner);
//setup trackable spinner
ArrayAdapter trackableSpinnerAdapter = new ArrayAdapter(this.getActivity(),
android.R.layout.simple_spinner_item, new ArrayList<>(myTrackables.values()));
trackableSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
trackableSpinner.setAdapter(trackableSpinnerAdapter);
trackableSpinner.setOnItemSelectedListener( new SpinnerTrackableItemSelectedListener(this.getActivity(),chosenTrackable));
chosenTrackable = myTrackables.get(trackableSpinner.getSelectedItemPosition());
Log.d(TAG, "onCreateView: " + chosenTrackable);
return view;
}
}
listner:
public class SpinnerTrackableItemSelectedListener implements AdapterView.OnItemSelectedListener {
Context context ;
private static final String TAG = "SpinnerTrackableItemSel";
MyTrackable chosenTrackable;
public SpinnerTrackableItemSelectedListener(Context context, MyTrackable chosenTrackable) {
this.context = context;
this.chosenTrackable = chosenTrackable ;
}
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
chosenTrackable = (MyTrackable) parent.getAdapter().getItem(position);
Log.d(TAG, "loadTimeSpinner: " + chosenTrackable);
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
}

Related

Nested ArrayList while creating RecyclerView

I am developing an android application where i am using a RecyclerView to display a list of items.I am getting the list from server as json.So my problem is within this list i am getting another list as item.That is if my main arraylist contain title and materials, the material is another arraylist.So can you please suggest a solution to display a list within recyclerview.
The code below is my adapter
public class CurriculumAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
private ArrayList<Curriculum> mArrayListCurriculum;
public CurriculumAdapter(Context mContext, ArrayList<Curriculum> mArrayListCurriculum) {
this.mContext = mContext;
this.mArrayListCurriculum = mArrayListCurriculum;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_key_features, parent,false);
return new KeyFeatureViewHolder(v);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof KeyFeatureViewHolder) {
((KeyFeatureViewHolder) holder).mTextViewFeatureTitle.setText(mArrayListCurriculum.get(position).getTitle());
}
}
#Override
public int getItemCount() {
return mArrayListCurriculum == null ? 0 : mArrayListCurriculum.size();
}
public static class KeyFeatureViewHolder extends RecyclerView.ViewHolder {
public TextView mTextViewFeatureTitle;
public KeyFeatureViewHolder(View itemView) {
super(itemView);
mTextViewFeatureTitle = (TextView) itemView.findViewById(R.id.txtFeature);
}
}
}
The code below is my fragment with dummy arraylist data
public class CourseCurriculumFragment extends Fragment {
private FragmentInterface mFragmentInterface;
private ArrayList<Curriculum> mArrayListCurriculum;
private ArrayList<Material> mArrayListMaterial;
private RecyclerView mRecyclerViewCurriculum;
private LinearLayoutManager mLinearLayoutManager;
private CurriculumAdapter mCurriculumAdapter;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_course_curriculum, container, false);
return view;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initView(view);
}
private void initView(View view) {
mArrayListMaterial = new ArrayList<>();
mArrayListCurriculum = new ArrayList<>();
populateMaterials();
populateKeyFeatures();
mRecyclerViewCurriculum = (RecyclerView) view.findViewById(R.id.recyclerViewCurriculum);
mCurriculumAdapter = new CurriculumAdapter(getActivity(), mArrayListCurriculum);
mLinearLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerViewCurriculum.setLayoutManager(mLinearLayoutManager);
mRecyclerViewCurriculum.setAdapter(mCurriculumAdapter);
mRecyclerViewCurriculum.setItemAnimator(new DefaultItemAnimator());
}
private void populateMaterials() {
mArrayListMaterial.add(new Material("12:00","pdf","","Sample Text","0"));
mArrayListMaterial.add(new Material("12:00","pdf","","Sample Text","0"));
}
private void populateKeyFeatures() {
mArrayListCurriculum.add(new Curriculum("UNIT 1",mArrayListMaterial));
mArrayListCurriculum.add(new Curriculum("UNIT 2",mArrayListMaterial));
mArrayListCurriculum.add(new Curriculum("UNIT 3",mArrayListMaterial));
}
}
A bind method in a holder is a good way to pass data to it.
In your case this bind method should take in a Curriculum and a Material object as parameters.
Inside the onBindViewHolder method of the adapter, instead of reaching into the variables of the holder, you should call this bind method.
In the implementation of the method inside the you KeyFeatureViewHolder class you should use these passed parameters and display them in the appropriate UI elements.
Lastly, to get the Material object data into adapter, add ArrayList<Material> as a constructor parameter just like you did with Curriculum.
Use RecyclerView with header, title as header and materials as items of that header. Look at this example.
You need to design a custom list for yourself. For example take an object like this.
public class ListItem {
public curriculumName = null;
public materialName = null;
}
Now populate this list after you parse the JSON string. Get your first Curriculum and populate the object like this
private ArrayList<ListItem> mListItemArray = new ArrayList<ListItem> ();
for(curriculum : mArrayListCurriculum) {
ListItem mListItemHead = new ListItem();
mListItemHead.curriculumName = curriculum.getName();
// Set the header here
mListItemArray.add(mListItemHead);
for(material : curriculum.getMaterials()){
ListItem mListItem = new ListItem();
mListItem.materialName = material.getName();
// Add materials here
mListItemArray.add(mListItem);
}
}
Now, you've a list with headers and materials. When the materialName in your mListItemArray is null, it identifies that this is a header and vice versa.
Now the trick is to modify your adapter of your RecyclerView so that you can bind proper view to your items in your list.
You can find an indication from this answer on how you can achieve this desired behaviour.
Basically, the idea is to modify your getItemViewType to pass the proper view in your onBindViewHolder. Your getItemViewType might look like this.
#Override
public int getItemViewType(int position) {
if (mListItemArray.get(position).curriculumName != null) {
// This is where we'll add header.
return HEADER_VIEW;
}
return super.getItemViewType(position);
}

ListView not updating properly in Android

I am trying to update the content of my listview by adding stuff to it. Although the listview does update its contents the size still stays the same for some reason. So for example, if the original listview contains A, B, Y, Z and I add C and D to it, the updated list view will be: A, B, C, D. What am I doing wrong?
Here is some relavent code:
//in main activity...
//additionalSongs is an arraylist
addAdditionalSongs(additionalSongs);//add the additional songs to the main list
songTabFragment = new SongTabFragment();//update the list on the screen
...
private void addAdditionalSongs(ArrayList<Song> additionalSongs){
for(int i = 0; i < additionalSongs.size(); i++) {
songList.add(additionalSongs.get(i));
}
}
SongTabFragment class
public class SongTabFragment extends Fragment {
private ListView songView;
private Context context;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
context = activity;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.song_tab_layout, container, false);
songView = (ListView) rootView.findViewById(R.id.song_list); //get a reference to the ListView created in song_tab_layout
SongAdapter theAdapter = new SongAdapter(context, MainActivity.getSongArray());
songView.setAdapter(theAdapter); //pass the ListView object the appropriate adapter
return rootView;
}
}
SongAdapter class
public class SongAdapter extends BaseAdapter {
private ArrayList<Song> songArray;
private LayoutInflater songInf;
public SongAdapter(Context c, ArrayList<Song> grabbedSongArray){
songArray = grabbedSongArray;
songInf = LayoutInflater.from(c);
}
#Override
public int getCount() {
return songArray.size();
}
#Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}
#Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout listLayout = (LinearLayout)songInf.inflate(R.layout.song, parent, false);
//layout for each individual song in the list. Uses song.xml
TextView songView = (TextView)listLayout.findViewById(R.id.song_title);
TextView artistView = (TextView)listLayout.findViewById(R.id.song_artist);
Song currentSong = songArray.get(position);
songView.setText(currentSong.getTitle()); //pass data to textView objects in each list item
artistView.setText(currentSong.getArtist());
listLayout.setTag(position); //use the song's position in list as a tag
return listLayout;
}
}
It might be that the SongTabFragment is not being updated. Instead of accessing your song array via the MainActivity
MainActivity.getSongArray()
Why not add a method in your fragment to update the arraylist in the SongAdapter and then notify the list view that the data set has changed so that it will recreate the view based on the new array list.
Example
In fragment class
// Fragment code
public void updateAdapterArray(ArrayList<Songs> adapter) {
((SongAdapter) mListView.getAdapter()).setSongs(adapter);
}
In adapter class
//Adapter code
public void setSongs(ArrayList<Songs> adapter) {
this.songList = adapter;
notifyDataSetChanged();
}
In mainactivity
// your mainactivity code
SongTabFragment songFragment = (SongTabFragment) mFragmentManager.findFragmentById(R.id.fragContainer);
songFragment.updateAdapterArray(newSongList);
Check your getCount() method,
#Override
public int getCount() {
return list.getSize(); //it should return size of list. Not 4
}
Are you updating the item count in your list view? If the listview still only thinks there are 4 items in the list it will only display 4 items. You have to update the value the getCount() returns.

Inflating a View inside DialogFragment's onCreateView() causes a memory leak

I have a memory leak in my program. I have a ListView, each of its items has a Button, when user presses it, I want my program to show a DialogFragment with some EditText fields and a Button which would perform some action with the data from these fields and from the item that started the dialog. So, every time I open and close that dialog, my app's memory consumption increases and after many reopenings it reaches ~150 MB of RAM and then the app freezes (no OOM error or crash, just a freeze and slow down of the whole OS showing just a couple of MB of free RAM).
After some testing, I have discovered that the leak is caused, for some reason, by this line View v = inflater.inflate(R.layout.fragment_flag_dialog, container, false); (I have marked this in the code with a comment). If I "comment" this line, the leak is gone, RAM consumption of my app isn't increasing anymore after reopening. So, I would like you to help me find the problem why this line (or maybe something else) is causing a memory leak, because my knowledge isn't enough to find a clear clarification and a solution of this problem. The View should be garbage collected after I close the dialog, but, for some reason, it isn't.
Below is my code and, by the way, I instantiate and use the BaseAdapter provided below with the following code PopupListAdapter pListAdapter = new PopupListAdapter(getActivity(), someArrayList, someString); listView.setAdapter(pListAdapter); from another Fragment which is inside my Activity and which holds the ListView. Please don't pay attention to the class name PopupListAdapter. I don't use it in a popup window in this case, I use it in a normal Activity, I just designed it to be used in a popup window elsewhere and reused it here because it perfectly matches the situation.
public class PopupListAdapter extends BaseAdapter {
private ArrayList<HashMap<String, String>> data;
private LayoutInflater inflater;
private Context context;
private static final String KEY_NAME = "name";
private static final String KEY_DATE = "date";
private static final String KEY_COMMENT = "comment";
private static final String KEY_COMMENT_ID = "id";
private final DialogFragment dFrag;
public PopupListAdapter(Context context, ArrayList<HashMap<String, String>> dataArg, String number) {
data = dataArg;
inflater = (LayoutInflater) context.getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.context = context;
dFrag = FlagDialogFragment.newInstance("test", number);
}
public int getCount() {
return data.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null)
view = inflater.inflate(R.layout.popuplistitem, null);
TextView name = (TextView) view.findViewById(R.id.name);
TextView date = (TextView) view.findViewById(R.id.date);
TextView comment = (TextView) view.findViewById(R.id.comment);
HashMap<String, String> element = new HashMap<String, String>();
element = data.get(position);
name.setText(element.get(KEY_NAME));
date.setText(element.get(KEY_DATE));
comment.setText(element.get(KEY_COMMENT));
final Button flagButton = (Button) view.findViewById(R.id.flag_button);
flagButton.setTag(element.get(KEY_COMMENT_ID));
flagButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.i(Utils.TAG, "Comment's ID is " + flagButton.getTag());
dFrag.show(((ActionBarActivity) context).getSupportFragmentManager(), "flag_dialog");
}
});
return view;
}
public static class FlagDialogFragment extends DialogFragment {
private String commentId;
private String number;
public static FlagDialogFragment newInstance(String listItemTag, String number) {
FlagDialogFragment f = new FlagDialogFragment();
Bundle args = new Bundle();
args.putString("comment_id", listItemTag);
args.putString("number", number);
f.setArguments(args);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
commentId = getArguments().getString("comment_id");
number = getArguments().getString("number");
setStyle(DialogFragment.STYLE_NO_TITLE, 0);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
Log.i(Utils.TAG, "FlagDialogFragment's onCreateView() is called");
View v = inflater.inflate(R.layout.fragment_flag_dialog, container, false);// Something in this line is causing a memory leak!!!
TextView flagNumber = (TextView) v.findViewById(R.id.flag_number);
flagNumber.setText(number);
Button flagButton = (Button) v.findViewById(R.id.flag_button);
flagButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.i(Utils.TAG, "Comment id is " + commentId);
}
});
return v;
}
}
}
It's been a while since I saw this issue, but I keep notes on "gotchas" and this was one of them that might take a long time to debug.
Putting the dialogFragment in it's own class or declaring it not static should fix the memory leak.
Sorry I couldnt' give links but try it out and let others know this solution helped or not.
EDIT:
public static class FlagDialogFragment extends DialogFragment {
private String commentId;
private String number;
private View testView;
and inside your onCreateView
testView = inflater.inflate(R.layout.fragment_flag_dialog, container, false);
then on onDestroyView()
testView = null;
let me know how this works?

How to let OnClick in ListView's Adapter call Activity's function

I have an Android application with a ListView in it, the ListView will setup fine but now I want a image in the ListView to be clickable. I do this by using 2 classes, the Activity class (parent) and an ArrayAdapter to fill the list. In the ArrayAdapter I implement a OnClickListener for the image in the list that I want to be clickable.
So far it all works.
But now I want to run a function from the activity class when the onClick, for the image in the list, is run but I do not know how. Below are the 2 classes that I use.
First the Activity class:
public class parent_class extends Activity implements OnClickListener, OnItemClickListener
{
child_class_list myList;
ListView myListView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// setup the Homelist data
myList = new child_class_list (this, Group_Names, Group_Dates);
myListView = (ListView) findViewById(R.id.list);
// set the HomeList
myListView.setAdapter( myList );
myListView.setOnItemClickListener(this);
}
void function_to_run()
{
// I want to run this function from the LiscView Onclick
}
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3)
{
// do something
}
}
And the ArrayAdapter from where I want to call a function from the Activity class:
public class child_class_list extends ArrayAdapter<String>
{
// private
private final Context context;
private String[] mName;
private String[] mDate;
public child_class_list (Context context, String[] Name, String[] Date)
{
super(context, R.layout.l_home, GroupName);
this.context = context;
this.mName = Name;
this.mDate = Date;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.l_home, parent, false);
ImageView selectable_image = (ImageView) rowView.findViewById(R.id.l_selectable_image);
selectable_image.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{
// I want to run the function_to_run() function from the parant class here
}
}
);
// get the textID's
TextView tvName = (TextView) rowView.findViewById(R.id.l_name);
TextView tvDate = (TextView) rowView.findViewById(R.id.l_date);
// set the text
tvName.setText (mName[position]);
tvDate.setText (mDate[position]);
return rowView;
}
}
If anyone knows how to run the function in the activity class from the arrayadapter or how to set the image onClickListener in the Activity Class I would greatly apriciate the help.
Inside onClick() Do something like this:
((ParentClass) context).functionToRun();
Just for clarity to expand on provided answers
In a BaseAdapter you can get the parent class by calling this.getActivity();If you then typecast this to the actual activity class you can then call a function as per #AdilSoomro answer below so you actually end up with something like this
public class MyAdapter extends BaseAdapter<Long> {
public MyAdapter(Activity activity,
TreeStateManager<Long> treeStateManager, int numberOfLevels) {
super(activity, treeStateManager, numberOfLevels);
}
#Override
public void handleItemClick(final View view, final Object id) {
((MyActivity) this.activity).someFunction();
}
}
Then just declare someFunction in MyActivity to do what you want
protected void someFunction(){
// Do something here
}

Customizing onClickItem listner - ListActivity

I am creating a contact list view, diplaying user name,user email and user id. Now while clicking on particular item i want to post some event to server based on the user id. How to get the user id inside the onClickItem listener, as user id is long value. i am able to get the user name that means the text but not the user id.
listView has onCLicklistener that has parameter position.If you are using arrayList to inflate ListView then you can use this positon to get corresponding object.
You can try to set the user id to tag using View.setTag and View.getTag. There are two version available choose the one you need. This way you can attach the user id as tag and then get it back.
http://developer.android.com/reference/android/view/View.html#setTag(int, java.lang.Object)
You code should look like:
view.setTag(Long.valueOf(id));
.....
onClickListener(..) {
Long id = (Long)view.getTag();
}
You should make your own bean class which contains your userid and put it in List (or ArrayList)
then create an adapter by creating a class extending ArrayAdapter (for instance) to bind your list of this bean class
register a listener either using onItemClickListener or View listener in your row if you inflate your custom layout.
get the position and use it to retrieve your userid on your bean list.
Here's my snippet
public class ListActivity extends Activity implements OnItemClickListener {
private Context context;
private RowAdapter adapter;
private ArrayList<Row> rowList = new ArrayList<Row>();
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
context = ListActivity.this;
initRows();
adapter = new RowAdapter(context, rowList);
ListView listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(adapter);
listView.setOnItemClickListener(this);
}
private void initRows() {
Row row = new Row(1);
rowList.add(row);
row = new Row(2);
rowList.add(row);
}
public class Row {
long userid = 0;
public Row(long userid) {
this.userid = userid;
}
public long getUserid() {
return userid;
}
public void setUserid(long userid) {
this.userid = userid;
}
}
public class RowAdapter extends ArrayAdapter<Row> {
private Context context;
public RowAdapter(Context context, ArrayList<Row> bindList) {
super(context, R.layout.row_layout, bindList);
this.context = context;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = convertView;
if(view == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.row_layout, null);
}
//if you use some additional View you can retrieve your position using this
Button button = view.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
updateSelected(position);
}
});
return view;
}
}
//or if you rather just listen on row click then you can retrieve your position using this
#Override
public void onItemClick(AdapterView<?> arg0, View view, int position, long arg3) {
updateSelected(position);
}
private void updateSelected(int position) {
rowList.get(position).getUserid();
//have it your way
}
}

Categories

Resources