Accessing activity's state in CursorAdapter subclass - android

I'm writing a fairly complex ListView, which (among other things) requires formatting Views in each list item.
To give me full control over how the views are bound in each list item, I subclassed CursorAdapter in this manner:
public class MyAdapter extends CursorAdapter {
public final LayoutInflater mInflater;
public MyAdapter(Context context, Cursor c) {
super(context, c);
mInflater = LayoutInflater.from(context);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
final ToggleButton tButton = (ToggleButton) view.findViewById(R.id.tbutton);
tButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// start activity based on a pending intent
}
});
}
}
The issue is that my ToggleButton click listener should start an activity based on a pending intent. The pending intent is instantiated in the activity which utilises this customized adapter.
I'm aware I could have used a SimpleCursorAdapter in the main Activity with a ViewBinder so that launching the intent would only be necessary from the main Activity. But SimpleCursorAdapter is not quite right since I don't map columns straight to views.
However, the alternative I have here would suggest accessing the main Activity's data from a cursor subclass. I feel that there must be a better way to design the application.

Taking a cue from the API Demos - specifically EfficientAdapter, I have declared the CursorAdapter sublcass as an inner class of my activity.
This avoids passing the pending intent around outside of the main activity.
Source: http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List14.html

Related

How get value from adapter class to activity Class

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

What is the correct approach / logic to access the activity which inflates a layout with custom view from this view?

I'm developing an application in which I have a dashboard with 4 buttons. Each button starts a new Intent to a different Activity. Each Activity inflates the same layout that consists of the custom view. In the custom view is a ViewPager. In the ViewPager I want to display a strings that resides in the arrays in the mentioned above activities. Should I pass those arrays to the custom view and display it in the ViewPager? If so how can I pass them? How the Custom View would know from which activity they come from? I know that the 'sender' activity will be the one that is currently running but how can I check it? Or should I just make the arrays static and easily access them? In this case I would also need to know which activity to access. Please advise or maybe there is a better way of implementing it. Thank you.
Intents let you start other activities, also allows you to pass primitives, primitive arrays/Lists as well as custom Parcelable objects to other activities.
You can pass array data, starter activity name etc here. Or you can use getCallingActivity() to know the sender activity.
UPDATE:
You can pass data to custom views in onCreate() , if You have setter methods in Custom View class.
For more complex communication, Interface your activity to its child view as shown:
Interface:
public interface CustomViewParent {
//----add as many communication methods you want---
public String[] getData();
public void doSomething();
}
Activity:
public class MyActivity extends Activity implements CustomViewParent {
// ------------------------ INTERFACE METHODS ------------------------
// --------------------- Interface CustomViewParent ---------------------
#Override
public String[] getData() {
return new String[]{"data1","data2"};
}
#Override
public void doSomething() {
Toast.makeText(this,"Custom view called me !",Toast.LENGTH_SHORT).show();
}
// -------------------------- OTHER METHODS --------------------------
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//----here we set the parent--
((CustomView)findViewById(R.id.my_custom_view)).setParent(this);
}
}
Accept and use this interface in Custom View:
public class CustomView extends ViewPager {
// ------------------------------ FIELDS ------------------------------
private CustomViewParent parent;
// --------------------------- CONSTRUCTORS ---------------------------
public CustomView(Context context) {
super(context);
init();
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
// --------------------- GETTER / SETTER METHODS ---------------------
public void setParent(CustomViewParent parent) {
this.parent = parent;
}
// ---------------------------- INITIALIZE-----------------------------
private void init() {
//-----initialize/inflate custom views-----
//-----calls to parent, !! do not forget the null check !! ----
if(parent != null){
String[] data = parent.getData();
parent.doSomething();
}
//----add data from parent to Views etc---
}
}

Android Override Multiple Classes in Activity

The immediate answer is no, this is not possible in Java. I understand that, and this is a bit of a noob question, but i've been coding this way for some time now and i've always sort of felt it was a dirty method so i'd like to clarify what I am doing. Say for instance I have an Activity that is extending the Activity class so I can use onCreate etc etc...within that Activity, I have a SimpleCursorAdapter that populates a ListView.
String sql = "SELECT * FROM myTable";
Cursor data = database.rawQuery(sql, null);
String fields[] = {"field1", "field2", "field3"};
adapter = new CustomCursorAdapter(this, R.layout.custom_row, data, fields, new int[] { R.id.Field1, R.id.Field2, R.id.Field2 });
list.setAdapter(adapter);
I have named this CustomCursorAdapter because I am creating an entirely new class called CustomCursorAdapter that extends SimpleCursorAdapter so I can use methods such as bindView, newView etc to use buttons on my ListView objects.
public class CustomCursorAdapter extends SimpleCursorAdapter {
private Context myContext;
private myActivity parentActivity;
private Button delButton;
public CustomCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c, from, to);
myContext = context;
parentActivity = (myActivity) myContext;
#Override
public void bindView(View view, Context context, Cursor cursor) {
int idColumn = cursor.getColumnIndex("_id");
final int getId = cursor.getInt(idColumn);
final double increment = 0.25;
UnitsConversions convert = new UnitsConversions();
int nameColumn = cursor.getColumnIndex("name");
String getName = cursor.getString(nameColumn);
TextView name = (TextView)view.findViewById(R.id.GrainName);
name.setText(getName);
delButton = (Button)view.findViewById(R.id.DeleteButton);
delButton.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View arg0) {
parentActivity.deleteItem(getId);
}
});
#Override
public View newView(Context context, Cursor cursor, final ViewGroup parent) {
View view = View.inflate(context, R.layout.list_item, null);
return view;
}
I have scaled my code down a lot here just to use as an example so if I removed anything that makes this non working code I appologize, but non-working code isn't the purpose of my question. My question is, is this the only way to override methods unless I extend the activity itself with a SimpleCursorAdapter? It doesn't seem like a big deal, but when I have 10 different activities, all doing basically the same thing but with different ListViews and items and I have to create 10 different CustomCursorAdapter's it seems dirty and redundant. Maybe there is a way to create only 1 other activity and then pass in the items I need? It seems like it would be a lot cleaner to just use SimpleCursorAdapter instead of having to create a custom one and override the methods I need right in the Activity. If I didn't name this question properly, please feel free to edit.
Yes, there is a nicer way. You can create a subclass of Activity (and declare it abstract if you like) which implements overrided versions of every method in which you share code across your 10 classes. Then for each real Activity you need, extend that subclass of Activity and just make the changes you need to each method. If a method is different in each class but shares most of its code with the other Activities, then you can call the general super.method() and then apply the specific changes.

