How to avoid boilerplate code when loading images with Picasso library - java

Let's say I have a layout with 4 ImageView's. Each ImageView must contain picture downloaded from remote url. Such task I can easily achieve with Picasso library like this:
Picasso.with(context)
.load(photo1Url)
.placeholder(R.drawable.image_view_placeholder)
.error(R.drawable.image_view_error_placeholder)
.centerCrop()
.tag(context)
.fit()
.into(feedListViewPhoto1);
Picasso.with(context)
.load(photo2Url)
.placeholder(R.drawable.image_view_placeholder)
.error(R.drawable.image_view_error_placeholder)
.centerCrop()
.tag(context)
.fit()
.into(feedListViewPhoto2);
Picasso.with(context)
.load(photo3Url)
.placeholder(R.drawable.image_view_placeholder)
.error(R.drawable.image_view_error_placeholder)
.fit()
.centerCrop()
.tag(context)
.fit()
.into(feedListViewPhoto3);
Picasso.with(context)
.load(photo4Url)
.placeholder(R.drawable.image_view_placeholder)
.error(R.drawable.image_view_error_placeholder)
.centerCrop()
.tag(context)
.fit()
.into(feedListViewPhoto4);
But maybe there is more compact solution to achieve such goal? eg.: to write only one time "Picasso with" and pass all required url's and ImageView object references? Maybe some kind of for cycle solution would help?

I strong recommend you to create a ImageLoader "service" inside your app.
First of all, create an Interface with the methods you need to download images and set into ImageView or to get bitmaps or features related:
public interface ImageService {
public void downloadAndSetImage(Context context, String url, ImageView image);
//Other important methods to you
}
With this, you can make implementatios with Picasso or other download systems. This is useful for example to change or test other systems without break your app.
After that create the implementation for the methods with all the boilerplate code.
public class PicassoImageImpl implements ImageService {
#Override
public void downloadAndSetImage(Context context, ImageDownloadInfo imageDownloadInfo) {
//All picasso code
}
}
Then in your application you only have to instanciate your ImageService (I strong recommend you to use DependencyInyection or at least a central Factory to get instances) and make simple calls to the method you need.

Related

Using Firebase Storage image with Glide

