i am writing a flickr client as part of a project from a book that i am reading. in it i create a subclass of HandlerThread that downloads images from flickr and then sets them in an ImageView. i set a placeholder .png earlier in the program and this binds (but with delay) however when i try to replace it nothing happens. the placeholder stays there and is not replaced. it seems like something is happening between that is preventing my HandlerThread from running properly. i have tried stepping through this with the debugger and also looking through the logcat however the logcat has been particularly unhelpful ever since the last update to android studio. it works partially if at all. so right i am getting no error message. earlier i got one stating that i had a null pointer exception but i wasnt able to see where it had come from and upon running the application again it was gone. i am posting samples of my code including the main Fragment which serves as the UI Thread, and the ThumbnailDownloader class which is my HandlerThread subclass.
public class PhotoGalleryFragment extends Fragment {
private static final String TAG = "PhotoGalleryFragment";
private RecyclerView mRecyclerView;
private List<GalleryItem> mItems = new ArrayList<>();
private ThumbnailDownloader<PhotoHolder> mThumbnailDownloader;
public static PhotoGalleryFragment newInstance(){
return new PhotoGalleryFragment();
}
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstance(true);
new FetchItemsTask().execute();
Handler responseHandler = new Handler();
mThumbnailDownloader = new ThumbnailDownloader<>(responseHandler);
mThumbnailDownloader.setThumbnailDownloadListener( new ThumbnailDownloader.ThumbnailDownloadListener<PhotoHolder>() {
#Override
public void onThumbnailDownloaded(PhotoHolder target, Bitmap bitmap) {
Log.i(TAG, "setThumbnailDownloadListener() called!");
Drawable drawable = new BitmapDrawable(getResources(), bitmap);
target.bindDrawable(drawable);
}
});
mThumbnailDownloader.start();
mThumbnailDownloader.getLooper();
Log.i(TAG, "background thread started!");
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View v = inflater.inflate(R.layout.fragment_photo_gallery, container, false);
mRecyclerView = (RecyclerView) v.findViewById(R.id.photo_recycler_view);
mRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 3));
setupAdapter();
return v;
}
public void onDestroyView(){
super.onDestroyView();
mThumbnailDownloader.clearQueue();
}
#Override
public void onDestroy(){
super.onDestroy();
mThumbnailDownloader.quit();
Log.i(TAG, "background thread destroyed!");
}
private void setupAdapter(){
if (isAdded()){
mRecyclerView.setAdapter(new PhotoAdapter(mItems));
}
}
private class PhotoHolder extends RecyclerView.ViewHolder{
private ImageView mItemImageView;
public PhotoHolder(View itemView){
super(itemView);
mItemImageView = (ImageView) itemView.findViewById(R.id.item_image_view);
}
public void bindDrawable(Drawable drawable){
mItemImageView.setImageDrawable(drawable);
}
}//end PhotoHolder inner class
private class PhotoAdapter extends RecyclerView.Adapter<PhotoHolder>{
private List<GalleryItem> mGalleryItems;
public PhotoAdapter(List<GalleryItem> items){
mGalleryItems = items;
}
#Override
public PhotoHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
View view = layoutInflater.inflate(R.layout.list_item_gallery, viewGroup, false);
return new PhotoHolder(view);
}
#Override
public void onBindViewHolder(PhotoHolder photoHolder, int position) {
GalleryItem galleryItem = mGalleryItems.get(position);
Drawable placeholder = getResources().getDrawable(R.drawable.wait); <--this is the placeholder image
photoHolder.bindDrawable(placeholder);//this <--this sets the placeholder but seems to wait for the thumbnail download to complete for some reason
mThumbnailDownloader.queueThumbnail(photoHolder, galleryItem.getUrl());
}
#Override
public int getItemCount() {
return mGalleryItems.size();
}
}
private class FetchItemsTask extends AsyncTask<Void, Void, List<GalleryItem>>{
#Override
protected List<GalleryItem> doInBackground(Void... params) {
return new FlickrFetchr().fetchItems();
}
#Override
protected void onPostExecute(List<GalleryItem> items){
mItems = items;
setupAdapter();
}
}//end FetchItemsTask inner class
}//end class
and here is my HandlerThread implementation:
public class ThumbnailDownloader<T> extends HandlerThread {
private static final String TAG = "ThumbnailDownloader";
private static final int MESSAGE_DOWNLOAD = 0;
private boolean mHasQuit = false;
private Handler mRequestHandler = new Handler();
private Handler mResponseHandler = new Handler();
private ThumbnailDownloadListener<T> mThumbnailDownloadListener;
private ConcurrentMap<T, String> mRequestMap = new ConcurrentHashMap<>();
public interface ThumbnailDownloadListener<T>{
void onThumbnailDownloaded(T target, Bitmap bitmap);
}
public void setThumbnailDownloadListener(ThumbnailDownloadListener<T> listener){
mThumbnailDownloadListener = listener;
}
public ThumbnailDownloader(Handler responseHandler){
super(TAG);
mResponseHandler = responseHandler;
Log.i(TAG, "ThumbnailDownloader created!");
}
#Override
protected void onLooperPrepared(){
mRequestHandler = new Handler(){
#Override
public void handleMessage(Message msg){
if (msg.what == MESSAGE_DOWNLOAD){
T target = (T) msg.obj;
Log.i(TAG, "got a request for a url:" + mRequestMap.get(target));
handleRequest(target);
}
}
};
}
#Override
public boolean quit(){
mHasQuit = true;
return super.quit();
}
public void queueThumbnail(T target, String url){
Log.i(TAG, "got a url: " + url);
if(url == null){
mRequestMap.remove(target);
}else {
mRequestMap.put(target, url);
mRequestHandler.obtainMessage(MESSAGE_DOWNLOAD, target).sendToTarget();
}
}
public void clearQueue(){
mRequestHandler.removeMessages(MESSAGE_DOWNLOAD);
mRequestMap.clear();
}
private void handleRequest(final T target){
try{
final String url = mRequestMap.get(target);
if (url == null){
return;
}
byte[] bitmapBytes = new FlickrFetchr().getUrlBytes(url);
final Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length);
Log.i(TAG, "Bitmap created!"); //<---this is output in the logcat at the terminal but not the android monitor
mResponseHandler.post(new Runnable(){
#Override
public void run() {
if (mRequestMap.get(target) != null || mHasQuit){
return;
}
mRequestMap.remove(target);
mThumbnailDownloadListener.onThumbnailDownloaded(target, bitmap);
}
});
}catch (IOException ioe){
Log.e(TAG, "error downloading image: ", ioe);
}
}
}
honestly at this point i am not even sure where the problem is. it doesn't seem like queueThumbnail ever runs. but just setting the placeholder wait.png takes so long that it almost seems like there is some mix up between the placeholder and the downloaded image when it comes to binding to the ViewHolder. i am just not sure where it could be. i added comments to point this out.
I'm trying to see what is making my listview jerk sometimes when scroll, at times it's bad especially when the application first launches.
All the conditions I have are necessary, unless there is something I don't know(highly likely).
I'm not running certain tasks on a seperate thread because they are dependent on the data I receive from the backend(I'm coding both, so backend suggestions are welcome as well). Product is in beta but really need to make this a slightly bit smoother. I'm compressing the images, and they are a bit long but it's not the problem because when I upload the images from the device, I also include the width and height of the image and send that along to the backend. These dimensions come back when loading the list.
One thing I wonder is if calculating/converting the dimensions for the specific device's screen is causing the slight lag. Not sure how resource intensive that task is, but without it(without knowing the dimensions, each row would start out flat and then expand to the actual picture size which would cause the list to jump, so I can't run that calculation on the background either.)
Basically the scrolling isn't bad, but I need to improve this somehow.
Here is my Adapter:
public class VListAdapter extends BaseAdapter {
ViewHolder viewHolder;
private boolean isItFromProfile;
/**
* fields For number formating, ex. 1000
* would return 1k in the format method
*/
private static final NavigableMap<Long, String> suffixes = new TreeMap<>();
static {
suffixes.put(1_000L, "k");
suffixes.put(1_000_000L, "M");
suffixes.put(1_000_000_000L, "G");
suffixes.put(1_000_000_000_000L, "T");
suffixes.put(1_000_000_000_000_000L, "P");
suffixes.put(1_000_000_000_000_000_000L, "E");
}
private Context mContext;
private LayoutInflater mInflater;
private ArrayList<Post> mDataSource;
private static double lat;
private static double lon;
public VListAdapter(Context context, ArrayList<Post> items) {
mContext = context;
mDataSource = items;
//mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
isItFromProfile = false;
mInflater = LayoutInflater.from(context);
}
public VListAdapter() {
}
public VListAdapter(Context baseContext, ArrayList<Post> posts, boolean b) {
mContext = baseContext;
mDataSource = posts;
//mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
isItFromProfile = b;
mInflater = LayoutInflater.from(baseContext);
}
public void addElement(Post post) {
mDataSource.add(0, post);
this.notifyDataSetChanged();
}
#Override
public int getCount() {
return mDataSource.size();
}
#Override
public Object getItem(int position) {
return mDataSource.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
int limit = Math.min(position + 4, getCount());
for (int i = position; i < limit; i++) {
Glide.with(mContext).load(((Post) getItem(i)).getFilename().toString()).preload();
}
// StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads()
// .detectDiskWrites().detectNetwork()
// .penaltyLog().build());
View rowView = convertView;
if (rowView == null) {
viewHolder = new ViewHolder();
rowView = mInflater.inflate(R.layout.mylist, parent, false);
viewHolder.titleTextView = (TextView) rowView.findViewById(R.id.usernameinlist);
viewHolder.timeago = (TextView) rowView.findViewById(R.id.timeago);
//viewHolder.sharebutton = (ImageView) rowView.findViewById(R.id.sharebutton);
viewHolder.likesTextView = (TextView) rowView.findViewById(R.id.likestext);
viewHolder.viewcount = (TextView) rowView.findViewById(R.id.viewcount);
viewHolder.distance = (TextView) rowView.findViewById(R.id.distance);
viewHolder.footprints = (TextView) rowView.findViewById(R.id.footprintcount);
viewHolder.postText = (TextView) rowView.findViewById(R.id.posttext);
viewHolder.profilePic = (ImageView) rowView.findViewById(R.id.profilethumb);
viewHolder.caption = (TextView) rowView.findViewById(R.id.captiontext);
viewHolder.moremenu = (ImageView) rowView.findViewById(R.id.dots);
viewHolder.likesPic = (ImageView) rowView.findViewById(R.id.likeimage);
viewHolder.mapitPic = (ImageView) rowView.findViewById(R.id.mapimage);
viewHolder.playbutton = (ImageView) rowView.findViewById(R.id.playbutton);
viewHolder.videoThumb = (ImageView) rowView.findViewById(R.id.videothumb);
viewHolder.listphoto = (ImageView) rowView.findViewById(R.id.listphoto);
viewHolder.rainbow = (ImageView) rowView.findViewById(R.id.rainbow);
rowView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) rowView.getTag();
}
final Post post = (Post) getItem(position);
int color = Color.parseColor("#dddddd");
viewHolder.likesPic.setColorFilter(color);
viewHolder.mapitPic.setColorFilter(color);
viewHolder.moremenu.setColorFilter(color);
if (Hawk.count() == 0)
initHawkWithDataFromServer();
if (isItFromProfile) {
viewHolder.profilePic.setVisibility(View.GONE);
viewHolder.titleTextView.setVisibility(View.GONE);
viewHolder.distance.setVisibility(View.GONE);
}
viewHolder.titleTextView.setText(post.getUsername());
PrettyTime prettyTime = new PrettyTime();
DateTime dateTime = new DateTime(post.getUploadDate().get$date());
viewHolder.timeago.setText(prettyTime.format(dateTime.toDate()));
viewHolder.likesTextView.setText(String.valueOf(format(post.getLikes())));
viewHolder.footprints.setText(String.valueOf(format(post.getLocation().size() - 1)));
//don't display 0 if there are no likes, just show heart icon
if (viewHolder.likesTextView.getText().equals("0"))
viewHolder.likesTextView.setVisibility(View.GONE);
else
viewHolder.likesTextView.setVisibility(View.VISIBLE);
//don't display 0 if there are no footprints
if (viewHolder.footprints.getText().equals("0"))
viewHolder.footprints.setVisibility(View.GONE);
else
viewHolder.footprints.setVisibility(View.VISIBLE);
double[] loc = post.getLocation().get(0);
viewHolder.distance.setText("~" + PostListFragment.distance(loc[0], loc[1], 'M') + " Miles");
if (post.getViews() != null)
viewHolder.viewcount.setText(format(post.getViews()) + (post.getViews() == 1 ? " View" : " Views"));
String profilePictureS3Url = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + post.getUsername()
+ ".jpg";
String filename = post.getS3link();
final String videoThumbURL = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + filename;
Glide.with(mContext).load(profilePictureS3Url).asBitmap().centerCrop().into(new BitmapImageViewTarget(viewHolder.profilePic) {
#Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.create(mContext.getResources(), resource);
circularBitmapDrawable.setCircular(true);
viewHolder.profilePic.setImageDrawable(circularBitmapDrawable);
}
});
int height = ((Post) getItem(position)).getHeight();
int width = ((Post) getItem(position)).getWidth();
if (height != 0 && width != 0) {
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
Resources r = mContext.getResources();
height = (int) getHeight(height, width);
params.height = height;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);
} else {
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);
}
if (post.getType() == null) {
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
//Glide.clear(viewHolder.listphoto);
viewHolder.videoThumb.setVisibility(View.VISIBLE);
viewHolder.rainbow.setVisibility(View.VISIBLE);
Glide.with(mContext).load(videoThumbURL).fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate().into(viewHolder.videoThumb);
viewHolder.playbutton.setVisibility(View.VISIBLE);
}
if (post.getType() != null) {
if (post.getType().equals("video")) {
viewHolder.playbutton.setVisibility(View.VISIBLE);
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
Glide.clear(viewHolder.postText);
viewHolder.postText.setVisibility(View.GONE);
viewHolder.videoThumb.setVisibility(View.VISIBLE);
viewHolder.rainbow.setVisibility(View.VISIBLE);
Glide.with(mContext).load(videoThumbURL).fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate().into(viewHolder.videoThumb);
}
if (post.getType().equals("image")) {
Glide.clear(viewHolder.videoThumb);
viewHolder.videoThumb.setVisibility(View.GONE);
viewHolder.rainbow.setVisibility(View.GONE);
Glide.clear(viewHolder.playbutton);
viewHolder.playbutton.setVisibility(View.GONE);
Glide.clear(viewHolder.postText);
viewHolder.postText.setVisibility(View.GONE);
viewHolder.listphoto.setVisibility(View.VISIBLE);
viewHolder.listphoto.setBottom(0);
Glide.with(mContext).load(post.getFilename().toString())
.diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate()
.into(viewHolder.listphoto);
}
if (post.getType().equals("text")) {
Glide.clear(viewHolder.videoThumb);
viewHolder.videoThumb.setVisibility(View.GONE);
viewHolder.rainbow.setVisibility(View.GONE);
Glide.clear(viewHolder.playbutton);
viewHolder.playbutton.setVisibility(View.GONE);
Glide.clear(viewHolder.listphoto);
viewHolder.listphoto.setVisibility(View.GONE);
viewHolder.postText.setVisibility(View.VISIBLE);
viewHolder.postText.setText(post.getText());
}
}
if (Hawk.contains("liked" + post.getId().get$oid())) {
viewHolder.likesPic.clearColorFilter();
Glide.with(mContext).load(R.drawable.heartroundorange).into(viewHolder.likesPic);
((ImageView) viewHolder.likesPic).setColorFilter(Color.parseColor("#ff3a6f"));
} else {
Glide.with(mContext).load(R.drawable.heartroundgray).diskCacheStrategy(DiskCacheStrategy.ALL)
.into(viewHolder.likesPic);
}
if (Hawk.contains("mapped" + post.getId().get$oid())) {
viewHolder.mapitPic.clearColorFilter();
((ImageView) viewHolder.mapitPic).setImageResource(R.drawable.dropmaincolororange);
((ImageView) viewHolder.mapitPic).setColorFilter(Color.parseColor("#444444"));
} else {
Glide.with(mContext).load(R.drawable.dropdarkgray).diskCacheStrategy(DiskCacheStrategy.ALL)
.into(viewHolder.mapitPic);
}
if (!Hawk.contains("mapped" + post.getId().get$oid())) {
viewHolder.mapitPic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Hawk.put("mapped" + post.getId().get$oid(), 1);
((ImageView) viewHolder.mapitPic).setImageResource(R.drawable.dropmaincolororange);
viewHolder.footprints.setText(String.valueOf(post.getLocation().size() + 1));
post.getLocation().add(new double[]{PostListFragment.lon, PostListFragment.lat});
notifyDataSetChanged();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
postMappedToServer(post.getId().get$oid());
}
});
t.start();
TastyToast.makeText(mContext, "Post dropped off here.", TastyToast.LENGTH_SHORT, TastyToast.CONFUSING);
}
});
} else {
viewHolder.mapitPic.setClickable(false);
}
if (!Hawk.contains("liked" + post.getId().get$oid())) {
viewHolder.likesPic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Hawk.put("liked" + post.getId().get$oid(), 1);
viewHolder.likesPic.setClickable(false);
((ImageView) viewHolder.likesPic).setImageResource(R.drawable.heartroundorange);
viewHolder.likesTextView.setText(String.valueOf(post.getLikes() + 1));
post.setLikes(post.getLikes() + 1);
notifyDataSetChanged();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
postLikeToServer(post);
}
});
t.start();
}
});
} else {
viewHolder.likesPic.setClickable(false);
}
if (post.getType() == null || post.getType().equals("video"))
viewHolder.videoThumb.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (VListAdapter.this.mContext instanceof ProfileFeed) {
((ProfileFeed) VListAdapter.this.mContext).closeActivity();
}
Intent broadcast = new Intent();
broadcast.setAction("com.molehead.openout.POST");
broadcast.putExtra("postId", post.getFilename().toString());
broadcast.putExtra("hawkId", post.getId().get$oid());
broadcast.putExtra("s3link", post.getS3link());
broadcast.putExtra("username", post.getUsername());
if (Hawk.contains("liked" + post.getId().get$oid()))
broadcast.putExtra("liked", "yes");
else
broadcast.putExtra("liked", "no");
broadcast.putExtra("likecount", post.getLikes().toString());
App.post = post;
LocalBroadcastManager.getInstance(mContext.getApplicationContext()).sendBroadcast(broadcast);
}
});
viewHolder.moremenu.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
PopupMenu popup = new PopupMenu(mContext.getApplicationContext(), viewHolder.moremenu, Gravity.CENTER);
//Inflating the Popup using xml file
popup.getMenuInflater().inflate(R.menu.menu_main, popup.getMenu());
//registering popup with OnMenuItemClickListener
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_share:
String postId = post.getId().get$oid();
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
String shareBody = postId + ".jpg"; //https://openout.herokuapp.com/posts/" + postId;
String shareSub = "Shared via Molehead";
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, shareSub);
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
sharingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent new_intent = Intent.createChooser(sharingIntent, "Share");
new_intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.getApplicationContext().startActivity(new_intent);
break;
}
return true;
}
});
popup.show();
}
});
return rowView;
}
private void initHawkWithDataFromServer() {
SharedPreferences settings = mContext.getApplicationContext().getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
final Call<List<Post>> call = loginService.getLikes(username);
Log.i("lonlat", String.valueOf(lon) + " and " + String.valueOf(lat));
call.enqueue(new Callback<List<Post>>() {
#Override
public void onResponse(Call<List<Post>> call, Response<List<Post>> response) {
ArrayList<Post> posts = new ArrayList<>();
posts = (ArrayList<Post>) response.body();
if (!posts.isEmpty())
for (Post p : posts) {
Hawk.put("liked" + p.getId().get$oid(), 1);
}
}
#Override
public void onFailure(Call<List<Post>> call, Throwable t) {
}
});
}
private void postMappedToServer(String oid) {
SharedPreferences settings = mContext.getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
Log.i("postlistfraglat", String.valueOf(PostListFragment.lat));
Call<ResponseBody> call = loginService.addLocation(oid, PostListFragment.lon, PostListFragment.lat);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful())
Log.i("mapped", "success");
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
}
public void postLikeToServer(Post post) {
SharedPreferences settings = mContext.getSharedPreferences("userinfo", 0);
String username = settings.getString("username", "ok");
String password = settings.getString("password", "ok");
LoginService loginService =
ServiceGenerator.createService(LoginService.class, username, password);
Call<ResponseBody> call = loginService.like(post, 1, username);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
try {
Log.i("call", response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.i("MFEED", "like request failed");
}
});
}
public static String format(long value) {
//Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here
if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1);
if (value < 0) return "-" + format(-value);
if (value < 1000) return Long.toString(value); //deal with easy case
Map.Entry<Long, String> e = suffixes.floorEntry(value);
Long divideBy = e.getKey();
String suffix = e.getValue();
long truncated = value / (divideBy / 10); //the number part of the output times 10
boolean hasDecimal = truncated < 100 && (truncated / 10d) != (truncated / 10);
return hasDecimal ? (truncated / 10d) + suffix : (truncated / 10) + suffix;
}
static class ViewHolder {
private TextView titleTextView;
private TextView timeago;
private TextView likesTextView;
private TextView viewcount;
private TextView distance;
private TextView footprints;
private ImageView profilePic;
private ImageView moremenu;
private ImageView likesPic;
private ImageView mapitPic;
private ImageView rainbow;
//private ImageView sharebutton;
private TextView caption;
private ImageView listphoto;
private ImageView videoThumb;
private ImageView playbutton;
private TextView postText;
private Post post;
}
private float getHeight(float height, float width) {
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return (height * size.x / width);
}
}
It is impossible to point to a specific issue because there is so much code in your Adapter. One thing is sure, though - switching to RecyclerView won't help you in this case.
Adapters should not contain business logic - they should only "adapt" input objects to the underlying Views. In your case, it seems like the adapter performs calculations, spawns new threads, performs network requests, etc.
You need to refactor your code such that the adapter will be similar to this:
public class PostsListAdapter extends ArrayAdapter<Post> {
private Context mContext;
public PostsListAdapter(Context context, int resource) {
super(context, resource);
mContext = context;
}
public void bindPosts(List<Post> posts) {
clear();
addAll(posts);
notifyDataSetChanged();
}
#NonNull
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// assign new View to convertView
// create new ViewHolder
// set ViewHolder as tag of convertView
// set listeners
} else {
// get a reference to existing ViewHolder
}
// populate ViewHolder's elements with data from getItem(position)
// kick off asynchronous loading of images
// NOTE: no calculations allowed here - just simple bidding of data to Views
return convertView;
}
}
Your code needs to be structured in such a way, that business logic that involves calculations and transformation of data executed before you bind a new data to ListView, and Post objects that you pass to bindPosts() method already contain the results of the aforementioned calculations and transformations.
Adapter just "adapts" the final data from Posts to Views - nothing more.
If you're short on time now, and just need to "make it work", then I would start by removing the logic that spawns new threads and makes network requests. See if this improves performance.
Change your implementation to RecyclerView which is more efficient in terms of scrapping views or recycling.
We can also enable optimizations if the items are static and will not change for significantly smoother scrolling:
recyclerView.setHasFixedSize(true);
Create an intent service and register BroadcastReceiver as data return callback or error callback when api request, business rule, data modification completed. Use synchronous call to execute initHawkWithDataFromServer() in advance and after getting result from api continue modifying or applying business logic. After that create new adapter or update existing adapter data set.
Move all the below data calculation or data value formatting logic from adapter's getView() to above intent service.
You can add more getter and setter to existing Post pojo.
DateTime dateTime = new DateTime(post.getUploadDate().get$date());
viewHolder.timeago.setText(prettyTime.format(dateTime.toDate()));
viewHolder.likesTextView.setText(String.valueOf(format(post.getLikes())));
viewHolder.footprints.setText(String.valueOf(format(post.getLocation().size)) - 1)));
Post{
//Your existing property
#Expose(serialize = false, deserialize = false)
//equals neither serialize nor deserialize or
private DateTime uploadedDateTime;
//etc. prettyTime.format, String.valueOf
}
Removes unnecessary reflection:
GsonBuilder builder = new GsonBuilder();
builder.excludeFieldsWithoutExposeAnnotation();
Gson gson = builder.create();
new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(gson)).build();
and it to your retrofit Service creation class.
You can also use transient(private transient DateTime uploadedDateTime;)
Remove
public void addElement(Post post) { mDataSource.add(0, post);
this.notifyDataSetChanged();}
and whenever you need to notify if a single or more items inserted, deleted etc. Use the below:
notifyItemChanged(int)
notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeChanged(int, int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)
We can use these from the activity or fragment:
//Add a new contact
items.add(0, new Post("Barney"));
//Notify the adapter that an item was inserted at position 0
adapter.notifyItemInserted(0);
Above methods are more efficient. Every time we want to add or remove items from the RecyclerView, we will need to explicitly inform to the adapter of the event. Unlike the ListView adapter, a RecyclerView adapter should not rely on notifyDataSetChanged() since the more granular actions should be used. See the API documentation for more details
Also, if you are intending to update an existing list, make sure to get the current count of items before making any changes. For instance, a getItemCount() on the adapter should be called to record the first index that will be changed.
// record this value before making any changes to the existing list
int curSize = adapter.getItemCount(); // replace this line with wherever you get new records
ArrayList<Post> newItems = Post.createPostsList(20);
// update the existing list
items.addAll(newItems);
// curSize should represent the first element that got added
// newItems.size() represents the itemCount
adapter.notifyItemRangeInserted(curSize, newItems.size());
Diffing Larger Changes
A new DiffUtil class has been added in the v24.2.0 of the support library to help compute the difference between the old and new list. Details
Don't preload images via glide if your image sizes are different. Try creating your own. Also try looking at.
Create color as the class member
int color = Color.parseColor("#dddddd");
Write View.GONE or View.VISIBLE in Post pojo itself, which will be executed in background thread from Retrofit if IntentService. Try api return boolean in json instead "0" as String.
Move all below to IntentService
//don't display 0 if there are no likes, just show heart icon
if (viewHolder.likesTextView.getText().equals("0"))
viewHolder.likesTextView.setVisibility(View.GONE);
else
viewHolder.likesTextView.setVisibility(View.VISIBLE);
//don't display 0 if there are no footprints
if (viewHolder.footprints.getText().equals("0"))
viewHolder.footprints.setVisibility(View.GONE);
else
viewHolder.footprints.setVisibility(View.VISIBLE);
double[] loc = post.getLocation().get(0);
viewHolder.distance.setText("~" + PostListFragment.distance(loc[0], loc[1], 'M') + " Miles");
All String concatenation also in Post or IntnetService like:
String profilePictureS3Url = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + post.getUsername() + ".jpg";
Also you can create color filter in advance and one time only. Remove scrollbar from listview as it calculates height to show scroll bar.
Too many things to improve here. Here is some examples.
I see this
if (Hawk.count() == 0)
initHawkWithDataFromServer();
I believe that the method initHawkWithDataFromServer will be called many times during the time the list appears.
This call can be done only once when the activity was created.
Glide.with(mContext).load(videoThumbURL).fitCenter()
But you should refactor your code first, moving the logic to another class. Try to remove some code like this (it should be done by using some layout attribites)
ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
Resources r = mContext.getResources();
height = (int) getHeight(height, width);
params.height = height;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewHolder.listphoto.setLayoutParams(params);
I'm editing an open source app: A simple coloring page app for kids. I need to be able to make the user import his own images to be colored,a code for a button in my menu that when clicked by the user it opens his gallery(on SD) to choose an image from and then import this image to the view where he could color it . Here is the full source code.
And here is the code for loading images from R.drawable:
public class StartNewActivity extends NoTitleActivity implements View.OnClickListener
{
// This is an expensive operation.
public static int randomOutlineId()
{
return new ResourceLoader().randomOutlineId();
}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Apparently this cannot be set from the style.
getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
setContentView(R.layout.start_new);
GridView gridview = (GridView) findViewById(R.id.start_new_grid);
gridview.setAdapter(new ImageAdapter(this));
}
public void onClick(View view)
{
setResult(view.getId());
finish();
}
private static class ResourceLoader
{
ResourceLoader()
{
// Use reflection to list resource ids of thumbnails and outline
// images.First, we list all the drawables starting with the proper
// prefixes into 2 maps.
Map<String, Integer> outlineMap = new TreeMap<String, Integer>();
Map<String, Integer> thumbMap = new TreeMap<String, Integer>();
Field[] drawables = R.drawable.class.getDeclaredFields();
for (int i = 0; i < drawables.length; i++)
{
String name = drawables[i].getName();
try
{
if (name.startsWith(PREFIX_OUTLINE))
{
outlineMap.put(name.substring(PREFIX_OUTLINE.length()),
drawables[i].getInt(null));
}
if (name.startsWith(PREFIX_THUMB))
{
thumbMap.put(name.substring(PREFIX_THUMB.length()),
drawables[i].getInt(null));
}
}
catch (IllegalAccessException e)
{
}
}
Set<String> keys = outlineMap.keySet();
keys.retainAll(thumbMap.keySet());
_outlineIds = new Integer[keys.size()];
_thumbIds = new Integer[keys.size()];
int j = 0;
Iterator<String> i = keys.iterator();
while (i.hasNext())
{
String key = i.next();
_outlineIds[j] = outlineMap.get(key);
_thumbIds[j] = thumbMap.get(key);
j++;
}
}
public Integer[] getThumbIds()
{
return _thumbIds;
}
public Integer[] getOutlineIds()
{
return _outlineIds;
}
public int randomOutlineId()
{
return _outlineIds[new Random().nextInt(_outlineIds.length)];
}
private static final String PREFIX_OUTLINE = "outline";
private static final String PREFIX_THUMB = "thumb";
private Integer[] _thumbIds;
private Integer[] _outlineIds;
}
private class ImageAdapter extends BaseAdapter
{
ImageAdapter(Context c)
{
_context = c;
_resourceLoader = new ResourceLoader();
}
public int getCount()
{
return _resourceLoader.getThumbIds().length;
}
public Object getItem(int i)
{
return null;
}
public long getItemId(int i)
{
return 0;
}
public View getView(int position, View convertView, ViewGroup parent)
{
ImageView imageView;
if (convertView == null)
{
// If it's not recycled, initialize some attributes
imageView = new ImageView(_context);
imageView.setLayoutParams(new GridView.LayoutParams(145, 145));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
imageView.setOnClickListener(StartNewActivity.this);
}
else
{
imageView = (ImageView) convertView;
}
imageView.setImageResource(_resourceLoader.getThumbIds()[position]);
imageView.setId(_resourceLoader.getOutlineIds()[position]);
return imageView;
}
private Context _context;
private ResourceLoader _resourceLoader;
}
}
I think you need to use pictures from sd card in your application .
You can do so using the following links
Load images from sd card folder
loading images from SD card directory in GridView
How to Dynamically show images from a folder in sdcard
gridview and setImageBitmap when load image from SDcard
Hope it helps
I want to read images from URL and show it in android gallery widget.
so I wrote below code in onCreate() method .
list = GuideDAO.getAllImages(businessId);
Gallery g = (Gallery) findViewById(R.id.gallery);
g.setSpacing(2);
// Set the adapter to our custom adapter (below)
if(list.size() > 0)
{
g.setAdapter(new ImageAdapter(this,list));
}
This is my ImageAdapter
public class ImageAdapter extends BaseAdapter {
List<Images> glist = null;
private String url;
public ImageAdapter(Context c,List<Images> lst) {
mContext = c;
glist = lst;
int i=0;
for (Images id : glist) {
url = id.getImageURL(); // Getting URL
InputStream inStream = null;
if (url.startsWith("http")) {
url = url.replace(" ", "%20");
HttpURLConnection conn;
try {
conn = (HttpURLConnection)new URL(url).openConnection();
conn.setDoInput(true);
conn.connect();
inStream = conn.getInputStream();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
inStream = new FileInputStream(url);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inPurgeable = true;
Bitmap b = BitmapFactory.decodeStream(inStream, null, options);
mImageCollection[i]=b;
i++;
}
}
public int getCount() {
return mImageIds.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ImageView i = new ImageView(mContext);
i.setImageBitmap(mImageCollection[position]);
i.setScaleType(ImageView.ScaleType.FIT_XY);
i.setLayoutParams(new Gallery.LayoutParams(136, 88));
return i;
}
private Context mContext;
private String[] mImageURLs = {};
private Bitmap[] mImageCollection = {};
}
This throw error because it not in Thread. How can I change this code so that URL reading and image loads in background?
So I have changed my ImageAdapter by using SmartImageView , which handles background thread and caching.
public class ImageAdapter extends BaseAdapter {
List<ImageGallery> glist = null;
private String url;
public ImageAdapter(Context c,List<ImageGallery> lst) {
mContext = c;
glist = lst;
int i=0;
al = new ArrayList<String>();
for (ImageGallery id : glist) {
al.add(id.getImageURL());
}
}
public int getCount() {
return mImageIds.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
Log.d("deepak", "getview gallery");
SmartImageView i = new SmartImageView(mContext);
i.setImageUrl(al.get(position));
i.setScaleType(ImageView.ScaleType.FIT_XY);
i.setLayoutParams(new Gallery.LayoutParams(136, 88));
return i;
}
private Context mContext;
private String[] mImageURLs = {};
private ArrayList<String> al;
private Bitmap[] mImageCollection = {};
private Integer[] mImageIds = {};
}
But my getView() is not getting called now.
you can make use of Smart Image view. SmartImageView is a drop-in replacement for Android’s standard ImageView which additionally allows images to be loaded from URLs or the user’s contact address book. Images are cached to memory and to disk for super fast loading.
Please refer the following link for more info https://github.com/loopj/android-smart-image-view .hope this may help u to accomplish ur task
I'd suggest writing an AsyncImageLoader class and having it handle image downloads from http. This way you can cache and manage everything on separate threads and have it set the image to the view once the loading is complete. Also you could use this class throughout the application if you want to download images elsewhere.
you could call something like mImageLoader.loadImage(myImageView, Url) in your adapter and it would drop it in once it was finished loading.
if you want more details let me know :)
hi i have create one simple media player.... now its working fine. i retrieve video thumbnails in gallery. when i click the thumbnails video will play in other page... if i click thumbnails video play same page...see my screen shot....my video play in that red box...this type of frame how to create.....
this is my screen shot:
if i click thumbnails the video will play another page:
that screen shot:
this is for my coding:
public class videothumb extends Activity
{
private final static Uri MEDIA_EXTERNAL_CONTENT_URI =
MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
private final static String _ID = MediaStore.Video.Media._ID;
private final static String MEDIA_DATA = MediaStore.Video.Media.DATA;
//flag for which one is used for images selection
private Gallery _gallery;
private Cursor _cursor;
private int _columnIndex;
private int[] _videosId;
private Uri _contentUri;
private int video_column_index;
protected Context _context;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_context = getApplicationContext();
setContentView(R.layout.main);
//set GridView for gallery
_gallery = (Gallery) findViewById(R.id.videoGrdVw);
//set default as external/sdcard uri
_contentUri = MEDIA_EXTERNAL_CONTENT_URI;
//initialize the videos uri
//showToast(_contentUri.getPath());
initVideosId();
//set gallery adapter
setGalleryAdapter();
}
private void setGalleryAdapter() {
_gallery.setAdapter(new VideoGalleryAdapter(_context));
_gallery.setOnItemClickListener(videogridlistener);
}
private void initVideosId() {
try
{
//Here we set up a string array of the thumbnail ID column we want to get back
String [] proj={_ID};
// Now we create the cursor pointing to the external thumbnail store
_cursor = managedQuery(_contentUri,
proj, // Which columns to return
null, // WHERE clause; which rows to return (all rows)
null, // WHERE clause selection arguments (none)
null); // Order-by clause (ascending by name)
int count= _cursor.getCount();
System.out.println("total"+_cursor.getCount());
// We now get the column index of the thumbnail id
_columnIndex = _cursor.getColumnIndex(_ID);
//initialize
_videosId = new int[count];
//move position to first element
_cursor.moveToFirst();
for(int i=0;i<count;i++)
{
int id = _cursor.getInt(_columnIndex);
//
_videosId[i]= id;
//
_cursor.moveToNext();
//
}
}catch(Exception ex)
{
showToast(ex.getMessage().toString());
}
}
protected void showToast(String msg)
{
Toast.makeText(_context, msg, Toast.LENGTH_LONG).show();
}
private AdapterView.OnItemClickListener videogridlistener = new OnItemClickListener() {
public void onItemClick(AdapterView parent, View v, int position,
long id) {
// Now we want to actually get the data location of the file
String [] proj={MEDIA_DATA};
// We request our cursor again
_cursor = managedQuery(_contentUri,
proj, // Which columns to return
null, // WHERE clause; which rows to return (all rows)
null, // WHERE clause selection arguments (none)
null);
//System.gc();
// video_column_index =
_cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA);
_columnIndex = _cursor.getColumnIndex(MEDIA_DATA);
// Lets move to the selected item in the cursor
_cursor.moveToPosition(position);
String filename = _cursor.getString(_columnIndex);
Intent intent = new Intent(videothumb.this, ViewVideo.class);
intent.putExtra("videofilename", filename);
startActivity(intent);
showToast(filename);
// Toast.makeText(videothumb.this, "" + position, Toast.LENGTH_SHORT).show();
}
};
private class VideoGalleryAdapter extends BaseAdapter
{
public VideoGalleryAdapter(Context c)
{
_context = c;
}
public int getCount()
{
return _videosId.length;
}
public Object getItem(int position)
{
return position;
}
public long getItemId(int position)
{
return position;
}
public View getView(int position, View convertView, ViewGroup parent)
{
ImageView imgVw= new ImageView(_context);
try
{
if(convertView!=null)
{
imgVw= (ImageView) convertView;
}
imgVw.setImageBitmap(getImage(_videosId[position]));
imgVw.setAdjustViewBounds(true);
imgVw.setBackgroundColor(Color.WHITE);
imgVw.setLayoutParams(new Gallery.LayoutParams(150, 100));
imgVw.setPadding(5,5,5,5);
imgVw.setScaleType(ImageView.ScaleType.FIT_XY);
}
catch(Exception ex)
{
System.out.println("StartActivity:getView()-135: ex " + ex.getClass() +",
"+ ex.getMessage());
}
return imgVw;
}
// Create the thumbnail on the fly
private Bitmap getImage(int id) {
Bitmap thumb = MediaStore.Video.Thumbnails.getThumbnail(getContentResolver(),id,
MediaStore.Video.Thumbnails.MICRO_KIND, null);
System.out.println("ff"+MediaStore.Video.Thumbnails.
getThumbnail(getContentResolver(),id, MediaStore.Video.Thumbnails.MICRO_KIND, null));
return thumb;
}
}
}
coding 2:`
public class ViewVideo extends Activity {
private String filename;
private VideoView Video;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main1);
//setContentView(new Zoom(this));
Video=(VideoView)findViewById(R.id.VideoView01);
System.gc();
Intent i = getIntent();
Bundle extras = i.getExtras();
filename = extras.getString("videofilename");
Video.setVideoPath(filename);
Video.setMediaController(new MediaController(this));
Video.requestFocus();
Video.start();
}
}
anyone help me...
You want to use Videoview
<VideoView android:id="#+id/Videoview1"
android:layout_marginTop="100px"
android:layout_marginLeft="10px"
android:layout_width="20px"
android:layout_height="200px"/>
and create object in your activity
and play.
videoview.start();
like this.
Hope this will help you
private VideoView mVView;
mVView= (VideoView) findViewById(R.id.Videoview1);
mVView.setVideoPath("sdcard/filename.mp4");
mVView.start();