So I have this app in which I have to make a RecyclerView which contains a list of items that can be deleted/edited e.t.c.
I followed a tutorial on youtube and I made a custom CardView item on another layout and a custom adapter for that item.
Thing is, depending on the state of the item, I have to change something(ex. background color or text color).
When I do that in the RecyclerView activity I get NullPointerException even if I have the id's.
How can I edit those TextViews inside the programmatically generated list of items in the moment I make the Retrofit call?
boolean isEnded;
if(!endedAt.equals("null"))
{
endedAt = endedAt.substring(endedAt.indexOf("T") + 1, endedAt.lastIndexOf(":"));
isEnded=true;
}
else
{
endedAt="ongoing";
isEnded=false;
}
item.timeDifference=createdAt+" - "+endedAt;
if(externalSystem.equals("null"))
{
item.externalSystem="";
}
else
{
item.externalSystem = externalSystem;
}
Log.i("attr",externalSystem);
items.add(item);
itemAdapter=new ItemAdapter(getApplicationContext(), items);
recyclerView.setAdapter(itemAdapter);
if(isEnded) {
error-> externalSystemView.setTextColor(Color.BLACK);
}
The app is rather big, but I think you can get the idea from this piece of code.
Here is the error: Attempt to invoke virtual method 'void android.widget.TextView.setTextColor(int)' on a null object reference
public class ItemAdapter extends
RecyclerView.Adapter<ItemAdapter.ItemViewHolder>{
private Context context;
private ArrayList<Item> itemList;
public ItemAdapter(Context context, ArrayList<Item> itemList)
{
this.context=context;
this.itemList=itemList;
}
#Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater=LayoutInflater.from(parent.getContext());
View view=layoutInflater.inflate(R.layout.item_layout,parent,false);
ItemViewHolder itemViewHolder=new ItemViewHolder(view);
return itemViewHolder;
}
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
Item item=itemList.get(position);
holder.timeDifference.setText(item.timeDifference);
holder.title.setText(item.title);
holder.timeCounter.setText(item.timeCounter);
holder.externalSystem.setText(item.externalSystem);
holder.type.setText(item.type);
holder.project.setText(item.project);
MY IDEA
"holder.timeDifference.setTextColor(Color.parseColor(item.color));"
}
#Override
public int getItemCount() {
if(itemList!=null)
return itemList.size();
else
return 0;
}
public static class ItemViewHolder extends RecyclerView.ViewHolder
{
public CardView cardViewItem;
public TextView title;
public TextView project;
public TextView externalSystem;
public TextView timeDifference;
public TextView timeCounter;
public TextView type;
public ItemViewHolder(View itemView)
{
super(itemView);
cardViewItem=(CardView)itemView.findViewById(R.id.card_view_item);
title=(TextView)itemView.findViewById(R.id.title);
project=(TextView)itemView.findViewById(R.id.project);
externalSystem=
(TextView)itemView.findViewById(R.id.external_system);
timeDifference=
(TextView)itemView.findViewById(R.id.time_difference);
timeCounter=(TextView)itemView.findViewById(R.id.time_counter);
type=(TextView)itemView.findViewById(R.id.type);
}
EDIT: I think I found a way, but I don't know if it's the best one
The solution would involve changing your Item class a little.
Your problem is that you're not passing over the boolean trigger to your RecyclerView.Adapter from your Activity properly. E.g. isEndedBoolean's value to know what state the item is in. You have the right idea in the use of all three classes.
What I would suggest do is create a constructor in your Item class passing the values from your Activity to be used in your adapter. I feel it's easier to use getters and setters rather than assigning the variables straight from code like you have.
So let's begin,
boolean isEnded;
if(!endedAt.equals("null")) {
endedAt = endedAt.substring(endedAt.indexOf("T") + 1, endedAt.lastIndexOf(":"));
isEnded=true;
} else {
endedAt="ongoing";
isEnded=false;
}
String timeDifference = createdAt+" - "+endedAt;
if(externalSystem.equals("null")) {
externalSystem="";
} else {
externalSystem = externalSystem;
}
Log.i("attr",externalSystem);
items.add(new ItemModel(isEnded, timeDifference, externalSystem);
itemAdapter=new ItemAdapter(this, items);
recyclerView.setAdapter(itemAdapter);
itemAdapter.notifyDataSetChanged();
You'll notice how I'm adding a new ItemModel for each row of variables inside the RecyclerView to the array and then passing it that array to the Adapter. This is so that it's easier to know what variables are being passed to the Model and thus the corresponding row at the position inside the Adapter.
An example of the ItemModel class would look something like:
public class ItemModel {
// Getter and Setter model for recycler view items
private boolean isEnded;
private String timeDifference;
private String externalSystem;
//other variables, title etc etc
public ItemModel(boolean isEnded, String timeDifference, String externalSystem) {
this.isEnded = isEnded;
this.timeDifference = timeDifference;
this.externalSystem = externalSystem;
//only pass to the model if you can access it from code above otherwise to assign the variables statically like you have.
}
public boolean getIsEnded() {return isEnded;}
public String getTimeDifference() {return timeDifference;}
public String getExternalSystem() { return externalSystem; }
}
The information above is just a guideline for you to create a more efficient model framework to pass the data rather than using static variables.
Now to solve your problem you need to check if (item.getIsEnded()) and then change the text color corresponding to that if condition.
RecyclerView.Adapter onBindViewHolder would look like:
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
ItemModel item =itemList.get(position);
holder.timeDifference.setText(item.getTimeDifference());
holder.title.setText(item.title);
holder.timeCounter.setText(item.timeCounter);
holder.externalSystem.setText(item.getExternalSystem());
holder.type.setText(item.type);
holder.project.setText(item.project);
if (item.getIsEnded() {
holder.timeDifference.setTextColor(item.color);
} else {
holder.timeDifference.setTextColor(item.color);
}
}
The purpose of the Adapter is to inflate a layout, bind components to that layout and perform functionality to the items corresponding to the layout at the dedicated position. You need to know which item in your list is in which state, you won't be able to do that from your Activity alone. Be mindful of how useful the Adapter is in keeping the code from your Activity separate from the actual activity of your RecyclerView.
Related
I have a recycleview showing a list of audio files fetched from my audios.json file hosted on my server. i have a model class with a getter method getLanguage() to see the audio language. I would like to show only audio files of users preference in recycle view. Say for example, if user wants only english and russian i would like to show only list of russian and english. How can we achieve this? Right now the entire list is displayed.
public class AudioAdapter extends RecyclerView.Adapter<AudioAdapter.HomeDataHolder> {
int currentPlayingPosition = -1;
Context context;
ItemClickListener itemClickListener;
List<Output> wikiList;
public AudioAdapter(List<Output> wikiList, Context context) {
this.wikiList = wikiList;
this.context = context;
}
#NonNull
#Override
public HomeDataHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(context).inflate(R.layout.audio_row_layout,viewGroup,false);
HomeDataHolder mh = new HomeDataHolder(view);
return mh;
}
#Override
public void onBindViewHolder(#NonNull final HomeDataHolder homeDataHolder, int i) {
String desc = wikiList.get(i).getLanguage() + " • " + wikiList.get(i).getType();
homeDataHolder.tvTitle.setText(wikiList.get(i).getTitle());
homeDataHolder.tvotherinfo.setText(desc);
homeDataHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (itemClickListener != null)
itemClickListener.onClick(view,homeDataHolder.getAdapterPosition());
}
});
homeDataHolder.rippleLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (itemClickListener != null)
itemClickListener.onClick(view,homeDataHolder.getAdapterPosition());
}
});
}
#Override
public int getItemCount() {
return wikiList.size();
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
public void setClickListener(ItemClickListener itemClickListener) { // Method for setting clicklistner interface
this.itemClickListener = itemClickListener;
}
public class HomeDataHolder extends RecyclerView.ViewHolder {
TextView tvTitle,tvotherinfo;
MaterialRippleLayout rippleLayout;
public HomeDataHolder(View v) {
super(v);
this.tvTitle = v.findViewById(R.id.title);
this.tvotherinfo = v.findViewById(R.id.audioDesc);
this.rippleLayout = v.findViewById(R.id.ripple);
}
}
}
The general idea for this should be:
you have one list with all items
you have filter rules selected by the user
You filter items from number 1, to see which ones match the constraints and store this in another list.
Then the recycler view only shows the items of the list from number 3.
This means that recycler view's getItemCount would return the size of the filtered list, not the whole list.
Instead of passing the wikiList as it is, filter it then send it:
Lets say that you filled up the wikiList, before passing it to the adapter, filter it like this:
In the activity that you initialize the adapter in:
public class YourActivity extends ............{
........
........
//your filled list
private List<Output> wikiList;
//filtered list
private List<Output> filteredList= new ArrayList<Output>();
//filters
private List<String> filters = new ArrayList<String>();
//lets say the user chooses the languages "english" and "russian" after a button click or anything (you can add as many as you want)
filters.add("english");
filters.add("russian");
//now filter the original list
for(int i = 0 ; i<wikiList.size() ; i++){
Output item = wikiList.get(i);
if(filters.contains(item.getLanguage())){
filteredList.add(item);
}
}
//now create your adapter and pass the filteredList instead of the wikiList
AudioAdapter adapter = new AudioAdapter(filteredList , this);
//set the adapter to your recyclerview........
......
.....
......
}
I use above "english" and "russian" for language. I don't know how they are set in your response, maybe you use "en" for "english" so be careful.
I have a RecyclerView list with CheckBox in the CardView, using a simple onClickListener. But when index 0 is clicked index 10 is also shown as clicked. The same if index 1 is clicked so is 11, index 2 so is 12, and so on. How do I solve this problem?
Three steps to solve it, and can be used everywhere you have a checkbox widget and it will be resued.
Firstly, Each adapter has a corresponding 'bean' binds to it. for example, List, The T is the bean.
Secondly, add a boolean field 'ischecked' into 'bean' class.
Finally, use this field to reset the status of checkbox when recycleview reuses checkbox, and update it when status of checkbox being changed.
RecyclerView will reuse the layouts from previous rows when scrolling, so if you check item 1, and scroll, item 11 will still be checked unless told to not be.
Within your onBindViewHolder, you want to set the layout to the current state of the object you're displaying.
For example, say we have a list of Dogs, and we can mark if we've pet the Dog.
class Dog {
public boolean hasPet = false;
public String name;
}
Now, if you're displaying each Dog, you can do.
void onBindViewHolder(MyViewHolder holde, int position) {
Dog dog = list[position];
holder.name = dog.name;
// Remove any onCheckChangeListener or else we'll change the previously rendered dog.
holder.check.setOnCheckedChangeListener(null);
// Set the checkbox to the current state of the dog.
holder.check.setChecked(dog.hasPet);
// Add a new onCheckChangeListener with the current dog.
holder.check.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Logger.d("Clicked.");
dog.hasPet = isChecked;
}
});
}
This is because of the behavior of your RecyclerView. RecyclerView tries to recycle the views generated already with minimal changes in data. So in order to populate your data precisely in your list items, you need to tell exactly what it needs to show.
In your case, you need to keep the track of your checkbox click somewhere (maybe in a different array) so that you can populate the views accordingly. Let us consider the following code.
// Let us create an integer array having the same size of your list that is being passed to your RecyclerView
// Initially, all elements in the array is zero.
private int[] array = new int[yourList.size()];
Now in your onBindViewHolder, I think you have implemented the onClickListener for your CheckBox. Hence when you are clicking the CheckBox, set the corresponding element in your array to 1.
public void onClick(int position) {
// Checbox click action here
if (array[position] == 0)
array[position] = 1; // Use the position of your adapter to set the value.
else array[position] = 0; // Toggle the values on check/uncheck
}
Now in the onBindViewHolder while populating the CheckBox, check the value of its position in the array and set the checked status accordingly.
if(array[position] == 1) checkBox.setChecked(true);
else checkBox.setChecked(false);
Hope that helps!
The problem because you didn't keep the click state for each item. You can modify your pojo and adding a check state property or you can simply using SparseBooleanArray to hold the check state for all items.
Here the sample for using SparseBooleanArray.
Assuming we're using a simple pojo of User:
public class User {
private String id;
private String name;
// constructor, setter, getter
}
First, create a SparseBooleanArray to hold the selected flags like this:
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {
private List<User> mUsers;
private SparseBooleanArray mSelectedFlags;
public UserAdapter(List<User> users) {
mUsers = users;
mSelectedFlags = new SparseBooleanArray();
}
...
}
Second, save the state whenever you click the CheckBox in your ViewHolder:
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView tvName;
public TextView tvId;
public CheckBox cbxSelect;
public ViewHolder(View itemView) {
super(itemView);
...
// bind view here
cbxSelect.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
// set the state for specific item by its position.
mSelectedFlags.put(getAdapterPosition()), isChecked);
}
});
}
Last, set the state in onBindViewHolder:
#Override
public void onBindViewHolder(ContactsAdapter.ViewHolder viewHolder, int position) {
int itemPosition = viewHolder.getAdapterPosition();
User user = mUsers.get(itemPosition);
viewHolder.tvId.setText(user.getId());
viewHolder.tvName.setText(user.getName());
// set the check state for each item
// SparseBooleanArray will return false as default value
viewHolder.cbxSelect.setChecked(mFlagSelected.get(itemPosition));
}
The full adapter will be something like this:
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {
private List<User> mUsers;
private SparseBooleanArray mSelectedFlags;
public UserAdapter(List<User> users) {
mUsers = users;
mSelectedFlags = new SparseBooleanArray();
}
#Override
public UserAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
...
// inflate and return view holder.
return viewHolder;
}
#Override
public void onBindViewHolder(ContactsAdapter.ViewHolder viewHolder, int position) {
int itemPosition = viewHolder.getAdapterPosition();
User user = mUsers.get(itemPosition);
viewHolder.tvId.setText(user.getId());
viewHolder.tvName.setText(user.getName());
// set the check state for each item
// SparseBooleanArray will return false as default value
viewHolder.cbxSelect.setChecked(mFlagSelected.get(itemPosition));
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView tvName;
public TextView tvId;
public CheckBox cbxSelect;
public ViewHolder(View itemView) {
super(itemView);
...
// bind view here
cbxSelect.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
// set the state for specific item by its position.
mSelectedFlags.put(getAdapterPosition()), isChecked);
}
});
}
}
Overview: I'm having a chat application. Till now, I was using CursorAdapter with a Listview to load my chat items in the list. But now, I'm planning to refactor the code to use RecyclerView with RecyclerView.Adapter and a "Load More" functionality like whatsapp.
Issue: Memory consumption. With CursorAdapter, items not in viewable area were getting Garbage Collected, but now since I'm using an ArrayList of my CustomModal, once you load all the items in the list (by clicking on the "Load More" button) I'm seeing high memory consumption in the memory logs (No Garbage Collection).
My guess is now, I'm loading all the items in an ArrayList and that is causing the issue. Is that it?
Is there a way to avoid the issue or optimize the problem?
EDIT:
Can't post the complete code here, but here is a snippet of the kind of Adapter that I've implemented:
public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.MyViewHolder> {
private ArrayList<MyModal> mMyModals;
public MessageAdapter(ArrayList<MyModal> mMyModals) {
this.mMyModals = mMyModals;
//... Some fields initialization here
}
public void changeList(ArrayList<MyModal> myModals, boolean isLoadMoreEnabled){
this.mMyModals = myModals;
//... Some fields initialization here
notifyDataSetChanged();
}
public void toggleLoadMore(boolean isLoadMoreEnabled){
if(isLoadMoreEnabled){
//..Checks if load more is already enabled or not
//..If not then enables it by adding an item at 0th poition of MyModal list
//..Then notifyDataSetChanged()
}else{
//..Checks if load more is already disabled or not
//..If not then disables it by removing an item at 0th poition of MyModal list
//..Then notifyDataSetChanged()
}
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MyViewHolder messageViewHolder = null;
View itemLayoutView = null;
MyModal.MessageType messageType = MyModal.MessageType.getMessageTypeFromValue(viewType);
switch (messageType){
case MESSAGE_TYPE1:
itemLayoutView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout1, null);
messageViewHolder = new Type1ViewHolder(itemLayoutView);
break;
case MESSAGE_TYPE2:
itemLayoutView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout2, null);
messageViewHolder = new Type2ViewHolder(itemLayoutView);
break;
}
return messageViewHolder;
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
final MyModal myModal = mMyModals.get(position);
MyModal.MessageType messageType = myModal.getMessageType();
holder.initialize(myModal);
}
#Override
public int getItemCount() {
return (mMyModals != null)?mMyModals.size():0;
}
#Override
public int getItemViewType(int position) {
return mMyModals.get(position).getMessageType().getValue();
}
public abstract class MyViewHolder extends RecyclerView.ViewHolder {
public MyViewHolder(View itemLayoutView) {
super(itemLayoutView);
}
public abstract void initialize(MyModal myModal);
}
class Type1ViewHolder extends MyViewHolder {
//...Variables
public Type1ViewHolder(View itemLayoutView) {
super(itemLayoutView);
//...variables initialization here
}
#Override
public void initialize(MyModal myModal) {
//...Setting values in view using myModal
}
}
class Type2ViewHolder extends MyViewHolder {
//...Variables
public TextViewHolder(View itemLayoutView) {
super(itemLayoutView);
//...variables initialization here
}
#Override
public void initialize(MyModal myModal) {
//...Setting values in view using myModal
}
}
}
First of all :
public void changeList(ArrayList<MyModal> myModals, boolean isLoadMoreEnabled){
this.mMyModals = myModals;
//... Some fields initialization here
notifyDataSetChanged();
}
Here you are creating a new arraylist and assigning it to your mMyModals. This means there are 2 arraylists at this point and they take up twice the amount of space than required. GC doesnt work the way you expect it to. Since the arraylist is initialized in your activity it will persist as long as the arraylist persists and so will the initial arraylist.
Instead of creating a new arraylist in your activity and passing it to changeList. Just clear your old arraylist and pass that.And also in adapter changeList method you can do the below
public void changeList(ArrayList<MyModal> myModals, boolean isLoadMoreEnabled){
this.mMyModals.clear();
this.mMyModels.addAll(myModels);
//... Some fields initialization here
notifyDataSetChanged();
}
Please let me know if i am not clear. Also show your activity code if this does not work.
Instead of replacing the whole ArrayList and calling notifyDataSetChanged, try adding the items to the ArrayList and then call notifyItemRangeInserted(int positionStart, int itemCount), maybe that could work. Also, you dont have to replace the Adapter's ArrayList. Your Activity/Fragment probably has the same ArrayList, just editing this list in your Activity/Fragment and then calling notifyItemRangeInserted(int positionStart, int itemCount) should do the trick. Also, instead of retrieving all the messages, you could also try to only get the next X amount of messages, so you wont retrieve the messages you already retrieved before (if you didn't do that already).
I'm trying to make a simple to-do list where you would long-press an item to mark it as 'done', in which case it will be greyed out and strikethrough.
I'm working on the strikethrough first and found some sample code here creating a strikethrough text in Android? . However the problem is that the setPaintFlags() method only seems to work on TextView whereas the items on my list are String. I can't cast a String to a TextView, and I found a workaround here but apparently it's highly discouraged to do it: Cast String to TextView . Also I looked up SpannableString but it doesn't seem to work for strings of varying length.
So I'm back at square one - is it at all possible to implement what I'm trying to do? Or will I have to store my list items differently instead?
Relevant code:
public class MainActivity extends ActionBarActivity {
private ArrayList<String> items;
private ArrayAdapter<String> itemsAdapter;
private ListView lvItems;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Setting what the ListView will consist of
lvItems = (ListView) findViewById(R.id.lvItems);
readItems();
itemsAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items);
lvItems.setAdapter(itemsAdapter);
// Set up remove listener method call
setupListViewListener();
}
//Attaches a long click listener to the listview
private void setupListViewListener() {
lvItems.setOnItemLongClickListener(
new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> adapter,
View item, int pos, long id) {
// Trying to make the onLongClick strikethrough the text
String clickedItem = items.get(pos);
//What do I do here??
// Refresh the adapter
itemsAdapter.notifyDataSetChanged();
writeItems();
// Return true consumes the long click event (marks it handled)
return true;
}
});
}
Let's take a step back and consider your app. You want to show a list of jobs to the user. Each job has a description. And each job has two possible states: 'done' or 'not done'.
So I would like to introduce a class 'Job'
class Job
{
private String mDescription;
private boolean mDone;
public Job(String description)
{
this.mDescription = description;
this.mDone = false;
}
// ... generate the usual getters and setters here ;-)
// especially:
public boolean isDone()
{
return mIsDone;
}
}
This way your ArrayList 'items' becomes be a ArrayList< Job >. Wether a job is done or not will be stored together with its description. This is important because you want to show the current state of the job to the user by changing the look of the UI element, but you need to keep track of the job's state on the data level as well.
The UI element - the TextView - will be configured to present information about the job to the user. One piece of information is the description. The TextView will store this as a String. The other piece of information is the state (done/ not done). The TextView will (in your app) store this by setting the strike-through flag and changing its color.
Because for performance reasons a ListView uses less elements than the data list ('items') contains, you have to write a custom adapter. For brevity's sake, I'm keeping the code very simple, but it's worth the time to read up on the View Holder pattern:
Let's use a layout file 'mytextviewlayout.xml' for the list rows:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Medium Text"
android:id="#+id/textView"/>
</LinearLayout>
Now the code for the adapter looks like this:
EDIT changed from ArrayAdapter to BaseAdapter and added a view holder (see comments):
public class MyAdapter extends BaseAdapter
{
private ArrayList<Job> mDatalist;
private int mLayoutID;
private Activity mCtx;
private MyAdapter(){} // the adapter won't work with the standard constructor
public MyAdapter(Activity context, int resource, ArrayList<Job> objects)
{
super();
mLayoutID = resource;
mDatalist = objects;
mCtx = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
if (rowView == null) {
LayoutInflater inflater = mCtx.getLayoutInflater();
rowView = inflater.inflate(mLayoutID, null);
ViewHolder viewHolder = new ViewHolder();
viewHolder.tvDescription = (TextView) rowView.findViewById(R.id.textView);
rowView.setTag(viewHolder);
}
ViewHolder vholder = (ViewHolder) rowView.getTag();
TextView tvJob = vholder.tvDescription;
Job myJob = mDatalist.get(position);
tvJob.setText(myJob.getJobDescription());
if (myJob.isDone())
{
// apply changes to TextView
tvJob.setPaintFlags(tvJob.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
tvJob.setTextColor(Color.GRAY);
}
else
{
// show TextView as usual
tvJob.setPaintFlags(tvJob.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
tvJob.setTextColor(Color.BLACK); // or whatever is needed...
}
return rowView;
}
#Override
public int getCount()
{
return mDatalist.size();
}
#Override
public Object getItem(int position)
{
return mDatalist.get(position);
}
#Override
public long getItemId(int position)
{
return position;
}
static class ViewHolder
{
public TextView tvDescription;
}
}
Due to the changed adapter,
in the MainActivity, you have to declare 'items' and 'itemsAdapter' as follows:
private ArrayList<Job> items;
private MyAdapter itemsAdapter;
...and in your 'onCreate()' method, you write:
itemsAdapter = new MyAdapter<String>(this, R.layout.mytextviewlayout, items);
Don't forget to change the 'readItems()' and 'writeItems()' methods because 'items' now is a ArrayList< Job >.
Then, finally, the 'onItemLongClick()' method:
EDIT use 'parent.getItemAtPosition()' instead of 'items.get()', see comments
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
{
// items.get(position).setDone(true);
Object o = parent.getItemAtPosition(position);
if (o instanceof Job)
{
((Job) o).setDone(true);
}
// and now indeed the data set has changed :)
itemsAdapter.notifyDataSetChanged();
writeItems();
return true;
}
I am trying to figure out how to find the position of a custom ParseObject in a ParseQueryAdapter<> so that I can set the position of a spinner or check rows of a check list in my Android project.
I am currently using a custom adapter extending ParseQueryAdapter<Vendor> where Vendor extends ParseObject. I also have an Item that extends ParseObject that is associated with a Vendor. In this specific example, if I want to edit an Item, I want the previously chosen Vendor displayed in the spinner. I want to set the selection of a spinner that is backed by my custom ParseQueryAdapter<Vendor>. Before I integrated Parse, I was using an ArrayAdapter<CharSequence> which could do:
String vendorName = Vendor.getName();
int position = adapter.getPosition(vendorName);
spinner.setSelection(position);
I was thinking that I could get the position of a Vendor object like when I was just using the String name in the array, but ParseQueryAdapter doesn't have a getPosition method. I was thinking of making a custom method to do so, but am at a loss of how to find the position of the Vendor. I am also thinking that I might need .isEqualTo(Vendor vendor) method in my Vendor class.
You need to facade the ParseQuery Adapter, do something like this :
public class CustomListAdapter extends BaseAdapter {
private final YourParseAdapter adapter;
public CustomListAdapter(final Context context) {
this.adapter = new YourParseAdapter(context);
adapter.registerDataSetObserver(new DataSetObserver() {
#Override
public void onChanged() {
super.onChanged();
notifyDataSetChanged();
}
});
}
public void reloadObjects() {
adapter.loadObjects();
}
// Customize the layout by overriding getItemView
#Override
public View getView(int i, View v, ViewGroup viewGroup) {
View result = adapter.getView(i, v, viewGroup);
// HERE YOU HAVE THE VIEW GENERATED BY PARSE AND THE ITEM POSITION
return result;
}
#Override
public int getCount() {
return adapter.getCount();
}
#Override
public Object getItem(int i) {
return adapter.getItem(i);
}
#Override
public long getItemId(int i) {
return adapter.getItemId(i);
}
}
I figured out what I needed to do. Instead of using a ParseQueryAdapter<Vendor>, I got my List<Vendor> from a separate query then I used an ArrayAdapter<Vendor> so that I could use the adapter.getPosition(obj) method. Since the ArrayAdapter uses .indexOf(Obj) which in turn uses .equals(Obj), I added to my Vendor object:
#Override
public boolean equals(Object o) {
return this.getObjectId().equals(((Vendor) o).getObjectId());
}
I just needed to have a way for my custom object to be compared.