When items are redrawn after invoking `invalidateViews()` - java

When are the items redrawn after invoking invalidateViews() ?
I ask because i try to refresh listItems after a bg-thread notify an image rsc was downloaded.
But nothing is updated. Only after exiting and re-entering the new icons are drawn.
I have an activity with adapter of type SettingValueAdapter extends BaseAdapter
it has a member:
private SettingsValue[] values;
it has two interesting methods:
#Override
public View getView(int position, View view, ViewGroup parent) {
AddressItem ai= (AddressItem)getItem(position);
DriveToNativeManager dnm = DriveToNativeManager.getInstance();
if (view == null) {
LayoutInflater li = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = li.inflate(R.layout.address_item, null);
}
view.setTag(R.id.addressItem,ai);
view.setTag(position);
view.findViewById(R.id.fullAddressItemCol).setVisibility(View.VISIBLE);
view.findViewById(R.id.addressItemTouch).setVisibility(View.GONE);
view.findViewById(R.id.addressItemImage).setVisibility(View.GONE);
if (ai != null) {
...
}
view.findViewById(R.id.addressItemIconLayout).setVisibility(View.VISIBLE);
Drawable icon = ResManager.GetSkinDrawable(ai.getIcon() + ".bin");
((ImageView)view.findViewById(R.id.addressItemIcon)).setImageDrawable(icon);
..
}
}
public void refreshListIcons() {
// NativeManager nativeManager = AppService.getNativeManager();
// SettingsValue[] values = new SettingsValue[categories.length];
// for (int i = 0; i < categories.length; i++) {
// values[i] = new SettingsValue(categories[i].value, nativeManager.getLanguageString(categories[i].displayString), false);
// values[i].icon = ResManager.GetSkinDrawable(categories[i].iconName + ".bin");
// }
// adapter.setValues(values);
this.runOnUiThread(new Runnable() {
#Override
public void run() {
adapter.notifyDataSetChanged();
}
});
}
I attach a callback to the bg-thread (c language) image downloading process.
The callback switches to the ui-thread and calls this refreshList:
public void refreshSearchIconsOnSearchActivity() {
Runnable refreshViewEvent = new Runnable() {
#Override
public void run() {
Activity currentActivity = AppService.getActiveActivity();
if (currentActivity instanceof SearchActivity) {
Log.d("w", "refreshSearchIconsOnSearchActivity callback running in thread "
+ Thread.currentThread().getId() );
//results list
((SearchActivity) currentActivity).refreshList();
}
}
};
AppService.Post(refreshViewEvent);
}
However, the images are done downloading and are not refreshed on the activity.
They are refreshed only when I leave an re-enter the activity.
What am I missing?

InvalidateViews just causes the listView to redraw itself. It will not call getView to do so, it just resets the current ones on screen- basically it just does
for(View child: getChildren()){
child.invalidate();
}
If you want to update the list, call notifyDataSetChanged on the adaptor.

Related

Android ListViewItem update Progressbar in row

