I'm having trouble trying to use Volley's NetworkImageView with an InfoWindow of Google Maps API v2.
My markers shows an InfoWindow with an image fetched from the internet. As you know, the only way to do this is to show the InfoWindow and refresh it when finished downloading the image.
So I need a way to know when the NetworkImageView have finished downloading the image so I can refresh that view.
I'm looking for something like onLoadComplete
Any ideas?
Modify NetworkImageView
public void setImageUrl(String url, ImageLoader imageLoader, ImageListener myListener) {
mUrl = url;
mImageLoader = imageLoader;
// The URL has potentially changed. See if we need to load it.
loadImageIfNecessary(false, myListener);
}
/** #param myListener notified when image has loaded or on error */
private void loadImageIfNecessary(final boolean isInLayoutPass, final ImageListener myListener) {
...
ImageContainer newContainer = mImageLoader.get(mUrl,
new ImageListener() {
#Override
public void onErrorResponse(VolleyError error) {
if (myListener != null) {
myListener.onErrorResponse(error);
}
if (mErrorImageId != 0) {
setImageResource(mErrorImageId);
}
}
...
#Override
public void onResponse(final ImageContainer response, boolean isImmediate) {
...
if (response.getBitmap() != null) {
if (myListener != null) {
myListener.onResponse(response, false); // pay attention to boolean isImmediate param
}
setImageBitmap(response.getBitmap());
...
}
}
}
...
Now you can use it in your Adapter or any other code:
public void bind(Item item) {
imageView.setImageUrl(imageUrl, app.getImageLoader(), new ImageListener() {
#Override
public void onResponse(ImageContainer response, boolean isImmediate) {
// isImmediate == true - taken from cache
// isImmediate == false - successfully loaded from server
}
#Override
public void onErrorResponse(VolleyError error) { }
});
}
I created a a custom NetworkImageView as oleksandr_yefremow suggested and got it working although the cache is not working by some reason.
public View getInfoWindow(final Marker marker) {
final CustomImageView imageView = ...
imageView.setImageUrl(imageUrl, app.getImageLoader(), new ImageListener() {
#Override
public void onResponse(ImageContainer response, boolean isImmediate) {
imageView.setImageBitmap(response.getBitmap());
marker.showInfoWindow();
// isImmediate == true - taken from cache
// isImmediate == false - successfully loaded from server
}
#Override
public void onErrorResponse(VolleyError error) { }
});
}
Related
I am working on the project to create the stories like whatsapp or instagram.
we are using the library from Github
Our image are loaded from firebase.
but the problem is that stories start with loading the image because image size is large.
#Override
public void onFirebaseLoadSuccess(final List<Movie> movieList) {
storiesProgressView.setStoriesCount(movieList.size());
storiesProgressView.setStoryDuration(1500L);
Picasso.get().load(movieList.get(counter).getImage()).into(imageView, new Callback() {
#Override
public void onSuccess() {
progressBar.setVisibility(View.GONE);
storiesProgressView.startStories();
}
#Override
public void onError(Exception e) {
}
});
storiesProgressView.setStoriesListener(new StoriesProgressView.StoriesListener() {
#Override
public void onNext() {
if(counter < movieList.size()){
counter++;
Picasso.get().load(movieList.get(counter).getImage()).into(imageView);
}
}
#Override
public void onPrev() {
if(counter > 0){
counter--;
Picasso.get().load(movieList.get(counter).getImage()).into(imageView);
}
}
#Override
public void onComplete() {
counter = 0;
Toast.makeText(MainActivity.this,"Completed!",Toast.LENGTH_LONG).show();
finish();
}
});
}
Picasso doesn't has a function to check when load started so i highly recommend you to use Glide instead of Picasso.
Here, you can show progress bar when image loading is started via onLoadStarted() function :
Glide.with(this).load(movieList.get(counter).getImage()).into(new Target<GlideDrawable>() {
#Override
public void onStart() {
}
#Override
public void onStop() {
}
#Override
public void onDestroy() {
}
#Override
public void onLoadStarted(Drawable placeholder) {
progressBar.setVisibility(View.VISIBLE);
}
#Override
public void onLoadFailed(Exception e, Drawable errorDrawable) {
}
#Override
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
progressBar.setVisibility(View.GONE);
imageView.setImageDrawable(resource);
storiesProgressView.startStories();
}
#Override
public void onLoadCleared(Drawable placeholder) {
}
#Override
public void getSize(SizeReadyCallback cb) {
}
#Override
public void setRequest(Request request) {
}
#Override
public Request getRequest() {
return null;
}
});
I am using Glide v4 to load a bitmap that can then be used to as a marker on the map. When I use the deprecated SimpleTarget like so everything works fine.
GlideApp.with(getContext()).asBitmap().load(url)
.into(new SimpleTarget<Bitmap>() {
#Override
public void onResourceReady(#NonNull Bitmap resource, #Nullable Transition<? super Bitmap> transition) {
// load bitmap as marker
}
});
When I try removing the deprecated code and using Target<Bitmap> like given below I can see the onLoadStarted gets called but the onResourceReady is never called neither is the onLoadFailed.
GlideApp.with(getContext()).asBitmap()
.load(UrlHelper.createUrl(poi.getMapMarker()))
.into(marketBitmap);
private Target<Bitmap> marketBitmap = new Target<Bitmap>() {
#Override
public void onLoadStarted(#Nullable Drawable placeholder) {
Log.d("GlideMar", "marker load started");
}
#Override
public void onLoadFailed(#Nullable Drawable errorDrawable) {
Log.e("GlideMar", "marker load failed");
}
#Override
public void onResourceReady(#NonNull Bitmap resource, #Nullable Transition<? super Bitmap> transition) {
Log.d("GlideMar", "onResourceReady");
}
#Override
public void onLoadCleared(#Nullable Drawable placeholder) {
Log.d("GlideMar", "marker onLoadCleared");
}
#Override
public void getSize(#NonNull SizeReadyCallback cb) {
}
#Override
public void removeCallback(#NonNull SizeReadyCallback cb) {
}
#Override
public void setRequest(#Nullable Request request) {
}
#Nullable
#Override
public Request getRequest() {
return null;
}
#Override
public void onStart() {
Log.d("GlideMar", "marker onStart");
}
#Override
public void onStop() {
Log.d("GlideMar", "marker onStop");
}
#Override
public void onDestroy() {
Log.d("GlideMar", "marker onDestroy");
}
};
From Glide Custom Targets documentation.
If you’re using a custom Target and you’re not loading into a View
that would allow you to subclass ViewTarget, you’ll need to implement
the getSize() method.
So in your case just put the below code in getSize method
#Override
public void getSize(SizeReadyCallback cb) {
cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
}
Now the onResourceReady method will be called when you run the app.
I am using MVP to decouple my view and model in my android application. I need to know how the model should feedback the result of the business logic to the view.
If for example a button is pressed to login, the activity would be like this, using butterknife #OnClick annotation:
#OnClick(R.id.login_button)
public void login() {
String email = mEmailEditText.getText().toString();
String password = mPasswordEditText.getText().toString();
LoginCredentials loginCredentials = new LoginCredentials(email, password);
mPresenter.loginWithEmail(loginCredentials);
}
The presenter would then validate and make a request to the repository:
public void loginWithEmail(LoginCredentials loginCredentials) {
boolean isEmailValid = AuthValidator.validateEmail(loginCredentials.getUsername());
boolean isPasswordValid = AuthValidator.validatePassword(loginCredentials.getPassword());
if(isEmailValid && isPasswordValid) repository.loginEmailUser(loginCredentials);
if (!isEmailValid) view.handleInvalidEmail();
if (!isPasswordValid) view.handleInvalidPassword();
}
The repository would then execute the business logic:
#Override
public void loginEmailUser(LoginCredentials loginCredentials) {
Call<Token> call = userServiceApi.loginInToken(loginCredentials);
call.enqueue(new Callback<Token>() {
#Override
public void onResponse(#NonNull Call<Token> call, #NonNull Response<Token> response) {
if (response.isSuccessful()) {
// handle successful login
} else {
// Handle unsuccessful login
}
}
#Override
public void onFailure(#NonNull Call<Token> call, #NonNull Throwable t) {
// Handle failed request
}
});
Where the comments say // handle unsuccessful something, how does the model feedback to the view the outcomes of the business logic so that the view can handle these outcomes?
Thank you.
You can use a interface as callback, for example :
public interface RepositoryCallback {
void onLoginEmailUserSuccess(/*paramaters if you need*/);
void onLoginEmailUserError(/*paramaters if you need*/);
void onRequestFailed(/*paramaters if you need*/)
}
In the repository defined the listener
public class MyRepository {
private RepositoryCallback mListener;
#Override
public void loginEmailUser(LoginCredentials loginCredentials) {
Call<Token> call = userServiceApi.loginInToken(loginCredentials);
call.enqueue(new Callback<Token>() {
#Override
public void onResponse(#NonNull Call<Token> call, #NonNull Response<Token> response) {
if (response.isSuccessful()) {
// handle successful login
if (mListener != null) {
mListener.onLoginEmailUserSuccess()
}
} else {
// Handle unsuccessful login
if (mListener != null) {
mListener.onLoginEmailUserError()
}
}
}
#Override
public void onFailure(#NonNull Call<Token> call, #NonNull Throwable t) {
// Handle failed request
if (mListener != null) {
mListener.onRequestFailed()
}
}
});
public void setRepositoryCallback(RepositoryCallback listener) {
mListener = listener;
}
}
Then set the presenter as listener :
public class MyPresenter implements RepositoryCallback {
public void loginWithEmail(LoginCredentials loginCredentials) {
repository.setRepositoryCallback(this) // here or in the presenter constructor
boolean isEmailValid = AuthValidator.validateEmail(loginCredentials.getUsername());
boolean isPasswordValid = AuthValidator.validatePassword(loginCredentials.getPassword());
if(isEmailValid && isPasswordValid) repository.loginEmailUser(loginCredentials);
if (!isEmailValid) view.handleInvalidEmail();
if (!isPasswordValid) view.handleInvalidPassword();
}
#Override
public void onLoginEmailUserSuccess(//paramaters if you need){
// your code
}
#Override
public void onLoginEmailUserError(//paramaters if you need){
// your code
}
#Override
public void onRequestFailed(//paramaters if you need){
// your code
}
}
Hope this helps.
Sorry for my english.
I have an Activity in which I load bitmaps in a ScrollGalleryView using Picasso.
When I exit from that activity and enter again the memory is not emptied and an OutOfMemory error is thrown.
I tried using .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE) in Picasso but the images are still loaded and kept in memory.
I also tried to recycle the bitmaps when the back button is pressed but I still had no luck.
This is the code i'm using:
//Method called in the onCreate that loads the photo in the scrollGalleryView:
private void loadPhotos() {
savedImages = new ArrayList<Uri>();
File file = new File(getPhotoDirectory());
File[] files = file.listFiles();
if (files != null) {
for (File f : files) { // loop and print all file
savedImages.add(Uri.fromFile(f));
}
}
if (!savedImages.isEmpty()) {
for (final Uri savedImage : savedImages) {
if (savedImage.getLastPathSegment().contains(radiatorId)) {
scrollGalleryView.setVisibility(View.VISIBLE);
RadiatorSettingsMediaLoader mMediaLoader = new RadiatorSettingsMediaLoader(savedImage);
scrollGalleryView.addMedia(MediaInfo.mediaLoader(mMediaLoader));
}
}
}
}
class RadiatorSettingsMediaLoader implements MediaLoader {
Uri savedImage;
public RadiatorSettingsMediaLoader(Uri savedImage) {
this.savedImage = savedImage;
}
#Override
public boolean isImage() {
return true;
}
#Override
public void loadMedia(final Context context, final ImageView imageView,
final MediaLoader.SuccessCallback callback) {
imageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public ViewTreeObserver.OnGlobalLayoutListener getLayoutListener() {
return this;
}
#Override
public void onGlobalLayout() {
Picasso.with(getApplicationContext()).load(savedImage)
.resize(imageView.getWidth(), (imageView.getHeight()) - 175)
.centerInside()
.placeholder(imageView.getDrawable())
.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
.into(imageView, new Callback() {
#Override
public void onSuccess() {
callback.onSuccess();
imageView.setPadding(0, 0, 0, 175);
imageView.getViewTreeObserver().removeOnGlobalLayoutListener(getLayoutListener());
Picasso.with(getApplicationContext()).invalidate(new File(savedImage.getPath()));
}
#Override
public void onError() {
Toast.makeText(context, "non sono riuscito a caricare l'immagine", Toast.LENGTH_SHORT).show();
imageView.getViewTreeObserver().removeOnGlobalLayoutListener(getLayoutListener());
loadMedia(context, imageView, callback);
}
});
imageViewsToClear.add(imageView);
}
});
}
#Override
public void loadThumbnail(final Context context, final ImageView thumbnailView,
final MediaLoader.SuccessCallback callback) {
thumbnailView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public ViewTreeObserver.OnGlobalLayoutListener getLayoutListener() {
return this;
}
#Override
public void onGlobalLayout() {
Picasso.with(context)
.load(savedImage)
.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
.into(thumbnailView, new Callback() {
#Override
public void onSuccess() {
callback.onSuccess();
thumbnailView.setLongClickable(true);
thumbnailView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(RadiatorSettingsActivity.this);
builder.setTitle(R.string.safe_delete_photo_title).setMessage(R.string.safe_delete_photo_text);
builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
File fdelete = new File(savedImage.getPath());
if (fdelete.exists()) {
if (fdelete.delete()) {
deleteFileFromMediaStore(getContentResolver(), fdelete);
System.out.println("file Deleted ");
finish();
startActivity(getIntent().putExtra("PhotoRemoved", true));
} else {
System.out.println("file not Deleted :");
}
}
}
});
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
return true;
}
});
thumbnailView.getViewTreeObserver()
.removeOnGlobalLayoutListener(getLayoutListener());
}
#Override
public void onError() {
Toast.makeText(context, "errore a caricare thumbnail", Toast.LENGTH_SHORT).show();
loadThumbnail(context, thumbnailView, callback);
thumbnailView.getViewTreeObserver()
.removeOnGlobalLayoutListener(getLayoutListener());
}
});
imageViewsToClear.add(thumbnailView);
}
});
}
}
Call this in your program
public void clearAllResources() {
// Set related variables null
System.gc();
Runtime.getRuntime().gc();
}
that is clearAllResources(); on start of activity
Has anyone used Glide to fetch images from a background thread? I keep getting this assert:
java.lang.IllegalArgumentException: You must call this method on the main thread
but according to this thread, it should work:
https://github.com/bumptech/glide/issues/310
Yet, I cannot get it to work, unless I call it from the main thread.
Here's is what I am trying to do from the main thread:
Glide.get(mContext);
loadUserImage(userImageUrl);
// wait 5 seconds before trying again
int imageLoadingTimeOut = mContext.getResources().getInteger(R.integer.image_loading_time_out);
if (imageLoadingTimeOut > 0) {
new Timer().schedule(new TimerTask() {
#Override
public void run() {
if (!mUserImageLoaded) {
loadUserImage(userImageUrl);
}
}
}, imageLoadingTimeOut);
}
}
and the loadUserImage:
private boolean mUserImageLoaded = false;
private void loadUserImage(String userImageUrl) {
if (userImageUrl != null && !userImageUrl.isEmpty() && !mUserImageLoaded) {
Glide.with(mContext).using(Cloudinary.getUrlLoader(mContext)).load(userImageUrl).crossFade().listener(new RequestListener<String, GlideDrawable>() {
#Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
mImageMessageContent.invalidate();
mUserImageLoaded = true;
return false;
}
}).into(mImageMessageContent);
} else {
mImageMessageContent.setVisibility(View.GONE);
}
}
and mContext is just the activity "this" pointer.
Anyway, can I use Glide from a thread different than main?
Update image in main ui thread
runOnUiThread(new Runnable() {
#Override
public void run() {
Glide.with(MainActivity.this)
.load("image URL")
.into(imageView);
}
});
The into(ImageView) method of Glide requires you to call it only on main thread, but when you pass the loading to a Timer it will be executed in a background thread.
What you can do is to retrieve a bitmap by calling get() instead of into() and then set that bitmap on the ImageView by calling setImageBitmap().
Glide.with(getApplicationContext())
.load("your url")
.asBitmap()
.into(new BitmapImageViewTarget(imgView) {
#Override
protected void setResource(Bitmap resource) {
//Play with bitmap
super.setResource(resource);
}
});
You can also take a look at this document for more information.
Posting the code just in case it helps someone.
Bitmap myBitmap = Glide.with(applicationContext)
.load(yourUrl)
.asBitmap()
.centerCrop()
.into(Target.SIZE_ORIGINAL,Target.SIZE_ORIGINAL)
.get()
imageView.setImageBitmap(myBitmap);
Here is the Kotlin-way solution
Glide.with(context).asBitmap().load(photo_url).into(
BitmapImageViewTarget(imgYourResourceID)
)
In my case i want to show notification from FirebaseMessagingService with image which will be downloaded via Glide, what gave me success is this piece of code:
try {
Bitmap bitmap= Glide.with(getApplicationContext())
.load(imageUrl) // image url in string
.asBitmap().into(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).get();
// now i can pass bitmap to notificationBuilder like
notificationBuilder.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(bitmap));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
I need to decrypt and display large images in a recyclerView
And because it was a heavy process, I had to do it in the background, so that's why :
public void decryptImage(Uri uri, ViewHolder holder) {
Runnable runnable = new Runnable() {
#Override
public void run() {
try {
Glide.with(G.context)
.asBitmap()
.load(decrypt(G.getEncryptionKey(), G.getConstantKey(), uri.getPath()))
.into(new SimpleTarget<Bitmap>() {
#Override
public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
Glide.with(G.context).load(resource).into(holder.mImageView);
}
});
}
};
new Thread(runnable).start();
}
private class AsyncTaskRunner extends AsyncTask<String, String, RequestBuilder>
{
ProgressDialog progressDialog;
#Override
protected void onPreExecute() {
progressDialog = ProgressDialog.show(MainActivity.this,
"Please Wait",
"Image is loading...");
}
#Override
protected RequestBuilder<Drawable> doInBackground(String... strings) {
URL url = null;
try {
url = new URL(strings[0]);
} catch (MalformedURLException e) {
e.printStackTrace();
}
RequestBuilder<Drawable> g= Glide.with(MainActivity.this).load(url);
return g;
}
#Override
protected void onPostExecute(RequestBuilder v) {
v.into(imageView);
imageView.setVisibility(View.VISIBLE);
progressDialog.dismiss();
}
}