So I've decided to try out the new Volley library as shown on Google IO 2013.
I've tried it out while using the easy solution of NetworkImageView to show multiple images on a GridView.
It works nice and shows images, but if I let it download the images and then I turn off the WiFi during the download, it doesn't show an error as if everything still loads. Not only that, but if I restore the connection, it doesn't resume the loading.
Why does it occur, and how can I fix it? Maybe it's actually a bug?
Here's my sample code, if anyone wishes to try it out (BitmapCacheLru code here):
public class MainActivity extends Activity {
private static final int COLUMNS_COUNT = 4;
private RequestQueue _requestQueue;
private ImageLoader _imageLoader;
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_requestQueue=Volley.newRequestQueue(this);
_imageLoader=new ImageLoader(_requestQueue, new BitmapLruCache());
final GridView gridView = new GridView(this);
gridView.setNumColumns(COLUMNS_COUNT);
final int screenWidth = getResources().getDisplayMetrics().widthPixels;
gridView.setAdapter(new BaseAdapter() {
#Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
NetworkImageView rootView = (NetworkImageView) convertView;
if (rootView == null) {
rootView = new NetworkImageView(MainActivity.this);
rootView.setLayoutParams(new AbsListView.LayoutParams(screenWidth / COLUMNS_COUNT, screenWidth / COLUMNS_COUNT));
rootView.setScaleType(ScaleType.CENTER_CROP);
rootView.setDefaultImageResId(android.R.drawable.sym_def_app_icon);
rootView.setErrorImageResId(android.R.drawable.ic_dialog_alert);
}
final String url = getItem(position);
rootView.setImageUrl(url, _imageLoader);
return rootView;
}
#Override
public long getItemId(final int position) {
return 0;
}
#Override
public String getItem(final int position) {
return Images.imageThumbUrls[position];
}
#Override
public int getCount() {
return Images.imageThumbUrls.length;
}
});
setContentView(gridView);
}
#Override
protected void onStop() {
_requestQueue.cancelAll(this);
super.onStop();
}
}
P.S. If you want to see the code of NetworkImageView, I think it's available here .
I think the problem is that the volley does not help you to reload the image.
A quick inspection shows that the NetworkImageView only loads data when onLayout method is called and the method loadImageIfNecessary will queue the network request if necessary.
When there is no Internet connection, the error callback will be called and there is no further action once the Internet get itself connected.
However, since you have the NetworkImage in a list, when you scroll the list, I suppose you will reuse the cell view and call setImageURL once again. If the Internet connection is available, the image will be loaded automatically. Alternatively, once the Internet connection is up, you can refresh the list view and so that the image will be loaded automatically.
Related
Hi I got frustrated while searching solution for my problem.My problem is that i have a gridview to show all images which i selected from gallery.I want to display progressbar on each images in gridview.and while uploading images to server using multipart i want too update progressbar..
I displayed progressbar on each imageview but i am unable to show progress on each progressbar.
SO please help me to show how to show progress bar and their respective process on each imageview.
thanks in advance
Create a interface for an observer:
interface ProgressListener {
void onProgressUpdate(String imagePath, int progress);
}
Let the view holder implement that observer and know the image path:
public class ViewHolder implements ProgressListener {
ImageView imgQueue;
ProgressBar pb;
TextView tv;
String imagePath; //set this in getView!
void onProgressUpdate(String imagePath, int progress) {
if (!this.imagePath.equals(imagePath)) {
//was not for this view
return;
}
pb.post(new Runnable() {
pb.setProgress(progress);
});
}
//your other code
}
The adapter shall hold an map of observers for a particular image path/uri whatever and have an method that is called by the upload/download task. Also add methods to add and remove observer:
public class SelectedAdapter_Test extends BaseAdapter {
private Map<String, ProgressListener> mProgressListener = new HashMap<>();
//your other code
synchronized void addProgressObserver(String imagePath, ProgressListener listener) {
this.mListener.put(imagePath, listener);
}
synchronized void removeProgressObserver(String imagePath) {
this.mListener.remove(imagePath);
}
synchronized void updateProgress(String imagePath, int progress) {
ProgressListener l = this.mListener.get(imagePath);
if (l != null) {
l.onProgressUpdate(imagePath, progress);
}
}
//other code
}
In getView of the adapter register the view holder as an observer:
public View getView(final int i, View convertView, ViewGroup viewGroup) {
//other code
holder.imagePath = data.get(i).getSdcardPath();
this.addProgressObserver(holder.imagePath, holder);
return convertView;
}
The problem right now is, that we register the observer but don't unregister. So let the adapter implement the View.addOnAttachStateChangeListener:
public class SelectedAdapter_Test extends BaseAdapter implements View.addOnAttachStateChangeListener {
//other code
void onViewAttachedToWindow(View v) {
//We ignore this
}
void onViewDetachedFromWindow(View v) {
//View is not visible anymore unregister observer
ViewHolder holder = (ViewHolder) v.getTag();
this.removeProgressObserver(holder.imagePath);
}
//other code
}
Register that observer when you return the view.
public View getView(final int i, View convertView, ViewGroup viewGroup) {
//other code
convertView.addOnAttachStateChangeListener(this);
return convertView;
}
Finally you are able to tell the views what the progress is:
#Override
public void transferred(long num) {
int progress = (int) ((num / (float) totalSize) * 100);
selectedAdapter.onProgressUpdate(listOfPhotos.get(i).getSdcardPath(), progress);
}
One final problem remains, what if the activity is gone while the upload is in progress? You need to check if the activity is still alive. Maybe setting a flag in the adapter to true in onCreate and to false in onDestroy would do the trick. Then the last code fragment could check that flag and not notify the adapter on changes anymore.
So thats basically the idea of how to solve this. Does it work? I don't know I wrote it from scratch without any testing. And even if it does, you still have to manage the states when the progress is 0 or 100. But I leave that to you. Also you might want to change the BaseAdapter for an recyclerView so that we can get rid of the View.addOnAttachStateChangeListener.
add boolean in adapter class
public SelectedAdapter_Test(Context c, ArrayList<CustomGallery> data, boolean showProgress) {
mContext = c;
inflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.data = data;
this.showProgress = showProgress;
}
changes in Adapter class getView
holder.pb = (ProgressBar) convertView.findViewById(R.id.progressbar);
if (showProgress)
holder.pb.setVisibility(View.VISIBLE);
else
holder.pb.setVisibility(View.GONE);
make changes in doFileUpload
private void doFileUpload(View v) {
View vi = v;
for (i = 0; i < listOfPhotos.size(); i++) {
<--your task-->
}
//**important**
SelectedAdapter_Test mTest = new SelectedAdapter_Test(context,data,false);
// set above adapter object respectively;
mList.setadapter(mTest);
}
FYI. pass showProgress value as true for the first time when you set adapter.
I was using recycler view to List Images but it was getting laggy after even caching and all. So I Decided to use Glide library but still laggy as hell. I finally thought to check with just a single drawable and still it's laggy. I don't get why. Please help. Here is the code. There is not much code still. You can see the code I was using for my Image Viewing commented out.
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ImageHolder>
{
private File file;
private String logging=getClass().getSimpleName();
private int size;
private MemCache memCache;
private Context context;
private Bitmap bitmap;
public ImageAdapter(File file,int Size,MemCache memCache,Context context)
{
this.file = file;
size=Size;
this.memCache=memCache;
this.context=context;
bitmap=BitmapFactory.decodeResource(context.getResources(),R.drawable.empty_photo);
}
#Override
public ImageHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
return new ImageHolder(new ImageView(context),size);
}
#Override
public void onBindViewHolder(ImageHolder holder, int position)
{
Glide.with(context).load(R.drawable.empty_photo).into(holder.getImageView());
// if (memCache.get(file.listFiles()[position].toString())!=null)
// {
// holder.getImageView().setImageBitmap(memCache.get(file.listFiles()[position].toString()));
// }
// else
// {
// ImageSyncTask imageSyncTask = new ImageSyncTask(size, holder.getImageView(),memCache);
// imageSyncTask.executeOnExecutor(imageSyncTask.THREAD_POOL_EXECUTOR, file.listFiles()[position]);
// }
// Glide.with(context).load(file.listFiles()[position]).crossFade().into(holder.getImageView());
}
#Override
public int getItemCount()
{
if (file!=null&&file.listFiles()!=null) return file.listFiles().length;
else return 0;
}
public static class ImageHolder extends RecyclerView.ViewHolder
{
private ImageView imageView;
public ImageHolder(View itemView,int size)
{
super(itemView);
imageView=(ImageView)itemView;
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setMinimumWidth(size);
imageView.setMinimumHeight(size);
}
public ImageView getImageView()
{
return imageView;
}
public void clearImage()
{
imageView.setImageResource(android.R.color.transparent);
}
}
}
I know there is one loading Bitmap but still its just one. That shouldn't make that much lag. And yes I have used the typical setImageBitmap instead of Glide but still laggy.
There is the view holder. Just Simple function. And I was previously using a proper layout for it but it was not working as well. In this I have just used New ImageView() and setParamaters just to make sure if there was a problem in Layout.
Please Help. I don't get why the typical Adapter is creating Lag.
Original MainActivity
public class MainActivity extends AppCompatActivity
{
RecyclerView recyclerView;
int cnum;
private MemCache memCache;
private String logging=getClass().getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DisplayMetrics displayMetrics=new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int maxmem=(int)Runtime.getRuntime().maxMemory()/1024;
int cache_mem=maxmem/10;
memCache=new MemCache(cache_mem);
int orientation=getResources().getConfiguration().orientation;
if (orientation== Configuration.ORIENTATION_PORTRAIT)
cnum=3;
else cnum=5;
recyclerView=(RecyclerView)findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new GridLayoutManager(getApplicationContext(),cnum));
recyclerView.setAdapter(new ImageAdapter(getGallery(),displayMetrics.widthPixels/cnum,memCache,getApplicationContext()));
}
private File getGallery()
{
return new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath(),"Trial");
}
}
I have also tried and removed maximum of calls which can be removed to make sure there is not much task on UI thread.
Doing disk I/O on the main application thread is a common source of "jank", as that I/O may take a bit of time. StrictMode can help you determine where you are doing disk I/O on the main application thread.
In general, you want to load your model data (e.g., a list of files) on a background thread, then work off of the in-memory representation of that model data on the main application thread.
I saw a kind of a similar unanswered question, but I can't comment and ask questions there and he is having this problem all the time even on a first load: Picasso doesn't load first image of ArrayAdapter
So the problem is: I have an app where I can scroll the GridView endlessly and download movie posters. When I return to the very beginning of the screen - it sometimes doesn't update only first poster and shows some other picture.
I set Targets to the ImageView to keep strong reference. My code is following:
#Override
public View getView(final int position, View view, ViewGroup parent) {
final MovieObject movieObject = getItem(position);
if (view == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.movie_item, parent, false);
}
final ProgressBar spinner = (ProgressBar) view.findViewById(R.id.movie_item_spinner);
final ImageView poster = (ImageView) view.findViewById(R.id.movie_poster);
// Target to show/hide ProgressBar on ImageView
final Target target = new Target() {
#Override
public void onPrepareLoad(Drawable drawable) {
poster.setBackgroundResource(R.color.white);
spinner.setVisibility(View.VISIBLE);
}
#Override
public void onBitmapLoaded(Bitmap photo, Picasso.LoadedFrom from) {
poster.setBackgroundDrawable(new BitmapDrawable(photo));
spinner.setVisibility(View.GONE);
}
#Override
public void onBitmapFailed(Drawable drawable) {
poster.setBackgroundResource(R.color.white);
}
};
// Save strong reference to be able to show pictures without sliding the screen
poster.setTag(target);
Picasso.with(getContext()).load(movieObject.poster_path).into((Target) poster.getTag());
// If movie doesn't have an image - uses text instead
if (movieObject.poster_path.contains("null"))
{
TextView imageText = (TextView) view.findViewById(R.id.movie_poster_text);
imageText.setText(movieObject.title);
}
poster.setContentDescription(movieObject.title);
return view;
}
How can I solve this problem?
Add: I also tried saving Targets in a MovieObject, I also tried declaring separate class for Target - nothing helped.
Solved the problem with a callback:
#Override
public View getView(final int position, View view, ViewGroup parent) {
...
spinner.setVisibility(View.VISIBLE);
Picasso.with(getContext()).load(movieObject.poster_path).into(poster, new Callback() {
#Override
public void onSuccess() {
spinner.setVisibility(View.GONE);
}
#Override
public void onError() {
poster.setBackgroundResource(R.color.white);
}
});
...
return view;
}
I'm still not sure how the poster will be recycled.
In Picasso docs they have a following note:
Note: The Callback param is a strong reference and will prevent your Activity or Fragment from being garbage collected. If you use this method, it is strongly recommended you invoke an adjacent Picasso.cancelRequest(android.widget.ImageView) call to prevent temporary leaking.
Where should I implement it?
I'm Settings up a Custom ListView.
The pull-to-refresh feature comes straight from https://github.com/chrisbanes/Android-PullToRefresh
The ListView displayes Images, so i created a custom Adapter:
class mAdapter extends BaseAdapter{
public mAdapter(Context context){
// nothing to do
}
#Override
public int getCount() {
return mValues.size();
}
#Override
public Object getItem(int position) {
return mValues.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public boolean areAllItemsEnabled()
{
return false;
}
#Override
public boolean isEnabled(int position)
{
return false;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if(v == null){
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.list_item, null);
}
ImageView iv = (ImageView) v.findViewById(R.id.imageView);
if(iv != null){
displayImageInView(iv);
iv.setClickable(true);
iv.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(context, "ImageView", Toast.LENGTH_SHORT).show();
}
});
}
return v;
}
}
in onCreate(), i get the listView and assign the adapter:
mListView = (PullToRefreshListView) findViewById(R.id.listView);
mListView.setAdapter(new mAdapter(context));
After that i add an image to mValues (url for image to load from web) and call notifiyDataSetChanged on the adapter.
in mListView.onRefresh(), i add an image to mValues.
This works smoothly for adding the first image, or even the first bunch of images (before calling mAdapter.notifyDataSetChanged()).
The refresh indicator shows and hides as intended.
The weird things start happening when i try to add another image (or bunch) after that.
The refresh indicator shows, the image is displayed in the list view.
BUT : the refresh indicator never hides again after that. "onRefreshComplete()" gets called, but seems not to work properly the second time.
The UI Thread is not blocking, so operation is still possible.
If i delete all items in mValues, notify the adapter and pull to refresh again, the image is added properly, and the refresh indicator is hidden properly.
Conclusion: The pull-to-refresh only hides properly if the list was empty before refreshing.
I really don't know where to look for a solution for this weird error.
Maybe someone familiar with the Pull-To-Refresh Library from Chirs Banes can help me out here.
Thank You !
I just figured it out myself -.-
For anyone interested:
You have to set onRefreshComplete from the UI Thread.
Use a Handler to .post it from inside onRefresh(). <- which by the way runs on a separate thread.
Have a nice day.
I've found 2 ways:
Dynamically, when you need pulltorefreshview to stop do task on pull up, you can set a custom AsyncTask, for example:
private class GetDataTask extends AsyncTask<Void, Void, String[]> {
#Override
protected String[] doInBackground(Void... params) {
return null;
}
#Override
protected void onPostExecute(String[] result) {
lv.onRefreshComplete();
showToast(getResources().getString(R.string.no_more));
super.onPostExecute(result);
}
}
Dynamically call setMode to the pulltorefreshView
ptrlv.setMode(Mode.Both); // both direction can be used
ptrlv.setMpde(Mode.PULL_FROM_START); // only pull down can be used.
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)