I have built a gallery which has options to select multiple items and delete. I am loading images to GridView using custom BaseAdapter and while deleting I am using AsyncTask. But if try to delete multiple items getting array out of bound exception.
java.lang.IndexOutOfBoundsException: Invalid index x, size is x
at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
at java.util.ArrayList.get(ArrayList.java:308)
at com.android.Example.Adapters.ImageAdapter.getView(ImageAdapter.java:94)
I am getting this error only when if I use AsynTask, Deleting works fine if I do it in main thread.
I have no clue at which point my ArrayList is going out of bound.
This my Custom BaseAdapter
public class ImageAdapter extends BaseAdapter {
private com.nostra13.universalimageloader.core.ImageLoader imageLoader;
private Context mContext;
private int displayWidth;
private int imageWidth;
private ArrayList<String> f = new ArrayList<String>();// list of file paths
public ImageAdapter(Context c, ArrayList<String> f) {
mContext = c;
this.f=f;
imageLoader = ImageLoader.getInstance();
imageLoader.init(ImageLoaderConfiguration.createDefault(mContext));
DisplayMetrics metrics = new DisplayMetrics();
((Activity) c).getWindowManager().getDefaultDisplay().getMetrics(metrics);
displayWidth = metrics.widthPixels;
imageWidth=(displayWidth/3);
}
#Override
public int getCount() {
return this.f.size();
}
#Override
public Object getItem(int position) {
return this.f.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(imageWidth,imageWidth));
imageView.setPadding(Utils.dpToPx(2), Utils.dpToPx(2), Utils.dpToPx(2), Utils.dpToPx(2));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setCropToPadding(true);
imageView.setBackground(mContext.getResources().getDrawable(R.drawable.gridview_selector));
} else {
imageView = (ImageView) convertView;
}
//imageView.setAdjustViewBounds(true);
imageLoader.displayImage("file:///"+this.f.get(position),imageView,); //This is line number 94
return imageView;
}
}
And this how I am deleting.
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
ArrayList<Uri> imageUris = new ArrayList<Uri>();
SparseBooleanArray checked = gridView.getCheckedItemPositions();
int checkedItemCount = gridView.getCheckedItemCount();
switch (item.getItemId()) {
case R.id.delete:
new DeleteAsync(checked, checkedItemCount).execute();
return true;
And this my AsyncTask
private class DeleteAsync extends AsyncTask<Void, Void, Void> {
SparseBooleanArray _checked;
int _checkedItemCount;
private DeleteAsync(SparseBooleanArray _checked, int _checkedItemCount) {
this._checked = _checked;
this._checkedItemCount = _checkedItemCount;
}
#Override
protected Void doInBackground(Void... params) {
for (int i = (_checkedItemCount - 1); i >= 0; i--) {
if (_checked.valueAt(i)) {
File file = new File(files.get(_checked.keyAt(i)));
if (file.delete()) {
MediaScannerConnection.scanFile(getActivity(), new String[]{file.toString()}, null, null);
files.remove(_checked.keyAt(i));
} else
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(rootView.getContext(), getResources().getString(R.string.delete_error), Toast.LENGTH_SHORT).show();
}
});
}
}
}
#Override
protected void onPostExecute(Void void) {
super.onPostExecute(void);
imageAdapter.notifyDataSetChanged();
progress.setVisibility(View.INVISIBLE);
}
}
Setting adapter to GridView
ArrayList<String> files=getListOfImagFiles();
imageAdapter= new ImageAdapter(rootView.getContext(), files);
gridView.setAdapter(imageAdapter);
Check the size of array before deleting.
Something like this may work.
if(items.size())>0
for (int i = _checkedItemCount; i > 0; i--)
{
//do your stuff
}
This is a common problem in getView of gridview adapters. For safe side I have followed like this.
put a condition check in getView()
if(position<this.f.size())
{
imageLoader.displayImage("file:///"+this.f.get(position),imageView,); //This is line number 94
}
else
{
//Display some default img or error img.
}
Suggestion:
Do not delete items of array based on the position of item. Delete based on the item model object.
Follow MVC guidelines.
Currently it is giving issue as
When you start you have total 10 items in files list. So index will be 0-9 and your for loop runs for this index range.
Now when you delete entry from files arraylist your count decreases by one i.e. it becomes Now you index range will be 0-8.
So as you go on deleting items from files arraylist your this line of code
imageLoader.displayImage("file:///"+this.f.get(position),imageView,);
inside baseadapter will loose that position.
So you are left with two options:
Do not delete from files arraylist just keep track of filenames and in onpostexecute delete those items from files arraylist and use notifydatasetchanged on your baseadapter.
Delete item from files arraylist but on each deletion use onprogressupdate to call notifydatasetchanged on your baseadapter.
Weirdly I solved my problem just putting safe guard before loading image.
if(position<getCount())
imageLoader.displayImage("file:///"+this.f.get(position),imageView,options);
I am not accepting my answer as I think this is not the perfect solution for this problem and also I have no idea why this is happening only when I am using AsyncTask
Do setAdapter again instead of NotifyDataSetChanged, it will reload the list and position will be correct.
Related
I am new to android..and my question is:
I am making one android Application in which I have one RadioGroup with two Radiobutton
btnA and btnB along with some other Parameters.
if btnA is Checked than value in database is 1 and if btnB is selected then Value in Database is 0.
I am retrieving Data from database while showing My Listview.
Now My Question is I want to display Listview with listItem like :
imgA if Value From Database is 1 .
imgB if Value from Database is 0.
How to do it???
I tried this
private Integer[] Images = {R.drawable.imgA,R.drawable.imgB};
Cursor cur = dop.getData();
if(cur!= null && cur.getCount()>0)
{
if(cur.moveToFirst()){
do {Integer btnType= cur.getInt(cur.getColumnIndex(databaseName.TableName.ColumnName));
if(btnType== 1){ImageId = Images[0];}
else if(btnType== 0){ImageId= Images[1];}}
//other Params
}while (cur.moveToNext());
}
Adapter myAdp = new Adapter(Activity.this,ImageId,para);
myList.setAdapter(myAdp);
My Adapter is Like
public class Adapter extends BaseAdapter {
public Context context;
public ArrayList<String>Param1;
public int ImageId;
public Adapter(Context context,int ImageId,ArrayList<String>Param1)
{
this.context = context;
this.ImageId = ImageId;
this.Param1= Param1;
}
public int getCount(){return param1.size();}
public Object getItem(int Position){return null;}
public long getItemId(int Position){return 0;}
public class viewHolder{
TextView tvParam1;
ImageView imgType;
}
#Override
public View getView(int Position,View Child,ViewGroup Parent)
{
viewHolder vHolder;
LayoutInflater inflator;
if(Child == null)
{
inflator = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Child = inflator.inflate(R.layout.list_row,null);
vHolder = new viewHolder();
vHolder.tvparam1 = (TextView)Child.findViewById(R.id.txtParam1);
vHolder.imgType = (ImageView)Child.findViewById(R.id.imgType);
Child.setTag(vHolder);
}
else {vHolder = (viewHolder)Child.getTag();}
vHolder.tvParam1.setText(Param1.get(Position));
vHolder.imgType.setImageResource(ImageId);
return Child;
}
}
my Problem is I am getting same image for all list items.
but I want ImgA for btnA and imgB for btnB.
How to resolve this???
I got solution for this issue
what i done is: I took Integer Arraylist for storing my Images
In my Main Activity:
public int[] Images = {R.drawable.imgA,R.drawable.imgB};
public ArrayList<Integer>ImageId = new ArrayList<Integer>();
int i = 0;
if(cur.moveToFirst()){
if(btnType == 1)
{
ImageId.add(Images[0]);
}
else if(btnType == 0)
{
ImageId.add(Images[1]);
}
} while(cur.moveToNext());
also in myAdapter: I jst changed Integer Array to Integer Arraylist for Image
this solve my Problem
Take an array of ImageId and save the id in that array in specific positions.
int i = 0;
if(cur.moveToFirst()){
do {Integer btnType= cur.getInt(cur.getColumnIndex(databaseName.TableName.ColumnName));
if(btnType== 1){ImageId[i] = Images[0];}
else if(btnType== 0){ImageId[i] = Images[1];}}
i++;
} while(cur.moveToNext());
Now inside your adapter load the images like this
vHolder.imgType.setImageResource(ImageId[Position]);
You've logical error in your code.
I've started working on a small project not to long ago, the main goal is to forge a way for me to keep track of my actions during the course of 100 weeks.
I'm still a rookie android developer and I've encountered an issue that I couldn't explain.
Basically I've populated a ListView using the ArrayAdapter with a list containing 100 strings (Week1, Week2, Week3 ... Week100)
Setting up an onclicklistener on each of the TextViews so that when a user performs a click on a textview, the background color would change to red.
However; whenever I click a single textview - more than a single textview is being colored.
Notes:
I'm using a ScrollView to scroll through the entire list. (Once populated, the 100 week list fills up the entire screen, the scroll view is used to access the entire list.)
I also saved a reference to the currently painted textview so I could make sure that when a user clicks a different textview, the previous one would lose its red background.
MainActivity initialization:
public class MainActivity extends ActionBarActivity
{
TextView selectedWeek; // Reference to the selected week.
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
populateWeekList(); // Populating the ListView
initWeekClick(); // Initializing click listener
}
Populating the ListView:
public void populateWeekList()
{
String[] weeks = new String [100]; // 100 weeks
for (int i=0; i<100;i++)
{
weeks[i] = "Week"+(i+1);
}
ArrayAdapter<String> weekAdapter = new ArrayAdapter<String>(
this,
R.layout.weeksview,
weeks
);
// R.id.weekTypeList is just a normal TextView.
ListView weekList=(ListView) findViewById(R.id.weekTypeList);
weekList.setAdapter(weekAdapter);
}
Code for initializing onClickListener and saving the selectedWeek reference:
public void initWeekClick()
{
ListView weekList=(ListView) findViewById(R.id.weekTypeList);
weekList.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent, View viewClicked, int position, long id)
{
if (selectedWeek != null)
{
selectedWeek.setBackgroundColor(0);
}
TextView clicked = (TextView) viewClicked;
// Change clicked TextView color to red.
clicked.setBackgroundColor(getResources().getColor(android.R.color.holo_red_light));
// Save the selected week reference
selectedWeek = clicked;
}
});
}
Ok, your background is shuffling because when you scroll your ListView getView() is called and it consider your current position of TextView(as current view) and set background on it as it detect setBackground() method at onClick listener on it..
First I recommend to create a Adapter class extends ArrayAdapter<?>
Solution 1 :
Use setTag() at onClick listener on your text view like..
text.setTag(position);
and above it use getTag() and put condition
if(holder.text.getTag().equals(position)){
holder.text.setBackgroundColor(Color.BLUE);
}else{
holder.text.setBackgroundColor(Color.WHITE);
}
Solution 2 :
Added this to onCreate method
ArrayList<String> _array = new ArrayList<String>();
for(int i=0 ; i <1000; i ++){ // 1000 value
_array.add(i+"");
}
list.setAdapter(new MainAdapter(this, _array)); // pass you list here
ArrayAdapter class :
public class MainAdapter extends ArrayAdapter<String> {
ArrayList<String> _st = new ArrayList<String>();
ArrayList<Integer> check = new ArrayList<Integer>();
Context _context;
public MainAdapter(Context context,ArrayList<String> _st) {
super(context,R.layout.main, _st); // your inflate layout
this._context = context;
this._st = _st;
}
#Override
public int getCount() {
return _st.size();
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
//---//
// check if current position is there in arraylist
if(checking(position)){
holder.text.setBackgroundColor(Color.BLUE);
}else{
holder.text.setBackgroundColor(Color.WHITE);
}
holder.text.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
// set background and put value in array list
holder.text.setBackgroundColor(Color.BLUE);
check.add(position);
}
});
return convertView;
}
// this will check whether current position is there is array list or not and if it there it will break loop and return true
public boolean checking(int position){
boolean fine = false;
for(int i=0; i<check.size();i++){
if(position == check.get(i)){
fine = true;
break;
}
}
return fine;
}
}
public class ViewHolder{
TextView text;
}
}
I don't how much I am ethical in this code...but as you have specified that you have 100 value.I have tested it on 1000 value and it worked
I am not expert so let me know if I am wrong somewhere
Hope it works !!!
I have a listview in the layout, and each item has two part: one is a user avatar (ImageView), and other is a chat content ( TextView). Looks like:
And I have a custom adapter.
I would like to implement : when I click the avatar, I can go to the system gallery and select a photo as the avatar.
So my code about onClickListener in adapter class is:
Intent itent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
((Activity)(context)).startActivityForResult(itent, LOAD_IMAGE_RESULT);
And I also override the onActivityResult method in activity.
However, the solution I mentioned above cannot change the avatar in onActivityResult method, because I don't know how to communicate between adapter and activity.
Hope for a solution.
EDIT - Initially, I have just shown one of the method to update child views only by leaving it's adapter in the inconsistent state (because the original question don't give much information about underlying data structure).
Adapter: An Adapter object acts as a bridge between an AdapterView and the underlying data for that view. The Adapter provides access to
the data items. The Adapter is also responsible for making a View for
each item in the data set.
Note: As the definition itself describes that it's important to maintain the state means (data + view). So you should always have the consistent user experience.
To give the example properly, I have also defined the data model class ItemData based on the assumption of given image in original question.
/**
* Model Class
*/
public class ItemData {
private Uri imageUri;
private String msg;
private Date timeStamp;
public void setImageUri(Uri uri) {
this.imageUri = uri;
}
public Uri getImageUri() {
return this.imageUri;
}
...
}
CustomAdapter
Create the custom adapter for listView which will maintain the child views with it's dataset accordingly. You've to maintain the reference of last selected row index lastSelectedIndexRow which can be used later for updating the view.
Note: To get the view for any index in the listView, we should not call getView() method of the adapter. As calling getView() with null for the convertView causes the adapter to inflate a new view from the adapter's layout resource (does not get the view that is already being displayed).
The AdapterView should always be updated with notifyDataSetChanged() based on the current dataset hold by adapter.
public class CustomAdapter extends BaseAdapter {
private Context context;
private LayoutInflater inflater;
private List<ItemData> dataList;
private int lastSelectedRowIndex = -1;
public static int LOAD_IMAGE_RESULT = 201;
public CustomAdapter(Context context, ArrayList<ItemData> dataList) {
// hold the items
this.context = context;
this.dataList = dataList;
this.inflater = LayoutInflater.from(this.context);
}
#Override
public int getCount() {
return (dataList != null && !dataList.isEmpty()) ? dataList.size() : 0;
}
#Override
public ItemData getItem(int position) {
return (dataList != null && !dataList.isEmpty()) ? dataList.get(position) : null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolderItem viewHolder;
if (converView == null) {
// inflate the layout
convertView = inflater.inflate(R.layout.row_item, parent, false);
// well set up the ViewHolder
viewHolder = new ViewHolderItem();
viewHolder.avatar = (ImageButton)view.findById(R.id.avatar);
// store the holder with the view.
convertView.setTag(viewHolder);
}
else {
// we've just avoided calling findViewById() on resource every time
// just use the viewHolder
viewHolder = (ViewHolderItem) convertView.getTag();
}
// Set row data
ItemData data = (ItemData)getItem(position);
if (data != null) {
// set message
viewHolder.message.setText(data.getMessage());
// set formatted timestamp
String formattedTimeStamp = ...; // convert data.getTimeStamp() into formatted version
viewHolder.timeStamp.setTextView(formattedTimeStamp);
// set Image and also it's action.
viewHolder.avatar.setImageUri(data.getImageUri());
viewHolder.avatar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
try {
Intent intent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
((Activity)context).startActivityForResult(intent, LOAD_IMAGE_RESULT);
} catch (Exception e) {
Log.e("Demo application", "Failed to invoke call", e);
}
}
}
}
}
/**
* Get the last selected item index
*
*/
public int getLastSelectedItemIndex() {
return lastSelectedRowIndex;
}
/**
* Update the adapater with new data
*
*/
public void updateItems(List<ItemData> dataList) {
if (this.dataList != dataList)
this.dataList = dataList;
// update the view.
notifyDataSetChanged();
// reset the last selection
lastSelectedRowIndex = -1;
}
/**
* Hold View items
*/
static class ViewHolderItem {
private ImageView avatar;
private TextView message;
private TextView timeStamp;
}
}
ExampleActivity
In activity, you've to defined the reference of custom adapter and handle the image selection results in onActivityResult() method. Once you get the selected image from gallery then update the underlying data holding inside dataList and also update the adapter by calling updateItems() custom method.
The updateItems() method of adapter will take the new data list as argument and invalidate the adapterView by calling notifyDataSetChanged().
public class ExampleActivity extend FragmentActivity {
private ListView listView;
private CustomAdapter adapter;
private List<ItemData> dataList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dataList = ...; // Load the list from database
// Create the custom adapter with filled list items.
adapter = new CustomAdapter(this, dataList);
// List View and set the data adapter
listView = (ListView)findViewById(R.id.listView);
listView.setAdapter(adapter);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Check if it's coming from MediaStore Selection.
if (resultCode == Activity.RESULT_OK &&
requestCode == CustomAdapter.LOAD_IMAGE_RESULT) {
// Get the selected rowIndex
if (adapter != null && dataList != null) {
// check the row index is valid
int rowIndex = adapter.getLastSelectedItemIndex();
if (rowIndex > -1 && rowIndex < dataList.size()) {
// Get the item
ItemData item = dataList.get(rowIndex);
// Update the item with imageUri
item.setImageUri(Uri.parse(data.getData()));
/**
* If you may want to update the information in database,
* then it's the best place, but please do in background thread.
*/
//Now notify the adapter with new changes
adapter.updateItems(dataList);
}
}
}
}
...
}
You should save a reference to the clicked view in the onClickListener method and have a public method in your adapter. When you get the response in the onActivityResult method call the adapter's method and use the
Add a public method in your custom adapter to set the image (like public void setAvatar(Bitmap image))
Then, in your onActivityResult method, get the image selected and use your newly created method to set the new image in your adapter. (see here for more informations: Getting a Result from an Activity
Don't forget to put notifyDataSetChanged(); at the end of your your newly method in your adapter to refresh all the list.
public void setAvatar(Bitmap image)
{
...
imageView.setImageBitmap(image);
...
notifyDataSetChanged();
}
In addition, if you need to know which image was clicked, you can specify an Integer as a parameter and check it in the onActivityResult.
I see you use a constant LOAD_IMAGE_RESULT for this parameter. Instead of a constant, put an integer who identify the clicked item.
In the adapter, (not sure which kind of adapter you using), you set the onClickListener for the ImageView. Hence,
mImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
<INSERT CODE HERE>
mContext.startActivity(intent);
}
});
Where mImageView is the ImageView for that item. Also a good idea might be to use an ImageButton since it allows Images and is a button which is what you want. Then by the add the code you want to select the Image. You may need to pass context through to the Adapter too (but you would generally do that anyway). Then you can process the onActivityResult method in the activity that the adapter is in i.e. setting the Image to the image you picked. There are a couple ways to do this.
i have parsed Json data from the server. On which im showing all the data in listview and i have Load more option below the ListView. Now when i click load more option, this application reload whole list and did not show previous list data. Please help me find out the solution. Here is footer view click listener :
lFooter.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
page += 1;
new ParseIssues().execute();
listView.removeFooterView(v);
}
});
in above code ParseIssues class parse json values and displays all the data in ListView Here is code for onPostExecute of AsynkTask class :
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if(page < totalPage){
listView.addFooterView(v);
}
listAdapter = new ListAdapterForSearch(activity, mainList);
listView.setAdapter(listAdapter);
// get listview current position - used to maintain scroll position
int currentPosition = listView.getFirstVisiblePosition();
listView.setSelection(currentPosition);
}
Here is BaseAdapter class:
public class ListAdapterForSearch extends BaseAdapter {
private Activity activity;
private ArrayList<HashMap<String, String>> data;
private static LayoutInflater inflater = null;
public ListAdapterForSearch(Activity a, ArrayList<HashMap<String, String>> d) {
activity = a;
data = d;
inflater = LayoutInflater.from(activity);
}
#Override
public int getCount() {
return data.size();
}
#Override
public Object getItem(int position) {
return data.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
View vi = convertView;
if (convertView == null) {
vi = inflater.inflate(R.layout.list_row_item, null);
}
TextView title = (TextView)vi.findViewById(R.id.title);
HashMap<String, String> hash = new HashMap<String, String>();
hash = data.get(position);
title.setText(hash.get("title"));
return vi;
}
}
The answer to your question lies with the variable "mainList", which the adapter uses. You need to add the new data to it, basically this list needs to contain all the data you want to show in your ListView.
You should set adapter on create, after that first add loaded data to mainlist(add new loaded data) & then after loading notify adapter like listadapter.notifydatachange()
Edit: Because every time you are creating adapter that's why you are facing this problem, instead after loading just notify your adapter..
Check in your code you may have clear your mainList.
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)