I have an ExpandableListView. Each child has a CheckBox and a TextView. When a user taps a child row, the CheckBox is supposed to change its state (checked vs unchecked). This works correctly if the user taps directly on the check box. However, if the user taps on the text view (same row, immediately to the right of the check box), I get a null pointer error as soon as I try to refer to the check box. Can anyone see what is wrong?
EDIT: After reading the suggestion below, I did some investigation and realized that I can implement my clickListener inside my adapter under getChildView(). This solves my issue with the null pointer as I can easily get a reference to the child view.
However, it creates another issue that I see no elegant solution to. Each time a child is clicked, I need to make changes to the listview itself. The data for this list resides in an ArrayList whose scope is within the Activity (not in the Adapter). If my clickListener is in the Adapter, how can I call back to the Activity to make changes to the ArrayList?
This strikes me as a catch-22. If I want to be able to manipulate my data, I can't get a reference to the child view. But if I want a reference to my child view, my data is out of scope, so I can't manipulate it. How do people resolve this? I must be missing something.
I'll throw in the relevant adapter code where you can see the beginning of my attempt to add a child onClickListener.
Thanks again!
public class Settings extends Activity {
//this is the list I need to access from the adapter if my click listener is there
private ArrayList<Categories> categoriesList = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_smart_settings);
db = new EventsDB(this);
expListGroups = new ArrayList<ExpandListGroup>();
setGroups();
/* Set up the data adapter */
expAdapter = new ExpandListAdapter(Settings.this, expListGroups);
populateExpandableGroups();
}
public void setGroups() {
/* Create lists of the individual line items */
categoriesList = db.getCategoriesForClient(client);
locationsList = db.getLocationsForClient(client);
/* Add an item to the locations list to allow the user to add a new location */
locationsList.add(new ClientSmartFinderLocations(client, "Add Location", null, false));
ExpandListGroup categoryGroup = new ExpandListCategory("Choose Categories", categoriesList);
ExpandListGroup locationGroup = new ExpandListLocation("Choose Locations", locationsList);
expListGroups.add(categoryGroup);
expListGroups.add(locationGroup);
}
private void populateExpandableGroups() {
expandableList = (ExpandableListView) findViewById(R.id.expandable_list);
expandableList.setAdapter(expAdapter);
//I've removed this section and moved it to the adapter, per my edit above
// expandableList.setOnChildClickListener(new OnChildClickListener() {
//
// #Override
// public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
//
// /* Update the finder setting for this client */
// String category = categoriesList.get(childPosition).getCategory();
// Boolean isSelected = !categoriesList.get(childPosition).getIsSelected();
// db.setClientCategory(client, category, isSelected);
//
// /* Update the check box to provide feedback to the user */
// View view = parent.getChildAt(childPosition - parent.getFirstVisiblePosition() + 1);
// CheckBox checkBox = (CheckBox) view.findViewById(R.id.check_box);
//
// //error occurs here
// checkBox.setChecked(!checkBox.isChecked());
// return true;
// }
// });
expAdapter.notifyDataSetChanged();
}
public class ExpandListAdapter extends BaseExpandableListAdapter {
//other methods
#Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View view, final ViewGroup parent) {
view = getCategoryChildView(groupPosition, childPosition, view);
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
TextView tv = (TextView) v.findViewById(R.id.expand_list_item);
CheckBox checkBox = (CheckBox) v.findViewById(R.id.check_box);
String category = tv.getText().toString();
Boolean isSelected = !checkBox.isSelected();
db = new EventsDB(context);
db.setClientCategory(client, category, isSelected);
checkBox.setChecked(!checkBox.isChecked());
//here I need to do some things that require me to manipulate the categoriesList from the Activity class - but it is out of scope
}
});
return view;
}
}
<CheckBox
android:id="#+id/check_box"
android:layout_marginLeft="30dp"
android:focusable="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/expand_list_item"
android:paddingLeft="10dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="#dimen/smart_finder_settings_font_size"
android:textColor="#FFFFFF" />
I think that I have a solution. If I create an instance of the Settings activity class within the Adapter, I can add the usual getter/setter methods to the Settings class. I can then call these methods from the adapter to get at that list, modify it, and then push it back to the Settings class, and all should be well.
It's a basic OOP approach, but the mental hangup for me was the idea of instantiating an activity. I don't know if this is common or not, but it seems weird to me. Anyone have a more elegant solution?
Within the Settings class:
public ArrayList<Categories> getCategoriesList() {
return categoriesList;
}
public void setCategoriesList(ArrayList<Categories> list) {
categoriesList = list;
}
Within the adapter:
Settings settings = new Settings();
ArrayList<Categories> tempCategoriesList = new ArrayList<Categories>();
tempCategoriesList = settings.getCategoriesList();
//make changes to the list
settings.setCategoriesList(tempCategoriesList);
You should set the checkbox checked state inside your ExpandListAdapter instead of accessing the view inside the ExpandableListView directly.
Im not a pro, so take that in mind. but what about storing the data you need the clicklistener to manipulate in a gobal application class? Maybe not the most elegant, but might work.
user55410 is on the right track. In your Settings() Activity, make categoryList static, then when you change it with the getter/setter methods from the new instance you create in your it will modify all instances of Settings.categoryList.
Related
I am having adapter class, In that, I need to pass invoiceId to an Activity Class. I have seen some example like pass-through interface, but I lost track on following the code procedure.
Here Is My Adapter Class extends BaseAdapter
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
companyName = ct.getSharedPreferences("prefs", 0);
Log.d("test", "" + deliveryListBeans.size());
LayoutInflater inflater = (LayoutInflater) ct.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.list_vew_for_delivery_order, null);
TextView invoice = (TextView) v.findViewById(R.id.invoice);
final TextView delivery = (TextView) v.findViewById(R.id.do_delivery);
final DeliveryListBean dlb = deliveryListBeans.get(position);
invoice.setText(dlb.getInvoiceNo());
}
delivery.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ct.startActivity(new Intent(ct, EmployeesListForPopUp.class));
DeliveryOrdersListAdapter deliveryOrdersListAdapter=new DeliveryOrdersListAdapter(EmployeesListForPopUp.this);
}
});
}
Here is My Activity Class
public class EmployeesListForPopUp extends Activity {
private List<EmployeeIdNameBean> employeeIdNameBeans = new ArrayList<EmployeeIdNameBean>();
ListView listView;
SharedPreferences companyName;
EmployeePopUpAdapter employeePopUpAdapter;
private ImageView img1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_employees_list_for_pop_up);
I need to get invoiceId from Adapter Class. How?
You need to pass context of the activity in adapters constructor.
Then set activity.invoiceid value in clickevents of adapter.
One simple way is that you write a method in MainActivity
public void setInvoiceId(int invoiceId) {
// do what you want with invoiceId
}
and pass the instance of your activity to adapter
DeliveryOrdersListAdapter adapter = new DeliveryOrdersListAdapter(EmployeesListForPopUp.this);
and get it in your adapter and keep it
EmployeesListForPopUp myActivity;
public MyAdapter(EmployeesListForPopUp activity) {
myActivity = activity;
}
and where you need to pass invoiceId just call the method of main activity
myActivity.setInvoiceId(invoiceId);
General way of implementing it:
In the adapter class, where you set text to invoice TextView, you also can add a tag to it. Put attention - despite every item in the list is build from the same prototype, the tag (as well as text) will be uniq. The best way is to use "position" as value of the tag: invoice.setText(dlb.getInvoiceNo());
invoice.setTag(Integer.valueOf(position).toString());
You need to make your items in the list clickable (this is out of the scope of this question). So, when you click on some item - you can retrieve any data it has, and specifically tag - getTag();.
Then you send Intent to other activity, providing the tag as extra message. So that activity will "know" which item in the array list it is related to (i.e. tag == position, right?). And continue from there.
I implemented simple project that illustrates it. This project is simple demo and illustration of working with ArrayList adapter,
displaying the item in the ListView, clicking on some item and display relevant data in separated activity. Please download it and try (min API 21). Basic description is available in README file.
The project is here on the GitHub:
(corrected path)
https://github.com/everall77/ArrayListSimpleExmpl
I am still stuck with this issue, can anyone help. It seems that my problem is that I cant update the data list. I have tried every solution that I've searched for on google etc.. but half the time i'm not even sure that I'm doing the correct thing.
I've used the onResume() to call notifyDataSetChanged, it didn't work. I've tried putting a refresh method into the adapter which i then called in OnResume(). Again it didn't work. Some people suggest clearing the adpater (adapter.clear();) in onResume and then using the addAll() function to relist the data but nothing works.
There has to be a simple solution to this. I have literally been stuck on this for 2 days now. very frustrated.
Here's my Fragment code again...
enter code here
public class SavedAppFragment extends ListFragment {
private static final String TAG = "AppClicked"; //DEBUGGER
private ArrayList<App> mSavedApps;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Populate the ArrayList
mSavedApps = SavedAppData.get(getActivity()).getApps();
AppAdapter adapter = new AppAdapter(mSavedApps);
setListAdapter(adapter);
}
//LIST ITEM CLICKED: /*Control what happens when list item is clicked: I.E. Load up a quiz while putting an EXTRA key containg the package name of the App to be launhced should the user get the question correct */ #Override public void onListItemClick(ListView l, View v, int position,long id) { //Return the crime for the list item that was clicked App c = ((AppAdapter) getListAdapter()).getItem(position); Log.d(TAG, "was clicked");
//Start the Activity that will list the detail of the app
Intent i = new Intent(getActivity(), Quiz_Activity.class);
String name = c.getPackage();
i.putExtra("packagename", name);
startActivity(i);
}
private class AppAdapter extends ArrayAdapter {
private ArrayList<App> mSavedApps;
public AppAdapter(ArrayList<App> apps) {
super(getActivity(), 0, apps);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//If we weren't given a view, inflate one
if (null == convertView) {
convertView = getActivity().getLayoutInflater().inflate(R.layout.list_item_app, null);
//((AppAdapter) getListAdapter()).notifyDataSetChanged();
}
((AppAdapter) getListAdapter()).notifyDataSetChanged();
//Configure the view for this crime
App c = getItem(position);
TextView nameTextView = (TextView) convertView.findViewById(R.id.app_name);
nameTextView.setText(c.getName());
// nameTextView.setText(applicationInfo.loadLabel(packageManager));
TextView packageTextView = (TextView) convertView.findViewById(R.id.app_package);
packageTextView.setText(c.getPackage());
CheckBox appCheckBox = (CheckBox) convertView.findViewById(R.id.app_checked);
appCheckBox.setChecked(c.isChecked());
//Return the view object to the ListView
return convertView;
}
}
}
THANKS!!!
When you return to Activity B, the previous Activity B hasn't been destroyed. Thus, it skips the onCreate. Move all of the stuff you want to make sure happens every time into the onResume. I think you want to make your Adapter a class variable (I'll call it mAdapter) in onCreate, and add code that will get data from the list directly. If you need to do something, put a "refresh" function in the adapter. I'm assuming you have a custom Adapter, because I've never heard of AppAdapter. If you don't, then extend AppAdapter and add that functionality. Thus, your onCreate should look like this:
mAdapter = new AppAdapter(mSavedApps);
setListAdapter(mAdapter);
Your onRefresh could update the data contained in the adapter by some new update function, like so:
mAdapter.update(SavedAppData.get(getActivity()).getApps());
I have an activity which has a TextView button (btnChangeMode) that toggles the mode from "admin" to "guest". Depending on the mode chosen, I need to hide/show a button (btnAddListItems) within my listview row. The code i have, currently doesn't seem to be cutting it.
Code speaks easier, so here's the gist of my code:
My activity layout:
<FrameLayout>
<ListView> ... </ListView> <!-- which has its items populated from myCustomAdapter -->
<TextView> ... </TextView> <!-- this is my btnChangeMode -->
<FrameLayout>
nothing fancy in my activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
List listItems = ... // some method that gets objects from Database
ListView listView = (ListView) findViewById(R.id.list_view_id);
listView.setAdapter(new MyCustomAdapter(this, listItems));
}
I have a custom adapter that basically has two types of rows header & item. In the "header" row, I have button "btnAddListItems" that allows me to add items into the list view. I want this button to be visible only in admin mode.
I've overridden the necessary methods in myCustomAdapter (getviewTypeCount, getItemViewType, getCount and getView). here's the getView method:
#Override
public View getView(int position, View row, ViewGroup parent) {
if (row == null) {
if (getItemViewType(position) == ITEM_VIEW_TYPE_HEADER) {
return getHeaderRow();
} else {
return getItemRow();
}
}
if (getItemViewType(position) == ITEM_VIEW_TYPE_ROW) {
MyHolder holder = (MyHolder) row.getTag();
holder.populateNewContent();
}
return row;
}
....
private View getHeaderRow() {
View lRow = LayoutInflater.from(getContext()).inflate(R.layout.my_header, null);
mViewMode = new DetailsViewMode((Activity) getContext(), getChangeViewModeListener(lRow));
return lRow;
}
So mViewMode here is a convenience POJO class i wrote that contains btnChangeMode, a boolean variable that indicates current mode (isAdmin) and the caller activity. I don't believe there's anything specific to my problem, so i'm not including that code here. Will be glad to if someone thinks that'll help.
private ChangeViewModeFragment.ChangeViewModeListener getChangeViewModeListener(final View headerRow) {
return new ChangeViewModeFragment.ChangeViewModeListener() {
#Override
public void onViewModeChanged(boolean isViewModeAdmin) {
mViewMode.changeViewModeButtonText(isViewModeAdmin);
toggleAdminFeatures(isViewModeAdmin, headerRow);
}
};
}
private void toggleAdminFeatures(boolean isViewModeAdmin, View headerRow) {
TextView btnAddListItems = (TextView) headerRow.findViewById(R.id.add_button_id);
if (isViewModeAdmin) {
btnAddListItems.setVisibility(View.VISIBLE);
} else {
btnAddListItems.setVisibility(View.GONE);
}
}
This is the part that's not working as it should. btnAddListItems is always visible within my listview.
btnChangeMode is within my activity so to speak, while the btnAddListItems is within my Adapter (ListView). But my requirement necessitates this behavior of having the listner of an activity, change the row state of my listview.
I suspect that when i change the visibility of my header row's button, I don't have hold of the correct header row instance, if that makes sense :P.
NitroNbg's suggestion of having a private button didn't work, which leads me to believe that maybe the ListView just needs a kickstart to get refreshed?
But I've tried calling notifyDataSetChanged() at the end of my toggleAdminFeatures method but that doesn't seem to be doing the trick.
I'd try the following - create a private Button within your adapter class and within your getView() method, put a reference to btnAddListItems to it.
private Button buttonToHide;
//...
public View getView(...) {
//...
buttonToHide = (Button) row.findViewById(R.id.add_button_id);
//...
}
Then, inside your ChangeViewModeListener() simply refer to a method of your adapter class (of course you'll have to write it) that sets the buttonToHide.setVisibility(View.INVISIBLE)
Hopefully, since it's within the adapter it would be accessible.
EDIT: Just to point out if it isn't obvious - only refer to the button that's in the header row.
You don't need to notifyDataSetChanged(), coz dataset hasn't changed, try setting the visiblity to INVISIBLE
I have a ListView that contains items with checkboxes that should behave sometimes like a CHOICE_MODE_MULTIPLE and sometimes like a CHOICE_MODE_SINGLE. What I mean is for certain items in the list, when selected certain other items needs to be deselected whilst other can remain selected.
So when item A is checked I can find in my data the item B that needs to be unchecked but how do I get the UI to refresh to show this as I (I believe) cannot find the actual View that represents B but just it's data?
It sounds like you're off to a good start. You're right that you should be manipulating the underlying data source for item B when A is clicked.
Two tips that may help you:
Your getView() method in the Adapter should be looking at your data source and changing convertView based on what it finds. You cannot find the actual View that represents B because in a ListView, the Views are recycled and get reused as different data needs to be displayed. Basically, when an item is scrolled off the list, the View that was used gets passed to the getView() function as convertView, ready to handle the next element's data. For this reason, you should probably never directly change a View in a ListView based on user input, but rather the underlying data.
You can call notifyDataSetChanged() from within your adapter to signal that somewhere the underlying data has been changed and getView() should be called again for the elements currently displayed in your list.
If you're still having trouble, feel free to post some code that illustrates the specific problem that you're having. It's much easier to provide concrete advice when the problem is better defined. Hope this helps!
you can use singleChoice alartDialog, i have used like:
private int i = 0; // this is global
private final CharSequence[] items = {"Breakfast", "Lunch", "Dinner"}; // this is global
Button settings = (Button)view.findViewById(R.id.settings);
settings.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext());
//Title of Popup
builder.setTitle("Settings");
builder.setSingleChoiceItems(items, i,
new DialogInterface.OnClickListener() {
// When you click the radio button
public void onClick(DialogInterface dialog, int item){
i=item;
}
});
builder.setPositiveButton("Confirm",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
if (i == 0) {
//it means 1st item is checked, so do your code
}
if (i == 1) {
//it means 2nd item is checked, so do your code
} /// for more item do if statement
}
});
//When you click Cancel, Leaves PopUp.
builder.setNegativeButton("Cancel", null);
builder.create().show();
}
});
i have initialized i=0, so that for the very first time when user click on settings button, the first item is selected. and after then when user select other item, i have saved the i value so that next time when user click settings button, i can show user his/her previously selected item is selected.
I come across and solve this question today.
public class ItemChooceActivity extends Activity implements OnItemClickListener {
private int chosenOne = -1;
class Madapter extends BaseAdapter {
.....
.....
#Override
public View getView(final int position, View convertView,
ViewGroup parent) {
// TODO Auto-generated method stub
if (chosenOne != position) {
set the view in A style
} else {
set the view in B style
}
return convertView;
}
}
#Override
public void onItemClick(AdapterView<?> arg0, View view, int position,
long arg3) {
,,,,
chosenOne = position;
adapter.notifyDataSetChanged();
,,,
}
}
I'm developing a Androidapplication using ListView.
ListView have a one file in each and every ListItem. Here, I have set onItemClickin ListView. So, that if user clicks the ListItememail application gets open and attach the particular file in email. Its for the single File, this gets implemented and working fine.
Now I want attach the multiple file in email. i.e. the implementing the CheckBoxin each ListItemand checked items have to attached into the Mail.
I know its possible because its very similar to the file manager application that checking the multiple file and deleting the all file by clicking the single Button. But don't know how to do.
In you ListAdapter create a SparseBooleanArray
private SparseBooleanArray checkStatus;
This SparseBooleanArray stores the checked items. Now in getView do the following
#Override
public View getView(int position, View view, ViewGroup parent) {
ViewCache viewCache;
if (view == null){
viewCache = new ViewCache();
view = layoutInflater.inflate(R.layout.list_box, null, false);
viewCache.checkBox = view.findViewById(R.id.check_box);
viewCache.checkBox.setOnCheckedChangeListener(onCheckedChangeListener);
//other views in the list box
...........
}
vewCache = (ViewCache)view.getTag();
viewCache.checkBox.setTag(position);
viewCache.checkBox.setChecked(isChecked(position));
//set other views
........
}
This is the class ViewCache
private static class ViewCache{
CheckBox checkBox;
//other views in the list box
.......
}
This method checks whether the position is checked
private boolean isChecked(int position){
return checkStatus.get(position, false);
}
This is the onCheckChangeListener
CompoundButton.OnCheckedChangeListener onCheckedChangeListener = new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
checkStatus.put(((Integer)compoundButton.getTag()), b);
}
};
Finally you can get the checked items from the SparseBooleanArray checkStatus. Think it will help you.
You can try implementing your own ArrayAdapter. Initialize it with an array of your file objects and use it in the list view.
Next make a list of indexes that is visible by the adapter and can be manipulated from the outside. In your onItemClick method you have the position of the clicked item. If it's in that list remove it, otherwise - insert it. Let's call that list selection.
Next in your adapter's getView method construct a view with a checkbox inside. Again you have the current position, because it's passed as an argument. Set the checkbox state depending on the presence of the position in selection.
Finally implement your button's onClick so that it does whatever you do with your file objects only for those objects of your file_array whose positions are in your selection.
Hope that helps
In the above answers Sreejith has given a good explanation of how to store the states of the checked items in the list view using a SparseBooleanArray. This solves the first part of your problem.
The second part regarding the passing of the states of these items to the other activities can be achieved using the Application class.
Application class:
Base class for those who need to maintain global application state. Sometimes you want to store data, like global variables which need to be accessed from multiple Activities - sometimes everywhere within the application. In this case, the Application object will help you.
Here is a sample code for this:
public class TopClass extends Application {
private static TopClass topClass;
public TopClass getInstance()
{
return topClass;
}
#Override
public void onCreate ( )
{
super.onCreate();
topClass = this;
}
public ArrayList<String> arrList = new ArrayList<String>();
}
You need to set tag android:name="TopClass" in the application manifest file under the application tag. Something like this:
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:name="TopClass" >
....
....
Here is how you can access it from the activity:
TopClass top = (TopClass)getApplicationContext();
top.arrList.add("StackOverflow");
Now you can access the same variable from other activities similarly.