I'm using ListView with custom list rows,where every ListItem has ProgressBar in it.
When the user click the ImageView,the app starts an AsyncTask to download a file from a remote server,and update the progress in progress bar.
I'm using Parallel async tasks,which mean app can launch multiple downloads and update them in the ProgressBar of each row.
This is the code
static class ViewHolder {
protected TextView title;
protected TextView size;
protected TextView version;
protected ImageView appIcon;
protected ProgressBar progressBar;
}
public class UpdateAdapter extends ArrayAdapter<UpdateItem> {
public UpdateAdapter(Context context, ArrayList<UpdateItem> users) {
super(context, 0, users);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
UpdateItem updateItem = getItem(position);
View v = convertView;
ViewHolder viewHolder;
LayoutInflater mInflater = LayoutInflater.from(getContext());
if (convertView == null) { // if convertView is null
convertView = mInflater.inflate(R.layout.row, null);
viewHolder = new ViewHolder();
viewHolder.title = (TextView) convertView.findViewById(R.id.apptitlelabel);
viewHolder.version = (TextView) convertView.findViewById(R.id.versionlabel);
viewHolder.size = (TextView) convertView.findViewById(R.id.sizelabel);
viewHolder.appIcon = (ImageView) convertView.findViewById(R.id.appicon);
viewHolder.progressBar = (ProgressBar) convertView.findViewById(R.id.downloadProgressBar);
convertView.setTag(viewHolder);
} else
viewHolder = (ViewHolder) v.getTag();
viewHolder.progressBar.setProgress(0);
View finalConvertView = convertView;
viewHolder.appIcon.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DownloadFileFromURL task = new DownloadFileFromURL();
task.position = position;
task.v = finalConvertView;
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, updateItem.downloadlink);
}
});
return convertView;
}
class DownloadFileFromURL extends AsyncTask<String, String, String> {
/**
* Before starting background thread Show Progress Bar Dialog
**/
int position;
View v;
#Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* Downloading file in background thread
**/
#Override
protected String doInBackground(String... f_url) {
int count;
try {
URL url = new URL(f_url[0]);
URLConnection conection = url.openConnection();
conection.connect();
// this will be useful so that you can show a tipical 0-100%
// progress bar
int lenghtOfFile = conection.getContentLength();
// download the file
InputStream input = new BufferedInputStream(url.openStream(),
8192);
// Output stream
String fileExtenstion = MimeTypeMap.getFileExtensionFromUrl(url.getPath());
String fname = URLUtil.guessFileName(url.getPath(), null, fileExtenstion);
OutputStream output = new FileOutputStream(Environment
.getExternalStorageDirectory().toString() + "/" + fname);
byte data[] = new byte[1024];
long total = 0;
while ((count = input.read(data)) != -1) {
total += count;
// publishing the progress....
// After this onProgressUpdate will be called
publishProgress("" + (int) ((total * 100) / lenghtOfFile));
// writing data to file
output.write(data, 0, count);
}
// flushing output
output.flush();
// closing streams
output.close();
input.close();
} catch (Exception e) {
Log.e("Error: ", e.getMessage());
}
return null;
}
/**
* Updating progress bar
**/
protected void onProgressUpdate(String... progress) {
// setting progress percentage
// Log.w(TAG, progress[0]);
updateStatus(position, Integer.parseInt(progress[0]));
}
/**
* After completing background task Dismiss the progress dialog
**/
#Override
protected void onPostExecute(String file_url) {
// dismiss the dialog after the file was downloaded
Log.w(TAG, "onPostExecute: ");
removeListItem(v, position);
}
}
public void updateStatus(int index, int Status) {
int in = index - updateLv.getFirstVisiblePosition();
View v = updateLv.getChildAt(in);
ProgressBar progress = (ProgressBar) v.findViewById(R.id.downloadProgressBar);
progress.setProgress(Status);
}
The problem is ,when the user starts two downloads(say hit the first the second imageviews),and the first task has been completed,and the first row getting removed from the list,in onPostExecute,now,the second row turns into the first row,but the task updates the current second row(which was the third before the first item removed...)
I know it happens because I pass into updateStatus,the position of the item to be updated,but in the meantime the ListView Changes and removes items(because their download has been completed),but I have no current solution for this...
I even tried passing a ProgressBar object reference to updateStatus method ,instead of using item position,and I thought it would solve the problem...but no luck :)

How to get a view in runtime when using socket.io in android?

