How to test if an image is fully loaded with Picasso - android

Picasso is asynchronous, so i was wondering if there is any way i can test if an image is fully loaded, before executing any additional code?
Picasso.with(context).load(imageURI).into(ImageView);
// image fully loaded? do something else ..

If the image is fully loaded it will be set on the ImageView synchronously.
You can use the callback to assert this.
final AtomicBoolean loaded = new AtomicBoolean();
Picasso.with(context).load(imageURI).into(imageView, new Callback.EmptyCallback() {
#Override public void onSuccess() {
loaded.set(true);
}
});
if (loaded.get()) {
// The image was immediately available.
}

Using overloaded method .into(ImageView target, Callback callback) is appropriate for your case. You can use the base implementation or extend your own like
Base:
Picasso.with(context).load(url).into(target, new Callback(){
#Override
public void onSuccess() {
}
#Override
public void onError() {
}
});
Extended version:
package main.java.app.picasso.test;
/**
* Created by nikola on 9/9/14.
*/
public abstract class TargetCallback implements Callback {
private ImageView mTarget;
public abstract void onSuccess(ImageView target);
public abstract void onError(ImageView target);
public TargetCallback(ImageView imageView){
mTarget = imageView;
}
#Override
public void onSuccess() {
onSuccess(mTarget);
}
#Override
public void onError() {
onError(mTarget);
}
}
Usage:
Picasso.with(context).load(url).into(target, new TargetCallback(target) {
#Override
public void onSuccess(ImageView target) {
}
#Override
public void onError(ImageView target) {
}
});

Related

Android MVP presenter unit test with Mockito causes "Wanted but not invoked" error

I know it was asked before, but i am currently diving into testing and i have the struggle to unit test presenter in MVP pattern with Mockito
My code setup:
Item class
public class ItemJSON {
#SerializedName("title")
String textHolder;
#SerializedName("id")
int factNumber;
public ItemJSON(String factText, int factNumber) {
this.textHolder = factText;
this.factNumber = factNumber;
}
//getters and setters
}
Contractor:
public interface Contractor {
interface Presenter {
void getPosts();
}
interface View {
//parse data to recyclerview on Succesfull call.
void parseDataToRecyclerView(List<ItemJSON> listCall);
void onResponseFailure(Throwable throwable);
}
interface Interactor {
interface onGetPostsListener {
void onSuccessGetPostCall(List<ItemJSON> listCall);
void onFailure(Throwable t);
}
void getPosts(onGetPostsListener onGetPostsListener);
}
}
API class:
#GET("posts")
Call<List<ItemJSON>> getPost();
Interactor class:
public class InteractorImpl implements Contractor.Interactor{
#Override
public void getPosts(onGetPostsListener onGetPostsListener) {
// NetworkService responsible for seting up Retrofit2
NetworkService.getInstance().getJSONApi().getPost().enqueue(new Callback<List<ItemJSON>> () {
#Override
public void onResponse(#NonNull Call<List<ItemJSON>> call, #NonNull Response<List<ItemJSON>> response) {
Log.d("OPERATION #GET","CALLBACK SUCCESSFUL");
onGetPostsListener.onSuccessGetPostCall (response.body ());
}
#Override
public void onFailure(#NonNull Call<List<ItemJSON>>call, #NonNull Throwable t) {
Log.d("OPERATION #GET","CALLBACK FAILURE");
onGetPostsListener.onFailure (t);
}
});
}
Presenter class:
public class PresenterImpl implements Contractor.Presenter, Contractor.Interactor.onGetPostsListener {
private final Contractor.View view;
private final Contractor.Interactor interactor;
public PresenterImpl (Contractor.View view,Contractor.Interactor interactor){
this.view = view;
this.interactor = interactor;
}
#Override
public void getPosts() {
interactor.getPosts (this);
}
#Override
public void onSuccessGetPostCall(List<ItemJSON> listCall) {
view.parseDataToRecyclerView (listCall);
}
}
So i try to ran some unit test on presenter, but they constanlty fail and i keep getting next error
Wanted but not invoked Actually, there were zero interactions with this mock
Unit test class:
#RunWith (MockitoJUnitRunner.class)
public class ApiMockTest{
#Mock
Contractor.View view;
private PresenterImpl presenter;
#Captor
ArgumentCaptor<List<ItemJSON>> jsons;
#Before
public void setUp() {
MockitoAnnotations.openMocks (this);
presenter = new PresenterImpl (view,new InteractorImpl ());
}
#Test
public void loadPost() {
presenter.getPosts ();
verify(view).parseDataToRecyclerView (jsons.capture ());
Assert.assertEquals (2, jsons.capture ().size ());
}
}
I try to understand what i am doing wrong and how to fix this issue, but as for now i am ran out of ideas. I will aprecciate any help.
Thanks in the adavance
UPD: in all cases in main activity presenter get called in onClick
Main Activity class:
public class MainActivity extends AppCompatActivity implements Contractor.View {
public Contractor.Presenter presenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
presenter = new PresenterImpl (this,new InteractorImpl ());
binding.getButton.setOnClickListener(view ->presenter.getPosts () );
...//code
#Override
public void parseDataToRecyclerView(List<ItemJSON> listCall) {
adapter.updateList(listCall); //diff call to put data into recyclerview adapter
}
}
}
I ran into this situation also, even using the mockk library. The problem is that your method is an interface method. You need to actually call it from a view which has implemented this interface.