Setting Initially checked CheckedTextViews in ListView for choiceMode="multipleChoice"

I am having a really difficult time trying to work with android's ListView multipleChoice mode. Here is what I am trying to do:
I have a "players" button in a game setup screen. When this is clicked it opens another activity with a multipleChoice ListView of all the players in the database in CheckedTextViews. I have this working properly and when you click on a player they will be added or removed from the game via a query to the game_players table.
The problem I am having is in setting up the ListView so that the players that have already been added to the game get checked initially when the activity opens.
I have tried to do this by iterating over the entire list in the ListView activity but this doesn't work because the Views that are not currently visible can't be accessed to check.
So now I'm trying to do this in my extended SimpleCursorAdapter in bindView but I can't even get this simple code to work:
#Override
public void bindView(View _view, Context _context, Cursor _cursor) {
String name = c.getString(c.getColumnIndexOrThrow(from[0]));
this.player = (CheckedTextView)_view.findViewById(to[0]);
this.player.setText(name);
this.player.setChecked(true);
}
It correctly sets the player's name with setText(), but I can't get any of the boxes to check in bindView. Is there somewhere else I should be doing this or am I just doing it incorrectly?
Call setItemChecked() on the ListView for each checked position.
I had trouble with this as well and pieced together this solution. I thought about passing the ListView directly into the Adapter, but chose to create an interface instead, to avoid a circular reference between the ListView and the Adapter.
An interface to loosely couple the adapter to the list:
public interface CheckControl {
public void setChecked(int position, boolean value);
}
Custom Adapter:
private class MyAdapter extends CursorAdapter {
private CheckControl checkControl;
public MyAdapter(Context context, Cursor cursor, CheckControl checkControl) {
super(context, cursor, 0);
this.checkControl = checkControl;
...
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
...
checkControl.setChecked(cursor.getPosition(), cursor.getInt(COL_ENABLED) == 1);
...
}
}
And here's how I use these two elements. In my case I'm extending ListViewFragment, but the same could done in any other class that contains the ListView.
public class MyListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
....
CheckControl checkControl = new CheckControl() {
public void setItemChecked(int position, boolean checked) {
getListView().setItemChecked(position, checked);
}
};
setListAdapter(new MyAdapter(getActivity(), null, checkControl));
// Kick off the loader
getLoaderManager().initLoader(LOADER_1, null, this);
}
}

