I am new to Android, so this may seem like a basic question. But what I got is a very simple class called SingleItem, which has an integer and a String and a getter and setter for each. In my application, I got an ArrayList which holds a collection of SingleItem objects. I also have a layout with a ListView widget.
What I am trying to do is populate the ListView with my String value in SingleItem, but when a user selects an item from the ListView, I need the integer ID from that SingleItem value. How do I do this in Android development?
If you are using your own adapter to populate the list then in the getView() function when building the view to return you can call setTag() on the view you are returning and store the entire "SingleItem" object. Then in the onClickListener of the views you return you can retrieve your info using the getTag() method of the view that has been clicked.
EDIT:
Specified which onClickListener I am referring to
here is a bunch of pseudo code: create your own adapter. This will give the flexibility to do all kinds of things but important to you here is displaying only the relevant fields from your custom class and make more complicated listviews. a decent tutorial is here: http://developerlife.com/tutorials/?p=327
You will have to handle the other overrides of baseadapter but the key is assigning the value of singleItem.getString()
public class SingleItemAdapter extends BaseAdapter{
private ArrayList<SingleItem> m_items= new ArrayList<SingleItem>();
private Context mContext;
public SingleItemAdapter (Context c,ArrayList<SingleItem> items) {
mContext = c;
m_items= items;
}
.
.
.
#Override
public Object getItem(int arg0) {
return m_items.get(arg0);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.singleitemview, null);
}
SingleItem i=(SingleItem) getITem(position)
if(v!=null){
TextView tv=(TextView) v.findViewById(R.id.yourListItemView);
tv.setText(i.getStringValue());
}
}
}
After defining your custom adapter, you can then assign it to the listview and assign a listener to the OnItemSelectedListener. since this returns the position, you can tie that back to the position in your ArrayList of SingleItems.
.
.
.
SingleItemAdapter sia=new SingleItemAdapter(this,yourArray);
yourArrayList.setAdapter(sia);
yourArrayList.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View v, int position, long row) {
SingleItem si= yourArray.getItem(position);
//do something with si.getValue();
}
.
.
.
});
Related
I am getting a very strange bug in my application. To make it more clear I will create a similar example with minus code.
I am reading objects from Firebase Realtime Database with an addListenerForSingleValueEvent. While I am reading the objects, I am stored them in an Array that I passed to an Adapter in a Recycleview. At this point, I can say, after debugging, that all seems to work correctly.
Then in the Adapter, I have a code similar to this:
public class AdapterObject extends RecyclerView.Adapter<AdapterObject.ViewHolder> {
ArrayList<Object> objectList;
Context mContext;
public AdapterObject (Context context, ArrayList<Object> objectList){
this.mContext = context;
this.objectList = objectList;
}
#NonNull
#Override
public AdapterObject.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.object_grid_layout, parent,false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull AdapterObject.ViewHolder holder, int position) {
Object o = objectList.get(position);
Log.d("TAG", o.getAtribute());
if (o.getAtribute().equals("A")){
holder.atribute.setVisibility(View.VISIBLE);
}
#Override
public int getItemCount() {
return objectList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView atribute;
boolean favorite;
String descuento, precioOriginal;
public ViewHolder(#NonNull View itemView) {
super(itemView);
atribute = itemView.findViewById(R.id.atribute);
}
}
}
As you can see in the code if the current object has the attribute value == "A", then his Textview is displayed, otherwise, the Textview remains hidden.
All seems correct when I debug it because the objects and their attribute corresponds to the Database, but when I deploy the application in the Android simulator and I start going up and down on the Recycleview, the holders start to display the Textviews although the console debugs seems correct...
Is this normal in RecycleViews? How can I fix that? I have found this, do you think it has any relation?
This is an extract from the RecyclerView documentation
As the name implies, RecyclerView recycles those individual elements.
When an item scrolls off the screen, RecyclerView doesn't destroy its
view. Instead, RecyclerView reuses the view for new items that have
scrolled onscreen. This reuse vastly improves performance, improving
your app's responsiveness and reducing power consumption.
That means that when the view gets reused it will keep the current properties. It's up to you to change them when onBindViewHolder gets called.
In your specific case
#Override
public void onBindViewHolder(#NonNull AdapterObject.ViewHolder holder, int position) {
Object o = objectList.get(position);
Log.d("TAG", o.getAtribute());
if (o.getAtribute().equals("A")){
holder.atribute.setVisibility(View.VISIBLE);
} else. {
holder.atribute.setVisibility(View.GONE);
}
}
I'm new to android, but have a good JavaFX experience. I'm trying to create a custom view that i can reuse, but having a hard time figuring out the correct way to do it.
In javafx i could achieve this by: Creating a separate fxml file defining the layout of the custom view, then create a controller class linked to the fxml file, in that class, i'd have a method to retrieve the data model of the controller and use it to fill in the labels, etc.
The custom view i want would be
Constrained Layout
TextView (constrained to right anchor)
Round TextView (constrained to left anchor)
What is the best way to do this in android? Also, Is it possible to achieve this with a RecyclerView? If yes, how can i use a custom view for each item and set its data?
The question is broad. You may need additional research on creating views
Create a recyclerview in the main.xml,
a separate file with an item view.
You have 3 views in your item view - white background with margins (linearlayout?), right textView, and left textview.
The left textview should have android:background="drawable/round_shape" and round_shape.xml defined in your drawables folder. Everything is done in 3 xml files, main.xml for recyclerview, item.xml, round_background.xml. Then, the recyclerview adapter to bind the textviews with your array, and recyclerview initialization
A typical RV adaptor
public class MyRV extends RecyclerView.Adapter<MyRV.ViewHolder> {
private List<MyModelItemWith2Strings> mDataSet; // You may need to setup an array,
// with 2 String objects - for the right and left textviews
// Use an array of class with 2 elements rather than <String>, e.g. List<MyModelItemWith2Strings>
// pass your model here
// this setData will be used to provide the contents for the textviews
void setData(List< /* set your 2 string class here*/ > dataSet) {
mDataSet = dataSet;
}
static class ViewHolder extends RecyclerView.ViewHolder {
// Here you bind item TV's
// first you declare textviews that you will use to fill with data
// Add any other item views you will need to fill in
public TextView tv;
public TextView tv2;
public ViewHolder(LinearLayout v) {
super(v);
// Bind itemview views here. Put R.id.tv from your itemview.xml
tv = v.findViewById(R.id.....);
tv2 = v...
}
}
// Add your itemview layout here
#Override
public MyRV.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LinearLayout v = (LinearLayout) LayoutInflater.from(parent.getContext())
.inflate(/***R.layout.item_view***/, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder( MyRV.ViewHolder h, int position) {
// get content from your model (the above list) and fill in the the itemview textviews
String a= mDataSet.get(position).getItem1();
String b = mDataSet.get(position). getItem2();
...
h.tv.setText(a);
// set clickers if you want to. The clicker class is below.
h.tv.setOnClickListener(new Click(position));
h.tv2.setText(...)
}
// This is obligatory to pass for your RV to initialize. It won't work if you don' t tell Android how to count your array soze
#Override
public int getItemCount() {
return mDataSet.size();
}
// These are my implementation of clickers. I prefer to put them in the nested class of the adapter.
private class Click implements OnClickListener {
private int pos;
Click(int position) {
pos = position;
}
#Override
public void onClick(View p1) {
// get data from your array on click
mDataSet.get(pos);
// Use pos as position on the array, mData.get(pos)
}
}
}
Then, in your main class set a recyclerview
RecyclerView rv = (RecyclerView) findViewById(R.id.rv_In_Main_Xml);
// just additional tunings.
rv.setHasFixedSize(true);
rv.setLayoutManager(new LinearLayoutManager(context)); // <- context = this, if you are in the Main activity
Then set the adapter
MyRV rva = new MyRV();
rva.setData(myArray_with_2_string_objects_to_fill_tvs);
rv.setAdaptor(rva);
And your recycler view gets filled with data
I'm creating an app for a bus station, to give the schedule. For that i'm using a custom listview. Here it is:
class custom_adapter extends ArrayAdapter<String>{
public custom_adapter(Context context, ArrayList<String> horarios) {
super(context, R.layout.costum_listview ,horarios);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater horarioInflater = LayoutInflater.from(getContext());
View costumView = horarioInflater.inflate(R.layout.costum_listview, parent, false);
String singleHorario = getItem(position);
TextView hora = (TextView) costumView.findViewById(R.id.Hora);
TextView nota = (TextView) costumView.findViewById(R.id.Nota);
hora.setText(singleHorario);
nota.setText(" ");
return costumView;
}
}
Now as you can see I have just 2 texViews yet, the "hora" is to show the timers of the bus, the "nota" is for some notes, like someday the bus don't go or something like that. And my problem is exactly on that "nota" textview. I have dozens of arrayList's passing to this custom ListView, and so dozens and dozens of timers, and there are some timers that I need to put a note and other that I don't. So, can I had another argument to this custom ListView, like a boolean or something, so I can do a if / else in that code to put a note on each one. What do I need to change in order to do that ? I've been trying, but didn't quite managed to do that.
Instead of using a String as the argument for your ArrayAdapter, create a custom class and use that instead.
That way you can pass all the information you want into the adapter and show it however you like.
public class custom_adapter extends ArrayAdapter<MyClass>{
public custom_adapter(Context context, ArrayList<MyClass> horarios) {
super(context, R.layout.costum_listview ,horarios);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater horarioInflater = LayoutInflater.from(getContext());
View costumView = horarioInflater.inflate(R.layout.costum_listview, parent, false);
MyClass singleHorario = getItem(position);
TextView hora = (TextView) costumView.findViewById(R.id.Hora);
TextView nota = (TextView) costumView.findViewById(R.id.Nota);
hora.setText(singleHorario.hora);
nota.setText(singleHorario.nota);
return costumView;
}
And the new class
public class MyClass {
public String hora;
public String nota;
}
How about making a class for holding two values 'hora' and 'nota', and with, lets say, boolean isNotaAvailable() method. Then in getView() you just make something like this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater horarioInflater = LayoutInflater.from(getContext());
View costumView = horarioInflater.inflate(R.layout.costum_listview, parent, false);
YourClassName singleHorario = getItem(position);
TextView hora = (TextView) costumView.findViewById(R.id.Hora);
TextView nota = (TextView) costumView.findViewById(R.id.Nota);
// set hora text
hora.setText(singleHorario);
// check if nota is available, if true - set nota text
if(singleHorario.isNotaAvailable()) {
nota.setText(singleHorario.getNota())}
else nota.setVisibility(View.GONE);
return costumView;
}
It's just an idea, tell me if it helps :)
Just extend BaseAdapter, so you can define the data structure.
class custom_adapter extends BaseAdapter
change ArrayList<String> horarios to ArrayList<Data> horarios
And the Data can be
public class Data{
private String hora;
private String nota;
private boolean shouldShowNota;
//write getter and setter here
}
at last, read the data in getView
Data data = getItem(position);
if (data.getShouldShowNota) {
nota.setText(data.getNote);
}
hora.setText(data.getHora);
There is ListView with correct values:
public class FragmentTab1 extends SherlockFragment {
ListView list;
LazyAdapter adapter;
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
list = (ListView) getActivity().findViewById(android.R.id.list); //also I tried view.findViewById(android.R.id.list)
............
adapter = new LazyAdapter(getActivity(), mSource);
list.setAdapter(adapter);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View rootView = inflater.inflate(R.layout.fragmenttab1, container, false);
return rootView;
}
when I try:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId(); //correct
int itemCount = list.getCount(); // 10 ps as show Logcat
if (R.id.save == id) {
CheckBox cb;
for(int i = itemCount - 1; i >= 0; i--) {
cb = (CheckBox)list.getChildAt(i).findViewById(R.id.checkBox1); //Error here
}
}
return true;
}
xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Save to database"
android:id="#+id/checkBox1" /> // same id
and adapter is next:
public class LazyAdapter extends BaseAdapter {
private Activity activity;
ArrayList<Bitmap> bitmapArray = new ArrayList<Bitmap>();
private ArrayList<Data> mObjects;
private static LayoutInflater inflater=null;
public ImageLoader imageLoader;
public LazyAdapter(Activity a, ArrayList<Data> mObjects1) {
activity = a;
mObjects = mObjects1;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return mObjects.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
Data item = mObjects.get(position);
View vi=convertView;
if(convertView==null)
vi = inflater.inflate(R.layout.item_internet, null);
TextView text=(TextView)vi.findViewById(R.id.title1);
ImageView image=(ImageView)vi.findViewById(android.R.id.icon);
text.setText(item.getmTitle());
bitmapArray.add(imageLoader.getBitmap());
imageLoader.DisplayImage(item.getmImageUrl(), image);
return vi;
}
I receive correct ListView, but receive error when I try click save button from action bar.
Probably, I should init CheckBox in adapter?
Somebody can help me?
list.getChildAt(i) will be null if the child item is not visible. So check for null before use.
So you cannot retrieve all checked items in this way.
Please post complete .xml and the definition of <Data>.
I'd think you'd get an indexoutofbounds but since it's null, this might be why: ListView getChildAt returning null for visible children
Also, put a log statement in your for loop to display the value of all variables concerned, so: i and itemCount etc.
And set a breakpoint just before the loop and run debug mode to step over to check the values as it loops through and you'll see what i value caused the nullpointer in the debugger or if you miss it, it will be in logcat
I know this is very old post. But I'm answering because people are still looking for a work around on ListView getChildAt() null pointer exception.
This is because the ArrayApdater is REMOVING and RECYCLING the views that are not visible yet on the ListView because of height. So that if you have 10 item views, and ListView can display 4 - 5 at a the time :
The Adapter REMOVE the item views at position 5 to 9, so that any attempt to adapter.getChildAt(5... to 9) will cause null pointer exception
The Adapter also RECYCLE the item view, so that any reference you made on position 3 for example will be lost when you scroll down to 5 to 9, and also any Input that you make on position 3 (EditText, Checkbox, etc.) will be recycled when you scroll down to 5 to 9 and will be reused at another position later (ex position 1, 2 or 3, etc.) with the same value
The only way I found to control this is to forget about getting the View and to have :
Attribute HashMap<Integer, Boolean> cbValues or any type you want for handling the values you want to use for each item on the list. The first type must be unique for item like item->getId() or position. Initialize it with new HashMap<>() in the Constructor;
Add InputListener for Input Views, (addTextChangedListener for EditText, setOnCheckedChangeListener for Checkbox, etc.) And on input, update the HashMap key (item.getId() or position) and value (editable.toString() or true or false). Ex. on #Override public void onCheckedChanged, put boolean result cbValues.put(item.getId(), b);
Prevent Adapter from using recycled convertView, remove condition if(convertView == null) so that adapter always inflate a brand new view instance. Because the view instance is new each time, you must set the value from HashMap each time also like if it already contains the key if(cbValues.containsKey(item.getId())){cbItem.setChecked(cbValue.get(cbItem.getId()))};. Probably in this case there is not tons of Items, so that smooth scrolling won't be a must.
And finally create public methods to get the Values outside of Adapter passing item->getId() Integer as parameter. Ex : 'public bool getCheckboxValueForItemId(int itemId) { return cbValues.get(itemId); }` . It will be easy then to select Item from Adapter
Here is the Codes at the end :
public class LazyAdapter extends BaseAdapter {
private Activity activity;
ArrayList<Bitmap> bitmapArray = new ArrayList<Bitmap>();
private ArrayList<Data> mObjects;
private static LayoutInflater inflater=null;
public ImageLoader imageLoader;
HashMap<Integer, Boolean> cbValues;
public LazyAdapter(Activity a, ArrayList<Data> mObjects1) {
activity = a;
mObjects = mObjects1;
cbValues = new HashMap<>();
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return mObjects.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
Data item = mObjects.get(position);
View vi=convertView;
// Remove convertView condition
//if(convertView==null)
vi = inflater.inflate(R.layout.item_internet, null);
TextView text=(TextView)vi.findViewById(R.id.title1);
ImageView image=(ImageView)vi.findViewById(android.R.id.icon);
Checkbox cbItem = (Checkbox) vi.findViewById(android.R.id.checkbox1);
text.setText(item.getmTitle());
bitmapArray.add(imageLoader.getBitmap());
imageLoader.DisplayImage(item.getmImageUrl(), image);
if(cbValues.containsKey(item.getId())) {
cbItem.setChecked(cbValue.get(cbItem.getId()))};
}
cbItem.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
cbValues.put(item.getId(), b);
}
});
return vi;
}
// itemId : unique identifier for an Item, not the position of Item in Adapter
public bool getCheckboxValueForItemId(int itemId) {
return cbValues.get(itemId);
}
}
I have a listView of custom views. Each view contains an editText. I'm trying to add listeners to the editText variables which I create in the getView(). The problem is that when I create the listeners it doesn't let me use the editText variable unless I make it final, but if I make it final, I guess I'm going to have problems when the row gets reused. This is a simple example of what my problem is:
private class MyAdapter extends ArrayAdapter<Date>{
public MyAdapter(Context context, int resource, int textViewResourceId,
List<Date> objects) {
super(context, resource, textViewResourceId, objects);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row;
if(convertView==null){
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(R.layout.listview_cell_single, parent, false);
}else{
row = convertView;
}
EditText et = (EditText) row.findViewById(R.id.editText_single);
et.setText("" + position);
et.setOnFocusChangeListener(new OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
if(!hasFocus){
et.setText("You edited row: " + position);
//Error: et must be final, position must be final
}
}
});
You should declare this EditText et as a class variable in MyAdapter. As
private class MyAdapter extends ArrayAdapter<Date>{
EditText et;
}
And then initiate it in getView(); method.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//do your stuff
........
et=(EditText) row.findViewById(R.id.editText_single);
}
This is a contract between scopes, i.e. the stack versus the heap. The reference created on the stack must be final to declare that the reference will not (and cannot) be changed since the reference is basically bound by the anonymous class (OnFocusChangeListener) using it.
The reference will not be reused since each stack will get its own copy of the reference to the EditText object.