There are tons of duplicated answers I had tried almost all of them but I am still not able to use Firebase storage image with Glide.
First of all I am using docs
FirebaseStorage storage = FirebaseStorage.getInstance();
StorageReference storageRef = storage.getReference();
StorageReference pathReference = storageRef.child("sorular/1.jpg");
// ImageView in your Activity
ImageView imageView = rootView.findViewById(R.id.imageView);
// Load the image using Glide
Glide.with(this /* context */)
.using(new FirebaseImageLoader()) // Cannot resolve method 'using
.load(pathReference)
.into(imageView);
if I clean the .using part of Glide, logcat it gives this error:
E/GlideExecutor: Request threw uncaught throwable com.bumptech.glide.Registry$NoModelLoaderAvailableException: Failed to
find any ModelLoaders for model:
gs://123...appspot.com/sorular/1.jpg
at com.bumptech.glide.Registry.getModelLoaders(Registry.java:227)
at
com.bumptech.glide.load.engine.DecodeHelper.getLoadData(DecodeHelper.java:179)
at
com.bumptech.glide.load.engine.DecodeHelper.getCacheKeys(DecodeHelper.java:197)
at
com.bumptech.glide.load.engine.ResourceCacheGenerator.startNext(ResourceCacheGenerator.java:41)
at
com.bumptech.glide.load.engine.DecodeJob.runGenerators(DecodeJob.java:282)
at
com.bumptech.glide.load.engine.DecodeJob.runWrapped(DecodeJob.java:249)
at com.bumptech.glide.load.engine.DecodeJob.run(DecodeJob.java:222)
at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
at
com.bumptech.glide.load.engine.executor.GlideExecutor$DefaultThreadFactory$1.run(GlideExecutor.java:347)
So how can use firebase storage images in my android app in a best way?
also this my build gradle dependencies:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.0.2'
implementation 'com.android.support:palette-v7:27.0.2'
implementation "com.android.support:cardview-v7:27.0.2"
implementation "com.android.support:recyclerview-v7:27.0.2"
implementation "com.android.support:support-v4:27.0.2"
implementation 'com.android.support:design:27.0.2'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.github.florent37:materialviewpager:1.2.3'
implementation 'com.google.firebase:firebase-database:11.8.0'
implementation 'com.google.firebase:firebase-storage:11.8.0'
implementation 'com.firebaseui:firebase-ui-storage:2.0.1'
implementation 'com.google.firebase:firebase-auth:11.8.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
Change this:
implementation 'com.firebaseui:firebase-ui-storage:2.0.1'
to this:
implementation 'com.firebaseui:firebase-ui-storage:3.2.1'
According to the Glide docs:
using()
The using() API was removed in Glide 4 to encourage users to register their components once with a AppGlideModule to avoid object re-use. Rather than creating a new ModelLoader each time you load an image, you register it once in an AppGlideModule and let Glide inspect your model (the object you pass to load()) to figure out when to use your registered ModelLoader.
To make sure you only use your ModelLoader for certain models, implement handles() as shown above to inspect each model and return true only if your ModelLoader should be used.
using() was removed from Glide 4.
To Solve this, you need to do this:
To load an image from a StorageReference, first register an AppGlideModule:
#GlideModule
public class MyAppGlideModule extends AppGlideModule {
#Override
public void registerComponents(Context context, Glide glide, Registry registry) {
// Register FirebaseImageLoader to handle StorageReference
registry.append(StorageReference.class, InputStream.class,
new FirebaseImageLoader.Factory());
}
}
Once you have created an AppGlideModule class and done a clean build, you can use GlideApp to load a StorageReference into an ImageView:
// Reference to an image file in Cloud Storage
StorageReference storageReference = ...;
// ImageView in your Activity
ImageView imageView = ...;
// Download directly from StorageReference using Glide
// (See MyAppGlideModule for Loader registration)
GlideApp.with(this /* context */)
.load(storageReference)
.into(imageView);
more info here: https://github.com/firebase/FirebaseUI-Android/tree/master/storage
I Know im bit late but it might help some of you.
Use both of these in app build.gradle.
implementation 'com.github.bumptech.glide:glide:4.10.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0' //For Kotlin You should use kapt instead of annotationProcessor though.
Then add This Class:
#GlideModule
public class MyAppGlideModule extends AppGlideModule {
#Override
public void registerComponents(Context context, Glide glide, Registry registry) {
// Register FirebaseImageLoader to handle StorageReference
registry.append(StorageReference.class, InputStream.class,
new FirebaseImageLoader.Factory());
}
}
GlideApp.with(getActivity()).load(storageReference).into(profileImg);
At last you need to go to File-> Invalidate Cache and Restart
Done:)
If you've uploaded little images for icons on to your Firebase storage, get rid off glide and that "model". It makes a lot of changes on its git. So your code should look like:
StorageReference referenseLcl = FirebaseStorage.getInstance().getReference();
StorageReference islandRefLcl = referenseLcl.child(userLcl.getImageIconPath());
final long ONE_MEGABYTE = 1024 * 1024;
islandRefLcl.getBytes(ONE_MEGABYTE).addOnSuccessListener(bytesPrm -> {
Bitmap bmp = BitmapFactory.decodeByteArray(bytesPrm, 0, bytesPrm.length);
imageOfUser.setImageBitmap(bmp);
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
imageOfUser.setImageResource(R.mipmap.ic_launcher);
}
});
As for Glide 4.6.1 you can't use .using(new FirebaseImageLoader())
I am force to downgrade to
implementation 'com.github.bumptech.glide:glide:3.8.0'
and Firebase UI implementation'com.firebaseui:firebase-ui-storage:2.0.1'
The answers above didn't help me.
I was missing this in my gradle.
annotationProcessor 'com.github.bumptech.glide:compiler:4.x' //For Kotlin advice use kapt instead of annotationProcessor
The best docs I have found are here
I am using kotlin.
in my case, because I still use annotation processor like this
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
I should use:
kapt 'com.github.bumptech.glide:compiler:4.11.0'
Kotlin implementation of GlideModule :
#GlideModule
class GlideModule : AppGlideModule() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
registry.append(StorageReference::class.java, InputStream::class.java, FirebaseImageLoader.Factory())
}
}
Don't forget to import the correct dependencies :
dependencies {
def glide_version = "4.12.0"
implementation "com.github.bumptech.glide:glide:$glide_version"
kapt "com.github.bumptech.glide:compiler:$glide_version"
}

How to integrate Firebase with Glide ('using' method)