The model layer (MVP) should use the Activity reference

How can I make a proper separation between the Model layer and the View layer, when I have an operation in the Model that needs the current activity instance?
For example, I've integrated Linkedin SDK in my Android app (written in MVP).
In the auth process I have the following code snippet, when init() method's first argument type is Activity:
public void authWithLinkedin(final IAuth listener, Activity activity) {
LISessionManager.getInstance(MyApplication.getContext()).init(activity, buildScope(), new AuthListener() {
#Override
public void onAuthSuccess() {
listener.onSuccess();
}
#Override
public void onAuthError(LIAuthError error) {
listener.onError();
}
}, true);
}
If my Model layer should get to know Android framework components, what options do I have left to preserve the MVP architecture clean?
You can use software conventions / principles like
"dependency inversion principle"
"ports and adapters"
Your model layer should not know about Android if you can avoid it is the point.
Try something like this:
Model:
private final SocialLoginProvider socialLoginProvider;
public MyModel(SocialLoginProvider socialLoginProvider) {
this.socialLoginProvider = socialLoginProvider;
}
public void authWithLinkedin(final IAuth listener) {
socialLoginProvider.init(buildScope(), new SocialLoginProvider.Listener() {
#Override
public void onAuthSuccess() {
listener.onSuccess();
}
#Override
public void onAuthError() {
listener.onError();
}
}, true);
}
Factory:
public MyModel getModel(Context context) {
LISessionManager li = LISessionManager.getInstance(context);
SocialLoginProvider provider = new LinkedInSocialLoginProvider(context, li);
return new MyModel(provider);
}
Interface:
public interface SocialLoginProvider {
void init(Scope scope, Listener listener);
interface Listener {
void onAuthSuccess();
void onAuthError();
}
}
Adapter for SocialLoginProvider:
public class LinkedInSocialLoginProvider implements SocialLoginProvider {
private final Context context;
private final LISessionManager linkedInSessionManager;
public LinkedInSocialLoginProvider(Context context, LISessionManager linkedInSessionManager) {
this.context = context;
this.linkedInSessionManager = linkedInSessionManager;
}
#Override
public void init(Scope scope, Listener listener) {
linkedInSessionManager.init(context, scope,
new AuthListener() {
#Override
public void onAuthSuccess() {
listener.onSuccess();
}
#Override
public void onAuthError(LIAuthError error) {
listener.onError();
}
}, true);
}
}
Ideally it is ok to have Android Framework components in the Model layer. For example you will need the Context to store/access data locally using getDefaultSharedPreferences(Context) and/or to manage local DB using SQLiteOpenHelper.
The LISessionManager.getInstance(MyApplication.getContext()).init seems to be like a BroadcastReceiver as it is a type of listener that receives a particular result from an outside component. To handle such a case you can refer to this

Android - library module's interface method is not called in app module

