Here is my method:
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
DataBaseHandler handler = new DataBaseHandler(getApplicationContext());
//set the spinner for measurement type
Spinner measurementTypeSpinner = (Spinner) findViewById(R.id.MeasurementTypes);
ArrayAdapter adapter = (ArrayAdapter) measurementTypeSpinner.getAdapter();
int typePos = adapter.getPosition(savedInstanceState.getString("measurementtype"));
measurementTypeSpinner.setSelection(typePos);
//set the spinner for the measurement unit
Spinner measurementUnitSpinner = (Spinner) findViewById(R.id.MeasurementSubValues);
ArrayAdapter arrayAdapter = (ArrayAdapter) measurementUnitSpinner.getAdapter();
int unitPos = arrayAdapter.getPosition(savedInstanceState.getString("measurementunit"));
measurementUnitSpinner.setSelection(unitPos);
//set the value
EditText value = (EditText) findViewById(R.id.unit_value);
value.setText(savedInstanceState.getString("value"));
/**
* The list view stuff
*/
ListView unitsList = (ListView) findViewById(R.id.units_list);
unitsList.setItemsCanFocus(true);
MeasurementType mType = handler.getMeasurementType(savedInstanceState.getString("measurementtype"));
//create the converter
Converter converter = new Converter(MeasurementType.getMeasurementType(savedInstanceState.getString("measurementtype")), savedInstanceState.getString("measurementunit"), savedInstanceState.getString("value"));
//convert the values
ArrayList<Unit> convertedValues = converter.convert();
//set the adapter for the list view
unitAdapter = new UnitListAdapter(this, convertedValues, mType);
unitsList.setAdapter(unitAdapter);
}
Basically, there is another activity with a list of items and when the user checks one, it updates the database setting an int property to 1, so that when the ArrayAdapter goes through an arraylist it picks up the property as 1 and displays it, instead of 0 in which case it doesn't display it.
Now on pressing the back button, both the spinners are populated with the values I stored, the value for the EditText is restored, but the ListView is not updated, yet when I leave the app and come back in, the value that was checked is there in the list...
This says to me that I might need to do something with onStop() and onRestart() could someone please advice me. The comment saying 'the list view stuff' is where I am trying to update the list view, it just isn't working and when I debug it won't go into the restore method at all, which is confusing.
EDIT
public class UnitListAdapter extends ArrayAdapter<Unit> {
private Context context;
private ArrayList<Unit> units;
private MeasurementType type;
public UnitListAdapter(Context context, ArrayList<Unit> units, MeasurementType type) {
super(context, R.layout.unit, R.id.unit_name, units);
this.context = context;
this.units = units;
this.type = type;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.unit, parent, false);
final TextView unitName = (TextView) rowView.findViewById(R.id.unit_name);
final EditText unitValue = (EditText) rowView.findViewById(R.id.unit_value);
if(units.get(position) != null) {
if(units.get(position).getView() == 1) {
unitName.setText(units.get(position).getUnitName());
unitValue.setText(units.get(position).getValue().toString());
} else {
unitName.setVisibility(View.GONE);
unitValue.setVisibility(View.GONE);
}
}
return rowView;
}
#Override
public void add(Unit u) {
units.add(u);
notifyDataSetChanged();
}
#Override
public void clear() {
units.clear();
notifyDataSetChanged();
}
#Override
public int getCount() {
return units.size();
}
}
As asked for. Sorry about confusion whilst editing.
onRestoreInstanceState() is not called when the user presses the back button. Most likely you need to move your logic to onResume(). I suggest that you read about the Activity lifecycle to get a better understanding about when each of the onXxx() methods are called.
After updating the list you need to call notifyDataSetChanged() to repopulate the listview.
http://developer.android.com/reference/android/widget/ArrayAdapter.html#notifyDataSetChanged()
Related
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;
}
This is a follow on from an earlier question: ImageButton within row of ListView android not working
But after suggestions from SO gurus it has been suggested I post a new question.
The issue is that I have a custom adapter that is not showing any data. I have looked into other questions, but it didn't provide a solution.
In my Main Activity I have a couple of buttons, one of them: ToDo, should create a row that displays data from a SQLite database, and depending on some factors (dates mainly), it shows a type of traffic light that is stored as a drawable.
Part of the Items in this Row is an Image Button that I want the user to be able to click and the image should change. The user should be able also to click on the actual row and a new activity starts.
The issue I have is that NO DATA is being displayed.
So, here is my code:
public class MainActivity extends Activity {
// definitions etc ...
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// definitions etc ...
}
public void ToDo(View v){ // the user has clicked in the ToDo button
IgroDatabaseHelper helper = new IgroDatabaseHelper(getBaseContext()); // create instance of SQLIte database
numRows = helper.NumEntries("ToDo"); // Get the number of rows in table
int i = 1;
ArrayList<RowItem> rowItems = new ArrayList<>();
RowItem myItem1;
while (i <= numRows){
// get items from database
// depending on value select different drawable
// put data into List Array of RowItem
myItem1 = new RowItem(TheWhat, R.drawable.teamworka, R.drawable.redtrafficlight, R.drawable.checkbox, TheWhenBy);
rowItems.add(myItem1);
//
i = i+ 1;
}
ListView yourListView = (ListView) findViewById(R.id.list);
CustomListViewAdapter customAdapter = new CustomListViewAdapter(this, R.layout.todo_row, rowItems);
yourListView.setAdapter(customAdapter);
}
The CustomListViewAdapter looks like this:
public class CustomListViewAdapter extends ArrayAdapter<RowItem> {
Context context;
ArrayList<RowItem> _rowItems;
public CustomListViewAdapter(Context context, int resourceId,
ArrayList<RowItem> rowItems) {
super(context, resourceId);
this.context = context;
_rowItems = rowItems;
System.out.println("I am in the custom Adapter class "+ _rowItems);
}
#Override
public View getView(int position, View convertView, ViewGroup parent){
System.out.println("This is the get view");
View row = convertView;
RowItem item = _rowItems.get(position);
// you can now get your string and drawable from the item
// which you can use however you want in your list
String columnName = item.getColumnName();
int drawable = item.getDrawable();
if (row == null) {
LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = mInflater.inflate(R.layout.todo_row, parent, false);
}
ImageButton chkDone = (ImageButton) row.findViewById(R.id.chkDone);
chkDone.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
View parentRow = (View) v.getParent();
ListView listView = (ListView) parentRow.getParent();
final int position = listView.getPositionForView(parentRow);
System.out.println("I am in position "+ position);
}
});
return row;
}
}
The RowItem Class looks like:
public class RowItem {
private String _heading;
private int _icon;
private int _lights;
private int _chkdone;
private String _date;
public RowItem(String heading, int icon, int lights, int chkDone, String date) {
_heading = heading;
_icon = icon;
_lights = lights;
_chkdone = chkDone;
_date = date;
System.out.println("adding stuff to my rows");
System.out.println("my column Name is " + heading);
System.out.println("My drawable int is "+ icon);
}
public String getColumnName() {
System.out.println("column Names is "+ _heading);
return _heading;
}
public int getDrawable() {
return _icon;
}
public int getLights(){
return _lights;
}
public int getchkDone(){
return _chkdone;
}
public String getDate(){
return _date;
}
}
I am obviously missing something, as I mentioned earlier, no data gets shown. I know that there are 2 row items that get passed to the CustomListViewAdapter. But I also know that the View getView inside the CustomListViewAdapter does not actually get called.
I hope I have put enough information/code, but if you feel I need to explain something further, please say.
Thanking all very much in advance!
I don't see a getCount() method. You should be overriding it like this:
#Override
public int getCount() {
return _rowItems.getCount();
}
Alternatively, calling super(context, resourceId, rowItems); should also fix it.
Your ListView thinks there are no items to display. If you are using your own array, you must override the getCount() method to indicate the number of items you want to display.
I have implemented a custom adapter and listItemView. The adapter sets an onlclick listener to a button that is on the listItemView. The onclick listener simply calls a private method I have in the adapter and passes it the position of the item to be removed. I know the position is correct because the database removes the proper item. I have found similar questions but have not been able to adapt the answers to work for me. Ideas and thoughts are greatly appreciated. Thanks.
Here is the full adapter class
public class FoodListAdapter extends ArrayAdapter<FoodListItem> {
//private
private int type;
public FoodListAdapter(Context context, ArrayList<FoodListItem> _objects) {
super(context, 0, _objects);
type = 0;
}
public FoodListAdapter(Context context, ArrayList<FoodListItem> _objects, int _type) {
super(context, 0, _objects);
type = _type;
}
#Override
public View getView(int position, View reusableView, ViewGroup parent)
{
//Cast the reusable view to a listAdpaterItemView
FoodListItemView listItemView = (FoodListItemView) reusableView;
//Check if the listAdapterItem is null
if(listItemView == null)
{
//If it is null, then create a view.
listItemView = FoodListItemView.inflate(parent, this, type);
}
if (type == 2)
{
Button deleteButton = (Button) listItemView.findViewById(R.id.listItemViewDeleteBTN);
deleteButton.setTag(new Integer(position));
}
//Now we need to set the view to display the data.
listItemView.setData(getItem(position));
return listItemView;
}
}
Here is a portion of my code used in fragment. Note that I have a private variable decalred in the class for listAdapter, though I don't think I need that.
private void displayListForDate(Calendar _date)
{
//get the list view
ListView listView = (ListView) getView().findViewById(1);
//Clear the listview by removing the listadapter and setting it to null.
//listView.setAdapter(null);
//First we must get the items.
Global global = (Global) getActivity().getApplicationContext();
DietSQLiteHelper database = global.getDatabase();
//Create a list to hold the items we ate. This list will then be added to the listView.
final ArrayList<FoodListItem> consumedList;
//Add the items to the array.
consumedList = database.getConsumed(_date.getTimeInMillis());
//Create an adapter to be used by the listView
listAdapter = new FoodListAdapter(getActivity().getBaseContext(), consumedList, 2);
//Add the adapter to the listView.
listView.setAdapter(listAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
consumedList.remove(position);
listAdapter.notifyDataSetChanged();
}
});
}
If you didn't implement "equals" method of FoodListItem, try to implements it.
I would suggest,
that you just update the underlying data, in your case its ArrayList<FoodItems>.
In your Adapter make this simple method and change :
private List<FoodListItem> myList = new ArrayList<FoodListItem>();
public FoodListAdapter(Context context, List<FoodListItem> myList) {
super(context, 0, myList);
type = 0;
this.myList = myList;
}
public FoodListAdapter(Context context, List<FoodListItem> myList, int _type) {
super(context, 0, myList);
type = _type;
this.myList = myList;
}
// Also update your getView() method to use myList!
#Override
public View getView(int position, View reusableView, ViewGroup parent)
{
...
listItemView.setData(myList.get(position));
public void removeItem(int positio){
if(myList != null){
myList.remove(position);
}
}
And then in class, you are creating the adapter (Activity/Fragment), just call the method.
// Update the underlying ArrayAdapter
adapter.removeItem(position);
// Notify the adapter, the data has changed
adapter.notifyDataSetChanged();
Also, you shouldnt open connection to your SQLiteDatabase on UI thread, because you are blocking it. You never know, how fast is the reading from disk going to be. If it takes too long, user can think, that your application froze and therefore, he leaves, which you dont want. I would suggest to use AsyncTask, you will find a lot of examples.
I went through and cleaned up my code and it now works, here is the working code. I really don't know exactly the difference other than I updated the IDs that I was using to assign and get views. If anyone can explain the cause for the issue I was having I would appreciate it.
Here is the snippet from my fragment where I create the list view and assign an adapter.
private void displayListForDate(Calendar _date)
{
//get the list view
ListView listView = (ListView) getView().findViewById(R.id.listView);
//Clear the listview by removing the listadapter and setting it to null.
//listView.setAdapter(null);
//First we must get the items.
Global global = (Global) getActivity().getApplicationContext();
DietSQLiteHelper database = global.getDatabase();
//Create a list to hold the items we ate. This list will then be added to the listView.
ArrayList<FoodListItem> consumedList;
//Add the items to the array.
consumedList = database.getConsumed(_date.getTimeInMillis());
//Create an adapter to be used by the listView
listAdapter = new FoodListAdapter(getActivity().getBaseContext(), consumedList, 2);
//Add the adapter to the listView.
listView.setAdapter(listAdapter);
}
and here is my adapter class.
public class FoodListAdapter extends ArrayAdapter<FoodListItem>
{
//private
private int type;
public FoodListAdapter(Context context, ArrayList<FoodListItem> _objects) {
super(context, 0, _objects);
type = 0;
}
public FoodListAdapter(Context context, ArrayList<FoodListItem> _objects, int _type) {
super(context, 0, _objects);
type = _type;
}
#Override
public View getView(int position, View reusableView, ViewGroup parent)
{
//Cast the reusable view to a listAdpaterItemView
FoodListItemView listItemView = (FoodListItemView) reusableView;
//Check if the listAdapterItem is null
if(listItemView == null)
{
//If it is null, then create a view.
listItemView = FoodListItemView.inflate(parent, type);
}
if (type == 2)
{
Button deleteButton = (Button) listItemView.findViewById(R.id.listItemViewDeleteBTN);
deleteButton.setTag(new Integer(position));
deleteButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Integer tag = (Integer) view.getTag();
deleteItem(tag.intValue());
}
});
}
//Now we need to set the view to display the data.
listItemView.setData(getItem(position));
return listItemView;
}
private void deleteItem(int position)
{
FoodListItem item = getItem(position);
Global global = (Global) getContext().getApplicationContext();
DietSQLiteHelper database = global.getDatabase();
database.removeConsumed(item.getID());
remove(getItem(position));
}
}
My activity contains a button and a ListView. The ListView contains a Spinner and an EditText view. I use the button each time I want to insert a new row entry in my Activity's ListView.
I have followed the instructions of previous stackoverflow threads like this one here: Android Listview with spinner and a checkbox on how to populate ListViews with focusable objects like Spinners.
My problem is that each time I dynamically add a new ListView entry in the ListView, the Spinner value of the previous ListView entry is lost (actuall the Spinner returns to its default setting). Say for simplicity that my Spinners are populated with the following data:
String spinner_data[] = {"apple", "banana", "pear", "watermelon", "strawberry"};
For example, if I select my first ListView's Spinner value to be "pear" and then I add a new ListView entry with my Button, the "pear" entry disappears from the 1st ListView Spinner and the default value "apple" appears).
Any help is appreciated!
This is my activity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
lv = (ListView) findViewById(R.id.lv);
da = new DataAdapter(this, new ArrayList<RawData>());
lv.setAdapter(da);
btn_new = (Button)findViewById(R.id.btn_new);
btn_new.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
da.add(new RawData(this));
da.notifyDataSetChanged();
}
});
}
The RawData class is this one:
public class RawData {
private int selected_position;
private ArrayAdapter<CharSequence> adapter;
public RawData(Context context)
{
adapter = ArrayAdapter.createFromResource(context, R.array.data, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
}
public ArrayAdapter<CharSequence> getAdapter()
{
return adapter;
}
/**
* get adapter's item text from selected position
* #return
*/
public String getText()
{
return (String) adapter.getItem(selected_position);
}
public int getSelected()
{
return selected_position;
}
public void setSelected(int selected)
{
this.selected_position = selected;
}
}
The DataArrayAdapter is the following:
public class DataArrayAdapter extends ArrayAdapter<RawData> {
private Activity myContext;
//private final List<RawData> list;
public DataArrayAdapter(Activity context, List<RawData> list)
{
super(context, R.layout.row_view, list);
myContext = context;
}
static class ViewHolder
{
protected RawData data;
protected Spinner spn;
protected EditText edt;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = null;
if ( convertView == null )
{
LayoutInflater inflator = myContext.getLayoutInflater();
view = inflator.inflate(R.layout.row_view, null);
final ViewHolder viewHolder = new ViewHolder();
viewHolder.edt = (EditText)view.findViewById(R.id.edt);
viewHolder.data = new RawData(myContext);
viewHolder.spn = (Spinner)view.findViewById(R.id.spn);
viewHolder.spn.setAdapter(viewHolder.data.getAdapter());
viewHolder.spn.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2_position, long arg3) {
// TODO Auto-generated method stub
viewHolder.data.setSelected(arg2_position);
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
}
});
// Update the TextView to reflect what's in the Spinner
view.setTag(viewHolder);
}
else
{
view = convertView;
}
// This is what gets called every time the ListView refreshes
ViewHolder holder = (ViewHolder) view.getTag();
holder.spn.setSelection(getItem(position).getSelected());
return view;
}
}
You're not handling the situation when getView gets a non-null convertView. In your example, after you add an item, ListView refreshes itself, and position that should display 'pear' gets an existing convertView (the one that was used previously to display 'apple') and you just pass it along to ListView without setting the data for current position. You cannot rely on ListView items to store any data, you should always set correct contents for position in getView method of your adapter.
Just to be clear, I see that your code sets the selected position in the end of getView but the issue is that whatever is tagged to your convertView when it is passed to getView by recycler mechanism in ListView is random and can be associated with any position it used to display before.
To make your application work you'll have to create array of selectedItem values for all your spinners, and attach it as a member to your adapter. You'll have to update the corresponding value on each OnItemSelected event and you'll add a new value for each "add" button click. And when you prepare your view in getView you'll just set the selected spinners index to corresponding value in your array.
From this answer in stack overflow and the sample project referred there, i got the Idea of RotationAsync, where a progress bar work fine with device rotation.
But my problem is, i have a listview with each row there is progress bar. And is there any way to retain the progress while rotation for reach row.
Me creating onclicklistener object for the button click listener in getview function of my adapter class. Where its onClick function call the AsyncTask class
Since each getview (row) is calling different instant of my AsyncTask, i cannot make it static of single ton class.
Any Idea on this.
Thanks.
So you have a ListView which I assume you have some adapter which in it's get view hosts the progress bars. However that progress must be backed by something right? So just save that data. Like I am assuming an adapter like so:
public class MyProgressBarAdapter extends BaseAdapter {
private ArrayList<Integer> mProgessValues;
private SparseArray<AsyncTask<?,?,?>> mTasks;
// No stored reference to a Context
private MyProgressBarAdapter() {
}
public void saveState(Bundle bundle) {
bundle.putIntegerArrayList(getClass().getName() + ".progressValues", mProgressValues);
}
public Object exportLiveState() {
return mTasks;
}
public static MyProgressBarAdapter restore(Bundle bundle, Object rawState) {
MyProgressBarAdapter adapter = new MyProgressBarAdapter();
Class<MyProgressBarAdapter> c = adapter.getClass();
ArrayList<Integer> progresses = null;
if (bundle != null) {
progresses = bundle.getIntegerArrayList(c.getName() + ".progressValues");
}
if (progresses == null) {
progresses = new ArrayList<Integer>();
}
adapter.mProgressValues = progresses;
if (rawState != null && rawState instanceof SparseArray) {
adapter.mTasks = (SparseArray<AsyncTask<?,?,?>>) rawState;
}
return adapter;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = getViewWithHolder(convertView, parent);
ViewHolder holder = convertView.getTag();
// set the appropriate things on the view elements.
holder.position = position;
holder.taskContainer = mTasks;
holder.progressBar.setProgress(mProgressValues.get(position));
convertView.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
ViewHolder holder = view.getTag();
int pos = holder.position;
SparseArray<AsyncTask> tasks = holder.taskContainer;
AsyncTask task = tasks.get(pos);
if (task == null) {
// Create your task
task = new AsyncTask<?, ?, ?> (...);
tasks.put(pos, task);
task.execute();
}
}
return convertView;
}
/// You can write the rest of the adapter I believe.
...
}
and then you don't really need onConfigurationChanged. Just read and save your data accordingly.
public class MyActivity extends Activity {
ListView mListView;
MyProgressBarAdapter mAdapter;
#Override
public void onCreate(Bundle savedState) {
super.onCreate();
Object[] liveState = getLastNonConfigurationInstance();
setContentView(R.layout.mylistview_with_progressbars);
mListView = findViewById(R.id.listview);
// Be consistent with the index
MyProgressBarAdapter adapter = MyProgressBarAdapter.restore(savedState, liveState[0]);
mListView.setAdapter(adapter);
mAdapter = adapter;
...
}
#Override
public void onSaveInstanceState(Bundle bundle) {
mAdapter.save(bundle);
}
#Override
public Object[] onRetainNonConfigurationInstance () {
// size to be whatever live state you need to store other than the adapter
Object[] objects = new Object[1];
// This reference will be retained between onCreate() and onDestroy() calls.
objects[0] = mAdapter.exportLiveState();
// Any other things that can't be serialized
return objects;
}
#Override
public Object[] getLastNonConfigurationInstance() {
Object[] live = (Object[]) super.getLastNonConfigurationInstance();
if (live == null) {
live = new Object[1];
}
return live;
}
// The rest of your activity
...
}
That will make it so that when you flip the orientation, the adapter will be recreated but it will be reinitialized to the same state it was in before. I made some assumptions about the way you store your progress and the nature of your asyncTasks but I hope you can adjust as needed.
You could even, if you don't store a reference to any context, you might be able to get away with just storing the entire adapter itself inside the onRetainNonConfigurationInstance() and using that in the getLastRetainedNonConfigurationInstance()
You can set android:configChanges="orientation" in manifest file to make your activity does not restart when rotating
One solution that i used
If we have only one layout for both landscape and portrait mode, then we can we can solve this by
1. Set the activity asandroid:configChanges="orientation" in manifest file
2. Override the onConfigurationChanged like this
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
But the problem is still the if i need to use different layout for both landscape and portrait mode, each have a listview with progress bar in each row. there i need to retain the progress while rotate which use same AsyncTask class.
How can you set percentage value for each row item? Why don't you update that value to the data item. You can have some thing like below. Since you have the data item you can store whatever you want :) Ps: I wonder that I can format text in comment to not add new answer.
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView,
ViewGroup parent)
public Object getChild(int groupPosition, int childPosition)