I have an android application in frontend side and node js socket in backend.
There is a list of Chats in activity that I get them from socket.on event.
Every item in this list has a customview in it , I need to update this customview with different values when a socket event is received.
How can I do that?
Here is My Code When getting the list of chats:
final Handler mHandler04 = new Handler(Looper.getMainLooper());
mHandler04.post(new Runnable() {
#Override
public void run() {
SocketManager.getInstance().getSocket().on("allchatres", new Emitter.Listener() {
#Override
public void call(final Object... args) {
g.context.runOnUiThread(new Runnable() {
#Override
public void run() {
JSONArray jsonArray = (JSONArray) args[0];
Log.d(TAG, "run: " + jsonArray);
try {
for (int i = 0; i < jsonArray.length(); i++) {
createView(jsonArray.getJSONObject(i).getString("title"), jsonArray.getJSONObject(i).getString("body"));
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
});
}
});
And Here is My Createview Code:
private void createView(final String title, final String body) {
customViewChat = new customViewChat(g.context);
layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
customViewChat.txtCsTitle.setText(title);
customViewChat.txtCsBody.setText(body);
LinearLayoutItemHolder.addView(customViewChat, layoutParams);
customViewChat.btnJoin.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent i = new Intent(g.context, DetailActivity.class);
i.putExtra("title", title);
i.putExtra("body", body);
startActivity(i);
}
});
}
But When i want to update customview in the list like this :
int count = LinearLayoutItemHolder.getChildCount() ;
Log.d(TAG,"child count : " + count) ;
for(int i = 0 ;i<count ; i++)
{
View v = LinearLayoutItemHolder.getChildAt(i) ;
}
I see the below result in logcat:
child count : 0
How can i get every customview in LinearLayoutItemHolder ?
I Want to change customView Values in another socket.on event but I can't.
I searched a lot but haven't found anything useful.
Any suggestion will be helpful.
Finally, I found the best method to acheive my goal: using RecyclerView!
I had some dificulties using custom views so I used recycler view instead.

List<ParseUser> .contains ParseUser always FALSE

I have an if statement written below:
//Set Friend Action OnClickListener & Image
if (ParseUser.getCurrentUser().getList("friendsArray").contains(searchResultsList.get(position))) {
Glide.with(holder.imgFriendAction.getContext()).load(R.drawable.ic_phone_black_24dp).into(holder.imgFriendAction);
ImageViewCompat.setImageTintList(holder.imgFriendAction, ColorStateList.valueOf(searchContext.getColor(R.color.green)));
}
else if (ParseUser.getCurrentUser().getList("pendingFriendsArray").contains(searchResultsList.get(position))) {
Glide.with(holder.imgFriendAction.getContext()).load(R.drawable.ic_check_black_24dp).into(holder.imgFriendAction);
ImageViewCompat.setImageTintList(holder.imgFriendAction, ColorStateList.valueOf(searchContext.getColor(R.color.gray_dark)));
}
else {
Glide.with(holder.imgFriendAction.getContext()).load(R.drawable.ic_person_add_black_24dp).into(holder.imgFriendAction);
ImageViewCompat.setImageTintList(holder.imgFriendAction, ColorStateList.valueOf(searchContext.getColor(R.color.colorPrimary)));
}
The problem is that every single time I run that statement it always returns FALSE for both if statements even though I know for a fact that 'friendsArray' & 'pendingFriendsArray' return TRUE in many circumstances.
Both arrays contain pointers to the _User table.
searchResultsList is declared as follows:
private List<ParseUser> searchResultsList;
I've logged all three items (friendsArray, pendingFriendsArray & searchResultsList.get(position)) to the console and they show the following:
D/friendsArray: [com.parse.ParseUser#ae66779, com.parse.ParseUser#8371cbe, com.parse.ParseUser#32d511f, com.parse.ParseUser#5fd2c6c, com.parse.ParseUser#7dd0235, com.parse.ParseUser#9c446ca, com.parse.ParseUser#5fe03b]
D/pendingFriendsArray: [com.parse.ParseUser#7c6a358, com.parse.ParseUser#3688cb1, com.parse.ParseUser#480596]
D/searchResultsList.get(position) =: com.parse.ParseUser#5fe03b
The entire class is below:
public class SearchUserAdapter extends RecyclerView.Adapter<SearchUserAdapter.ViewHolder> {
private Context searchContext;
private List<ParseUser> searchResultsList;
OnItemClickListener onItemClickListener;
public SearchUserAdapter(Context context, List<ParseUser> dataSet) {
searchContext = context;
searchResultsList = dataSet;
}
public interface OnItemClickListener {
public void onItemClick(View view, ParseUser searchUserObject, int position);
}
public void setOnItemClickListener(final OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(searchContext).inflate(R.layout.ly_search_user, parent,false);
return new ViewHolder(v);
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
//Set User Name
holder.txtUserName.setText(searchResultsList.get(position).getString("fullName"));
//Set User Location
holder.txtUserLocation.setText(GlobalFunctions.getParseUserLocationAsString(holder.txtUserName.getContext(), searchResultsList.get(position)));
//Set User Profile Image
if (searchResultsList.get(position).getParseFile("profilePicture") != null) {
Glide.with(holder.imgUserProfilePicture.getContext()).applyDefaultRequestOptions(RequestOptions.circleCropTransform()).load(searchResultsList.get(position).getParseFile("profilePicture").getUrl()).into(holder.imgUserProfilePicture);
}
else {
Glide.with(holder.imgUserProfilePicture.getContext()).applyDefaultRequestOptions(RequestOptions.circleCropTransform()).load(R.drawable.ic_profile_place_holder).into(holder.imgUserProfilePicture);
}
//Set Row OnClickListener
holder.rlUserItem.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (searchResultsList.get(position).getObjectId().equalsIgnoreCase(ParseUser.getCurrentUser().getObjectId())) {
Intent openProfile;
openProfile = new Intent(holder.rlUserItem.getContext(), TimelineActivity.class);
holder.rlUserItem.getContext().startActivity(openProfile);
}
else {
Intent openOtherProfile = new Intent(holder.rlUserItem.getContext(), OtherUserTimelineActivity.class);
openOtherProfile.putExtra("otherUserProfileId", searchResultsList.get(position).getObjectId());
holder.rlUserItem.getContext().startActivity(openOtherProfile);
}
}
});
//Set Friend Action OnClickListener & Image
if (ParseUser.getCurrentUser().getList("friendsArray").contains(searchResultsList.get(position))) {
Glide.with(holder.imgFriendAction.getContext()).load(R.drawable.ic_phone_black_24dp).into(holder.imgFriendAction);
ImageViewCompat.setImageTintList(holder.imgFriendAction, ColorStateList.valueOf(searchContext.getColor(R.color.green)));
}
else if (ParseUser.getCurrentUser().getList("pendingFriendsArray").contains(searchResultsList.get(position))) {
Glide.with(holder.imgFriendAction.getContext()).load(R.drawable.ic_check_black_24dp).into(holder.imgFriendAction);
ImageViewCompat.setImageTintList(holder.imgFriendAction, ColorStateList.valueOf(searchContext.getColor(R.color.gray_dark)));
}
else {
Glide.with(holder.imgFriendAction.getContext()).load(R.drawable.ic_person_add_black_24dp).into(holder.imgFriendAction);
ImageViewCompat.setImageTintList(holder.imgFriendAction, ColorStateList.valueOf(searchContext.getColor(R.color.colorPrimary)));
}
holder.imgFriendAction.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
friendActionListenerAction(holder, searchResultsList.get(position));
}
});
}
private void friendActionListenerAction(ViewHolder holder, ParseUser parseUser) {
if (ParseUser.getCurrentUser().getList("friendsArray").contains(parseUser)) {
FLKCallUtils.showCallDialog(holder.imgFriendAction.getContext());
}
else if (ParseUser.getCurrentUser().getList("pendingFriendsArray").contains(parseUser)) {
//Do nothing
}
else {
//Add Friend
FLKFriendUtils.sendFriendRequestFromUserToUser(ParseUser.getCurrentUser(), parseUser);
//Update Image
Glide.with(holder.imgFriendAction.getContext()).load(R.drawable.ic_check_black_24dp).into(holder.imgFriendAction);
ImageViewCompat.setImageTintList(holder.imgFriendAction, ColorStateList.valueOf(searchContext.getColor(R.color.gray_dark)));
}
}
#Override
public int getItemCount() {
return searchResultsList.size();
}
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public MediumRobotoTextView txtUserName;
public RegularRobotoTextView txtUserLocation;
public RelativeLayout rlUserItem;
ImageView imgUserProfilePicture;
ImageView imgFriendAction;
public ViewHolder(View itemView) {
super(itemView);
rlUserItem = (RelativeLayout) itemView.findViewById(R.id.rl_user_container);
rlUserItem.setOnClickListener(this);
txtUserName = (MediumRobotoTextView) itemView.findViewById(R.id.txt_user_name);
txtUserLocation = (RegularRobotoTextView) itemView.findViewById(R.id.txt_user_location);
imgUserProfilePicture = (ImageView) itemView.findViewById(R.id.img_profile_picture);
imgUserProfilePicture.setOnClickListener(this);
imgFriendAction = (ImageView) itemView.findViewById(R.id.img_friend_action);
imgFriendAction.setOnClickListener(this);
}
#Override
public void onClick(View v) {
//TODO - do something here if you wish
}
}
Upon further investigation I found that the parse-android SDK does not fetch pointers the same every single time. For example when I fetch 'friendsArray', let's say right now, it will return
[com.parse.ParseUser#ae66779, com.parse.ParseUser#8371cbe, com.parse.ParseUser#32d511f, com.parse.ParseUser#5fd2c6c, com.parse.ParseUser#7dd0235, com.parse.ParseUser#9c446ca, com.parse.ParseUser#5fe03b]
However if I then fetch it, let's say in 5 minutes, it will return
[com.parse.ParseUser#ec99877, com.parse.ParseUser#674bcg, com.parse.ParseUser#749hhc, com.parse.ParseUser#6fh3d6dg, com.parse.ParseUser#jdj8dk, com.parse.ParseUser#4c966ca, com.parse.ParseUser#3f0eeb]
Additionally, I noted that even the pointer to searchResultsList.get(position) changes it's reference every time I loaded it.
The way I got around this was to create a function (seen below) that returns an array of the actual objectId's of the pointers inside the 'friendsArray'. This way I can guarantee that it will always be returning the same items and can therefore create an accurate 'contains' comparison.
public static List<String> friendsArrayObjectIdsArray() {
//Create Array of Friends
List<ParseUser> friendsArray = ParseUser.getCurrentUser().getList("friendsArray");
//Create Temp Array of Object Id's
List<String> tempObjectIdsArray = new ArrayList<>();
//Iterate List
for (ParseUser friendUser : friendsArray) {
tempObjectIdsArray.add(friendUser.getObjectId());
}
return tempObjectIdsArray;
}
I then run the following comparison to get the result I am looking for
if (FLKUserUtils.friendsArrayObjectIdsArray().contains(searchResultsList.get(position).getObjectId())) {
//Do something
}

Downloading images and text from Parse and adding to ArrayList provides unexpected result?

I've been working on an app that downloads thumbnails and text from Parse and adds it to ArrayLists which are then displayed through a custom adapter for a ListView. The issue here is, the thumbnails for the required content is sometimes misplaced. For example: In my profile, instead of my pic another pic downloaded from parse would be placed. How to fix it ?
MainActivity.java ( Downloads the content )
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (isParseInitialized == false) {
Parse.initialize(new Parse.Configuration.Builder(this)
.applicationId("AppId")
.clientKey("ClientKey")
.server("https://parseapi.back4app.com")
.build()
);
isParseInitialized = true;
}
catchVideos();
progressBar = (ProgressBar) findViewById(R.id.progressBar);
context = this;
listView = (ListView) findViewById(R.id.listView);
customAdapter = new CustomAdapter(MainActivity.this, titles, thumbnails, channel);
//progressBar.setVisibility(View.INVISIBLE);
final Handler handler = new Handler();
Runnable run = new Runnable() {
#Override
public void run() {
handler.postDelayed(this, 1000);
if (tapped == true) {
ParseQuery<ParseObject> query = new ParseQuery<ParseObject>("Content");
query.whereEqualTo("Title", title);
query.findInBackground(new FindCallback<ParseObject>() {
#Override
public void done(List<ParseObject> objects, ParseException e) {
if(e == null) {
for (ParseObject object : objects) {
Log.i("Info", object.getString("url"));
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(object.getString("url")));
startActivity(intent);
}
}
}
});
tapped = false;
}
}
};
handler.post(run);
}
public void catchVideos(){
ParseQuery<ParseObject> query = new ParseQuery<ParseObject>("Content");
query.whereNotEqualTo("Status", null);
query.orderByDescending("createdAt");
query.findInBackground(new FindCallback<ParseObject>() {
#Override
public void done(List<ParseObject> objects, ParseException e) {
if(e == null) {
if (!titles.isEmpty()) {
titles.clear();
}
if (!channel.isEmpty()) {
channel.clear();
}
if (!thumbnails.isEmpty()) {
thumbnails.clear();
}
for (ParseObject object : objects) {
titles.add(object.getString("Title"));
channel.add(object.getString("Channel"));
ParseFile file = (ParseFile) object.get("Thumbnail");
file.getDataInBackground(new GetDataCallback() {
#Override
public void done(byte[] data, ParseException e) {
if (e == null) {
Bitmap thumbnail = BitmapFactory.decodeByteArray(data, 0, data.length);
thumbnails.add(thumbnail);
listView.setAdapter(customAdapter);
progressBar.setVisibility(View.INVISIBLE);
}
}
});
customAdapter.notifyDataSetChanged();
Log.i("Info", object.getString("Title"));
Log.i("Info", object.getString("url"));
}
}
}
});
}
CustomAdapter.java
public class CustomAdapter extends BaseAdapter{
ArrayList<String> result;
ArrayList<String> channelName;
Context context;
ArrayList<Bitmap> imageId;
private static LayoutInflater inflater=null;
public CustomAdapter(MainActivity mainActivity, ArrayList<String> titles, ArrayList<Bitmap> thumbnails, ArrayList<String> channel) {
// TODO Auto-generated constructor stub
result=titles;
channelName=channel;
context=mainActivity;
imageId=thumbnails;
inflater = ( LayoutInflater )context.
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
// TODO Auto-generated method stub
return result.size();
}
#Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return position;
}
#Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
public class Holder
{
TextView tv;
TextView channelText;
ImageView img;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
Holder holder=new Holder();
View rowView;
rowView = inflater.inflate(R.layout.custom_row, null);
holder.tv=(TextView) rowView.findViewById(R.id.textView1);
holder.channelText = (TextView) rowView.findViewById(R.id.channel);
holder.img=(ImageView) rowView.findViewById(R.id.imageView1);
try {
holder.img.setImageBitmap(imageId.get(position));
} catch (Exception e) {
e.printStackTrace();
}
holder.tv.setText(result.get(position));
holder.channelText.setText(channelName.get(position));
rowView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
//Toast.makeText(context, "You Clicked "+ result.get(position), Toast.LENGTH_LONG).show();
//Launch URL
MainActivity.tapped = true;
MainActivity.title = result.get(position);
}
});
return rowView;
}
}
You're adding each image to a list once it is downloaded. Later, you're iterating over the images in order. However, the order that they appear in the list won't necessarily be the same as the order you requested them. They're being loaded asynchronously, meaning that the code doesn't wait for the image to finish loading before moving on to the next one. If you start loading a large image and then start loading a small image immediately afterwards, the small one might finish downloading first. In the resulting list, the small image would appear before the larger one, even though it was requested second.
To fix the order of the list of images, you could use Futures. Instead of declaring thumbnails as a List<Bitmap>, make it a List<Future<Bitmap>>. Then, add all three items to the list at the same time.
titles.add(object.getString("Title"));
channel.add(object.getString("Channel"));
CompletableFuture<Bitmap> futureThumbnail = new CompletableFuture<>();
thumbnails.add(futureThumbnail);
That guarantees all three lists will be in the same order, regardless of how long the images take to download.
The next step is to fill in each future with the corresponding image.
ParseFile file = (ParseFile) object.get("Thumbnail");
file.getDataInBackground(new GetDataCallback() {
#Override
public void done(byte[] data, ParseException e) {
if (e == null) {
Bitmap thumbnail = BitmapFactory.decodeByteArray(data, 0, data.length);
future.complete(thumbnail);
...
This method has the added benefit that you can wait for the images to finish downloading. To wait for all the images to download and put them in a list in the right order:
List<Bitmap> thumbnails = new ArrayList<>();
for (Future<Bitmap> future : futureThumbnails) {
thumbnails.add(future.get());
}
Or, if you prefer not to wait:
List<Bitmap> thumbnails = new ArrayList<>();
Bitmap defaultValue = null; // or preferably some other default value
for (Future<Bitmap> future : futureThumbnails) {
thumbnails.add(future.isDone() ? future.get() : defaultValue);
}

ProgressBar - Not Updating in AsyncTask

Hi just had a quick question about why my progressbar isn't updating. I will add comments in the below to demonstrate what is working and what isn't.
To my knowledge it should be working since it updates in an asynctask.
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = convertView;
if (!(data.get(position) instanceof TemporarySongInfomation)) {
SongViewHolder holder;
view = inflater.inflate(R.layout.music_list_format, null);
holder = new SongViewHolder();
holder.timesplayed = (TextView) view.findViewById(R.id.textView7);
holder.artist = (TextView) view.findViewById(R.id.textView6);
holder.title = (TextView) view.findViewById(R.id.textView5);
holder.imagebutton = (ImageButton) view.findViewById(R.id.playbutton);
holder.source = (TextView) view.findViewById(R.id.textView8);
tempValue = (SongInfomation) data.get(position);
String songName = tempValue.getName();
holder.imagebutton.setBackgroundResource(R.drawable.playbutton1);
holder.source.setText(tempValue.getVideoid());
holder.title.setText(songName.length() > 45 ? songName.substring(0, 38) + "..." : songName);
holder.timesplayed.setText("" + tempValue.getTimesplayed());
holder.artist.setText(tempValue.getArtist());
swipeDetector = new SwipeDetector();
view.setOnClickListener(new SongListOnItemClickListener(position));
view.setOnTouchListener(swipeDetector);
holder.imagebutton.setOnClickListener(new OnPlayButtonClickListener(position));
} else {
TemporarySongViewHolder holder;
view = inflater.inflate(R.layout.music_list_process_format, null);
holder = new TemporarySongViewHolder();
holder.artist = (TextView) view.findViewById(R.id.artisttemp);
holder.bar = (ProgressBar) view.findViewById(R.id.ppbar);
holder.title = (TextView) view.findViewById(R.id.titletemp);
holder.source = (TextView) view.findViewById(R.id.sourcetemp);
tempValue1 = (TemporarySongInfomation) data.get(position);
String songName = tempValue1.getName();
holder.source.setText(tempValue1.getVideoid());
holder.title.setText(songName.length() > 45 ? songName.substring(0, 38) + "..." : songName);
holder.artist.setText(tempValue1.getArtist());
holder.bar.setMax(100);
// the below line starts the task!
new UpdateProgressBar(holder.bar, tempValue1).execute();
}
return view;
}
private class UpdateProgressBar extends AsyncTask<Void, Void, Void> {
private TemporarySongInfomation songinfo;
private ProgressBar progress;
UpdateProgressBar(ProgressBar bar, TemporarySongInfomation tp) {
progress = bar;
songinfo = tp;
}
#Override
protected Void doInBackground(Void... params) {
while (!songinfo.isCompleted()) {
System.out.println("going " + (int) songinfo.getProgress());
// the above line prints different values for songinfo.getProgress()
progress.setProgress((int) songinfo.getProgress());
publishProgress();
System.out.println("Progress "+progress.getProgress());
// the above line only prints "Progress 0"
// and obviously the ui doesnt update.
try {
Thread.sleep(500);
} catch (Exception e) {
}
}
return null;
}
}
publishProgress(Progress...) calls onProgressUpdate(Progress...)
onProgressUpdate(Progress...) invoked on the UI thread after a call to
publishProgress(Progress...). The timing of the execution is
undefined. This method is used to display any form of progress in the
user interface while the background computation is still executing.
For instance, it can be used to animate a progress bar or show logs in
a text field.
so basically you need to update the UI thread from onProgressUpdate method.
Here an example:
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
This part is wrong
progress.setProgress((int) songinfo.getProgress());
publishProgress();
You need to update the progress bar from the UI thread. So to update progress, you must override onProgressUpdate, which is run on the UI thread, and update your progress bar from there.
in doInBackground, do this
publishProgress((int) songinfo.getProgress()); // this calls onProgressUpdate on the UI thread
then, in onProgressUpdate, do this
progress.setProgress(values[0]); // called on UI thread
You will also need to change your AsyncTask class definition
private class UpdateProgressBar extends AsyncTask<Void, Integer, Void> { // Integer progress type

Categories