I am using Android Image Slider library for showing images on a slider. However, some images are not loading because backend requires authentication. So I need a listener for not loading images.
This is library: inside abstract BaseSliderView class, there is ImageLoadListener interface. I am setting listener using setOnImageLoadListener method.
public abstract class BaseSliderView {
.....
private ImageLoadListener mLoadListener;
.....
protected void bindEventAndShow(final View v, ImageView targetImageView){
....
rq.into(targetImageView,new Callback() {
#Override
public void onSuccess() {
if(v.findViewById(R.id.loading_bar) != null){
v.findViewById(R.id.loading_bar).setVisibility(View.INVISIBLE);
}
}
#Override
public void onError() {
if(mLoadListener != null){
mLoadListener.onEnd(false,me);
}
if(v.findViewById(R.id.loading_bar) != null){
v.findViewById(R.id.loading_bar).setVisibility(View.INVISIBLE);
}
}
});
}
/**
* set a listener to get a message , if load error.
* #param l
*/
public void setOnImageLoadListener(ImageLoadListener l){
mLoadListener = l;
}
.....
public interface ImageLoadListener{
void onStart(BaseSliderView target);
void onEnd(boolean result,BaseSliderView target);
}
.....
}
I checked, when image is not loaded, interface onEnd method is called in library module.
But on app module, onEnd method is not called even in library module it is called.
Why is this happening? Should not onEnd method be called in app module? How to solve this problem?
I could solve this problem using greenrobot's EventBus library. First of all, I have added library dependency to library build.gradle file:
compile 'org.greenrobot:eventbus:3.0.0'
Created class for event:
public class ImageLoadErrorEvent {
String url;
ImageView imageView;
public ImageLoadErrorEvent(String url, ImageView imageView) {
this.url = url;
this.imageView = imageView;
}
public String getUrl() {
return url;
}
public ImageView getImageView() {
return imageView;
}
}
Posted on BaseSliderView class:
#Override
public void onError() {
if(mLoadListener != null){
mLoadListener.onEnd(false,me);
EventBus.getDefault().post(new ImageLoadErrorEvent(mUrl, targetImageView));
}
if(v.findViewById(R.id.loading_bar) != null){
v.findViewById(R.id.loading_bar).setVisibility(View.INVISIBLE);
}
}
In Activity, inside onCreate method, registered EventBus:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user);
EventBus.getDefault().register(this);
Then created onMessageEvent:
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(ImageLoadErrorEvent event) {
MyToast.show("Error");
}
Yay, now it is working!

Call a method in the calling class on API success in android