checkbox in List being checked unexpectedly

I have list of checkboxes in list binded by Custom simpleCursorAdapter.
In my custom simpleCursorAdapter, I've overridden newView and bindView with my modifications.
I've managed somehow to do multichoice.
The wierd thing is, after I delete any item from my list, the first item's checkbox is being checked all of a sudden. How does that happen? How can I solve it?
My SimpleCursorAdapter class:
public class MyListCursorAdapter extends SimpleCursorAdapter
{
private Context context;
private int layout;
public MyCursorAdapter(Context context, int layout, Cursor c,
String[] from, int[] to)
{
super(context, layout, c, from, to);
this.context = context;
this.layout = layout;
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent)
{
Cursor c = getCursor();
final LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(layout, parent, false);
CheckBox chkBoxBtn = (CheckBox) v.findViewById (R.id.deleteTwittChkBox);
if (chkBoxBtn != null)
{
chkBoxBtn.setChecked(false);
}
return v;
}
#Override
public void bindView(View v, Context context, Cursor c)
{
--binding view to my textsview in my items
//now it's the importat part:
CheckBox chkBoxBtn = (CheckBox) v.findViewById(R.id.deleteTwittChkBox);
if (chkBoxBtn != null)
{
chkBoxBtn.setId(Integer.valueOf(c.getString(c
.getColumnIndex(MyUsers.User._ID))));
chkBoxBtn.setOnClickListener(new OnItemClickListener(chkBoxBtn, v));
chkBoxBtn.setChecked(false);
}
}
//i couldnt find another way of doing this, but this is how i set listeners to my checkboxses
static ArrayList<String> checkedItemsList = new ArrayList<String>();
private class OnItemClickListener implements OnClickListener
{
private int mPosition;
private CheckBox chkBox;
OnItemClickListener(CheckBox mChkBox, View v)
{
chkBox = mChkBox;
chkBox.setChecked(false);
}
#Override
public void onClick(View v)
{
if (chkBox.isChecked())
{
checkedItemsList.add(String.valueOf(chkBox.getId()));
}
else
{
checkedItemsList.remove(String.valueOf(chkBox.getId()));
}
}
}
}
Here is the code part from the ListActivity class which describes the button that deletes the checked box items:
OnClickListener btListener = new OnClickListener()
{
public void onClick(View view)
{
// long[] items = listView.getCheckItemIds();
int x = 0;
Uri myUri = Uri
.parse("content://com.idan.datastorageprovider/users");
String where = "_id" + "=?";
//here i am tatking all checkboxes which ive added from the adapter class
ArrayList<String> checkedItemsList = MySimpleCursorAdapter.checkedItemsList;
for (String itemID : checkedItemsList)
{
getContentResolver()
.delete(myUri, where, new String[] { itemID});
checkedItemsList.remove(itemID);
}
}
};
I doubt that SimpleCursorAdapter is the right class to extend here.
Is the "checked" state connected to the data XML in any way? No? So you need your own custom adapter!
Basically all adapters have to implement a way to generate a view from a given element (more precisely an element position!). This will be called at any time where the list wants to display an element. Now, the trick it uses is to re-use formerly created list view elements that cannot be seen on screen any more! Thus: when you scroll your list down and an element disappears at the top, EXACTLY this view object will be re-used for the next appearing item.
So, when this method is called with a given "old" view that should be re-used, all contained elements will have to be set according the elements data. If a checkbox is part of this game, you will have to have a storage for the checked state! It is not sufficient to have a checkbox as there will be less checkbox objects as there are list elements!
SimpleCursorAdapters are there to - yeah - represent SIMPLE things. An XML describing data (images and text, as the documentation states). Because of this simplicity all you have to do here is provide a method to create NEW element view objects - you are not intercepting the re-use process AT ALL! It basically only knows how to put the data into an existing view object - but it is lacking the knowledge of how to handle checked/unchecked boxes!
Your solution: write your own BaseAdapter extension and do what has to be done: implement "getView" (and some other methods like getItem, getItemId and getCount). It's not hard at all!
This API Demo uses a BaseAdapter and the mExpanded state here is basically identical to your checkbox states!
Good luck!
You might need to call notifyDataSetChanged when you modify the data.
The problem is probably that you're calling setChecked from within the onItemClickListener. One hacky way around this is to do the following before and after you call setChecked from within your listener:
chkBox.setClickable(false);
chkBox.setChecked(false);
checkBox.setClickable(true);
This will prevent your onItemClickListener from getting called when you manually call setChecked.

Categories

Resources