I'm trying to use Firebase integration with Glide and for some reason,
Glide.using() cannot resolve this method.
I did add:
compile 'com.firebaseui:firebase-ui-storage:0.6.0'
Into build.gradle and also:
compile 'com.github.bumptech.glide:glide:4.0.0-RC1'
Here is the part which I'm trying to use Glide:
mStorageRef = FirebaseStorage.getInstance().getReference();
mStorageRef.child("images/Puffer-fish-are-pretty-damn-cute.jpg");
// Load the image using Glide
Glide.with(this)
.using(new FirebaseImageLoader()) // cannot resolve method using!
.load(mStorageRef)
.into(imageView);
I hope you can help me with that, didn't find any solutions online.
To solve this, please change this line:
compile 'com.github.bumptech.glide:glide:4.0.0-RC1'
with
compile 'com.github.bumptech.glide:glide:3.7.0'
Glide v4 is using module loaders with the annotation processor library.
Create AppGlideModule and then register FirebaseImageLoader. When loading images use StorageReference.
Here it is in details.
Add libraries in gradle
implementation 'com.github.bumptech.glide:glide:4.7.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'
implementation 'com.firebaseui:firebase-ui-storage:4.1.0'
Extend the module and register
#GlideModule
public class MyAppGlideModule extends AppGlideModule {
#Override
public void registerComponents(#NonNull Context context, #NonNull Glide glide, #NonNull Registry registry) {
registry.append(StorageReference.class, InputStream.class, new FirebaseImageLoader.Factory());
}
}
Load images with ref
Uri uri = Uri.parse(photoUrl);
StorageReference ref = FirebaseStorage.getInstance().getReference().child(uri.getPath());
Glide.with(itemView.getContext())
.load(ref)
.into(thumb);

Issues with garbage collection and Picasso

I am trying to set an ImageView in a Google Maps Marker's InfoWindow and have copied the code from this answer pretty exactly, except that my InfoWindowAdapter isn't an anonymous inner class (it's just an inner class of the activity). This was working before, but for some reason it has stopped working - the onSuccess method in the Callback doesn't get called, so the InfoWindow only displays the image the second time it is opened.
Looking at the logs for Picasso I'm getting messages similar to Main canceled [R20]+374ms target got garbage collected. I figured this might be because the Callback is getting gc'd, and tried making it final, and also saving the object in a class field (neither of these worked, although maybe I was doing it wrong?)
What could be happening here, and how can I fix it? Is that target in the error message referring to the Callback, or could it be referring to the marker that gets passed as an argument to the Callback's constructor?
Another odd thing is that sometimes the images are loaded correctly when the InfoWindow is first opened - I'm trying to find out why, but basically I have a lot of markers and whether their images load correctly or not on the first go seems to be inconsistent. There are some (the majority) that never seem to load correctly when the InfoWindow is first opened.
[edit] This was after a bunch of code was merged into that activity, so could it be a memory thing? (there's more processing done now than there was when I wasn't having this problem)
[edit 2] I'm having exactly the same issue with Glide!! Probably garbage collection?
I'm not familiar with that answer, but Target could be gc'ed when you do not hold strong reference to that.
It's because Picasso holds Target instance with weak reference.
You should hold Target instance somewhere outside of Picasso.
Check this issue: https://github.com/square/picasso/issues/352
Solved it, the garbage collection message was actually referencing the ImageView, not the Callback object. Ensuring that the ImageView object isn't garbage collected will correct this (e.g. by saving the ImageView in a field in the class, or even the activity that my class was nested in)
I was doing the same mistake, here was the solution that worked :
My previous code :
picasso.load(url).into(new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
}
#Override
public void onBitmapFailed(Exception e, Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
});
After this I just created a new variable for Target object :
final Target target=new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
imageView.setBackground(new BitmapDrawable(mContext.getResources(), bitmap));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
}
#Override
public void onBitmapFailed(Exception e, Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
};
then I used the target object later in my code :
picasso.load(url).into( target);

How to use an API level 8 class when possible, and do nothing otherwise

I have a custom view in my Android App that uses a ScaleGestureDetector.SimpleOnScaleGestureListener to detect when the user wants to scale the view via a 2 finger pinch in or out. This works great until I tried testing on an Android 2.1 device, on which it crashes immediately because it cannot find the class.
I'd like to support the pinch zoom on 2.2+ and default to no pinch zoom <2.1. The problem is I can't declare my:
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
{
#Override
public boolean onScale(ScaleGestureDetector detector)
{
MyGameActivity.mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
MyGameActivity.mScaleFactor = Math.max(0.1f, Math.min(MyGameActivity.mScaleFactor, 1.0f));
invalidate();
return true;
}
}
at all on android <2.1. Is there a clever way to get around this using java reflection? I know how to test if a method exists via reflection, but can I test if a class exists? How can I have my
private ScaleGestureDetector mScaleDetector;
declared at the top of my class if the API can't even see the ScaleGestureDetector class?
It seems like I should be able to declare some sort of inner interface like:
public interface CustomScaleDetector
{
public void onScale(Object o);
}
And then somehow...try to extend ScaleGestureDetector depending on if reflection tells you that the class is accessible...
But I'm not really sure where to go from there. Any guidance or example code would be appreciated, let me know if it isn't clear what I'm asking.
well, you can get ScaleGestureDetector source code and put in your project, make it as your own class, and use this to instead of system ScaleGestureDetector, this can avoid the android 2.2 and below version.
here is the ScaleGestureDetector source code: http://pastebin.com/r5V95SKe
Create two classes A and B. A for 2.2+ and B for 2.1 or earlier versions.
if(Build.VERSION.SDK_INT < 8) //2.2 version is 8
{
Use the class B
}
else
{
Use class A
}
Build.VERSION.RELEASE
use the above line to get the OS version of the device.
And you can have simple if else case. just check if the OS version is above 2.2 the you can have zoom in zoom out. else make it no zoom in or zoom out.