I use the following class to make an API call in android using Retrofit
public Class Checkin {
public static void checkinViaApi(CheckinSendModel checkinSendModel) {
final ApiHandler apiHandler = new ApiHandler();
apiHandler.setApiResponseListener(new ApiResponseListener() {
#Override
public void onApiResponse(ApiResponseModel apiResponse) {
Log.i("CheckedIn","true");
}
#Override
public void onApiException(Error error) {
Log.i("fail",error.getErrorMessage());
}
});
List<CheckinSendModel> checkinSendModelList = new ArrayList<CheckinSendModel>();
checkinSendModelList.add(checkinSendModel);
Call<ApiResponseModel> request = RetrofitRestClient.getInstance().checkinToMainEvent(checkinSendModelList,Constant.API_KEY);
apiHandler.getData(request);
}
}
I call that method as follows:
Checkin.checkinViaApi(checkinSendModelObject);
Now, when the API call is successful, I want to execute a function checkedInSuccessfully() in the class from where I make the call. How can I do it?
Thanks in advance
Pass in the response interface.
public class Checkin {
public static void checkinViaApi(CheckinSendModel checkinSendModel, ApiResponseListener listener) {
final ApiHandler apiHandler = new ApiHandler();
apiHandler.setApiResponseListener(listener);
Other class - Call that method
CheckinSendModel model;
Checkin.checkinViaApi(model, new ApiResponseListener() {
#Override
public void onApiResponse(ApiResponseModel apiResponse) {
Log.i("CheckedIn","true");
checkedInSuccessfully();
}
#Override
public void onApiException(Error error) {
Log.i("fail",error.getErrorMessage());
}
);
Interface is your handy man. Create an interface like below.
Interface CheckInListener {
void onCheckIn();
}
Change the checkinViaApi() to below signature.
public static void checkinViaApi(CheckinSendModel checkinSendModel, CheckinListener listener) {
#Override
public void onApiResponse(ApiResponseModel apiResponse) {
Log.i("CheckedIn","true");
listener.onCheckIn();
}
}
When you call the above function you can provide an instance of the interface.
Checkin.checkinViaApi(checkinSendModelObject, new CheckInListener() {
#Override
void onCheckIn() {
//Do your action here
}
});

Convert RequestHandler from Picasso to Glide

In Picasso there exists the RequestHandler class. And I can add custom RequestHandlers to Picasso.
How can this be done in Glide?
I for example want that following URI can be handled by a custom RequestHandler: "appicon:custom_data_to_interprete_manually"
EDIT - what I have so far
public class GlideConfiguration implements GlideModule {
#Override
public void applyOptions(Context context, GlideBuilder builder) {
// Apply options to the builder here.
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
}
#Override
public void registerComponents(Context context, Glide glide) {
glide.register(CustomModelParams.class, CustomModelParams.class, new CustomFactory());
}
class CustomModelParams
{
final String data;
public CustomModelParams(String data)
{
this.data = data;
}
public String getId()
{
return data;
}
}
class CustomFactory implements ModelLoaderFactory<CustomModelParams, CustomModelParams>
{
#Override
public ModelLoader<CustomModelParams, CustomModelParams> build(Context context, GenericLoaderFactory loaderFactory) {
return new CustomModelLoader();
}
#Override
public void teardown() {
}
}
class CustomModelLoader implements ModelLoader<CustomModelParams, CustomModelParams>
{
public CustomModelLoader() {
super();
}
#Override
public DataFetcher<CustomModelParams> getResourceFetcher(final CustomModelParams model, int width, int height)
{
return new DataFetcher<CustomModelParams>()
{
#Override
public CustomModelParams loadData(Priority priority) throws Exception { return model; }
#Override
public void cleanup() { }
#Override
public String getId() { return model.getId(); }
#Override
public void cancel() { }
};
}
}
class CustomBitmapDecoder implements ResourceDecoder<CustomModelParams, Bitmap>
{
private final Context context;
public CustomBitmapDecoder(Context context)
{
this.context = context;
}
#Override
public Resource<Bitmap> decode(CustomModelParams source, int width, int height) throws IOException
{
BitmapPool pool = Glide.get(context).getBitmapPool();
Bitmap bitmap = pool.getDirty(width, height, Bitmap.Config.ARGB_8888);
if (bitmap == null) {
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
}
// TODO
// create custom bitmap from CustomModelParams!!!
return BitmapResource.obtain(bitmap, pool);
}
#Override
public String getId()
{
return CustomBitmapDecoder.class.getName();
}
}
}
QUESTION
How do I link those classes together? The Decoder must somehow be linked with the new Model
How do I define, that my custom loader can handle a request? I have to somehow determine if the url I get can be handled by this loader...
You can use ModelLoaders. See the Downloading custom sizes wiki for an example of a custom ModelLoader. Note that instead of a BaseGlideUrlLoader, you will want to register a ModelLoader that handles your particular data model.
I have implemented very simular code for my project (It loads other applications icons from Package Manager) and
#Override
public void registerComponents(Context context, Glide glide) {
glide.register(MyDataModel.class, Bitmap.class, new MyUrlLoader.Factory());
}
doesn't work for me.
So to make custom loaded works I just use Glide Builder
private final GenericRequestBuilder<MyDataModel, Bitmap, Bitmap, Bitmap> mGlideBuilder;
mGlideBuilder = Glide.with(mContext)
.using(new MyUrlLoader(mContext), Bitmap.class)
.from(MyDataModel.class)
.as(Bitmap.class)
.decoder(new MyBitmapDecoder())
.diskCacheStrategy(DiskCacheStrategy.NONE);
mGlideBuilder.load(entry).into(holder.icon);
and MyBitmapDecoder and MyUrlLoader declared as
public class MyBitmapDecoder implements ResourceDecoder<Bitmap, Bitmap> {
public class MyUrlLoader implements ModelLoader<MyDataModel, Bitmap> {

Categories

Resources