Android - Java - Access private variable in Main-Activity from another instanced class

I've been thinking for hours about this problem now without finding a solution. Still I think I'm just overlooking some very simple things.
My program is (mainly) supposed to record and playback audio, so I got one Main-Activity which contains all the functions with interactivity to the user and I got one class I called 'AudioHandler' to manage all the audio stuff in the background.
The Main-Activity uses an instance of AudioHandler.
Within the AudioHandler-class I got an OnCompletionListener, to notice when playback of the recorded audio-file has finished.
The problem is: When playback has finished - and the OnCompletionListener gets called, I want to change an ImageView (Turn the pause-button into a play-button) in the Main-Activity.
How can I access the ImageView in the Main-Activity from the AudioHandler-Instance?
...without making the ImageView-Variable public.
Method that calls play from the AudioHandler in the Main-Activity:
public State playRecording() {
//change play-button to stop-button
iPlay.setImageResource(R.drawable.stop_button_active);
//start or resume playback of the recorded Audio
ah.play();
//Return that the program is in playing-mode
return State.PLAYING;
}
play-function in AudioHandler
public void play() {
try {
mPlayer.start();
} catch (IllegalArgumentException e) {
Log.d("MEDIA_PLAYER", e.getMessage());
e.printStackTrace();
} catch (IllegalStateException e) {
Log.d("MEDIA_PLAYER", e.getMessage());
e.printStackTrace();
}
//add listener to notice when the end of the audio record has been reached
mPlayer.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
//should somehow change the ImageView 'iPlay' in the Main-Activity when reaching this point.
}
});
}
Any help is appreciated :)
Marco
If you need to gain access to a variable, that variable should be either package-accessible or public. Or, even better, you should have an encapsulating method. Something like:
// On the main class:
public ImageView getIPlay(){
return iPlay;
}
private static MainActivity instance;
public MainActivity getInstance(){
return instance;
}
/** Either on the constructor or the 'OnCreate' method, you should add: */
instance = this;
Now you can access the ImageView from any Activity or class with:
MainActivity.getInstance().getIPlay();
It's that simple, really. You can go through a lot of other solutions, from using reflection through AOP or any kind of things. But it's just making the issue more complex. Try creating the setter method.
Your question is about object encapsulation.
When you define an instance variable as private, you are saying that only that object and members of that class can access the variable. This is a good thing because it protects you from accidentally changing data you did not want to. This is also why we have getters and setters.
So in your class you have something I imagine looks like this
public class Main {
... some code here
private ImageView myView;
... more code here
what you need to do is add the following methods to your Main class
public ImageView getView() {
return myView;
}
and
public void setView(ImageView a) {
myView = a;
}
so now when you want to access the ImageView from another class, you just call something like Main.getView() and you'll have it.
Now if this doesn't answer your question, it's because you didn't design your app too thoughtfully (so it seems) but luckily I won't be the kind of responder who tells you to uproot everything you have done and start over.
It sounds like you're working with an Android app and your issue is to communicate between activities.
This is why Android has something called Intents. You can send an Intent from one activity to another to carry data. So in this case you would send an Intent from one activity to the main class that would tell the main class to change the picture in the imageview. I won't lie, I suck at using intents but there are many wonderful tutorials online that will show you how to do this.
Another option would be to create a RemoteView of your layout that has the play/pause button and access it from the other activity, RemoteView has a built in function to change the bitmap of a remote imageview (again, see the Android Developer Documentation for RemoteView for more on this)
I'm sorry I couldn't give you some actual code, I'm not confident enough in my abilities on these topics but I'm 100% confident that one of the three methods I just listed will answer your question.
You could create a public wrapper method within your main Activity that toggles the ImageView between Pause and Play.
Or, you could pass the the ImageView to the AudioHandler (via the constructor?) and just manipulate it there.

Categories