Call a method in the calling class on API success in android - 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
}
});

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.

My Class in not an enclossing class

so I have class constructor:
public class HealthDataStore { // this class is 3rd party api - can't modify
public HealthDataStore(Context context, HealthDataStore.ConnectionListener listener){ /* bla... */ }
/* bla... */
// with Listener Interface:
public interface ConnectionListener {
void onConnected();
void onConnectionFailed(HealthConnectionErrorResult var1);
void onDisconnected();
}
}
and in my repository class i have:
public class HealthRepository {
private string DSConnectionStatus;
public void connectDataStore(HealthDSConnectionListener listener) {
mStore = new HealthDataStore(app, listener);
mStore.connectService();
}
// with inner class:
public class HealthDSConnectionListener implements HealthDataStore.ConnectionListener{
#Override public void onConnected() { DSConnectionStatus = "Connected"; }
#Override public void onConnectionFailed(HealthConnectionErrorResult healthConnectionErrorResult) { DSConnectionStatus = "Connection Failed"; }
#Override public void onDisconnected() { DSConnectionStatus = "Disconnected"; }
};
}
and in my view model class i have below object:
public class SplashViewModel extends AndroidViewModel {
public void connectRepoDataStore(){
// repo is object of class HealthRepository
repo.connectDataStore(mConnectionListener)
// other things to do here
}
private final HealthRepository.HealthDSConnectionListener mConnectionListener = new HealthRepository.HealthDSConnectionListener(){
#Override public void onConnected() {
super.onConnected(); // i need this super to set DSConnectionStatus value
// other things to do here
}
#Override public void onConnectionFailed(HealthConnectionErrorResult error) {
super.onConnectionFailed(error); // i need this super to set DSConnectionStatus value
// other things to do here
}
#Override public void onDisconnected() {
super.onDisconnected(); // i need this super to set DSConnectionStatus value
// other things to do here
}
}
why is private final HealthRepository.HealthDSConnectionListener mConnectionListener = new HealthRepository.HealthDSConnectionListener() throw me error that the class is not enclosing class?
then how should i achieve this? to have my final listener class have capability to set DSConnectionStatus in healthrepository class?
Always try to avoid using inner classes if you know you'll have to extend them. Instead use a separate class, and swap the outer class with a field. If you need to modify a private field that you do not want to expose then create a package-private setter.
public class HealthRepository {
private String DSConnectionStatus;
public void connectDataStore(HealthDSConnectionListener listener) {
mStore = new HealthDataStore(app, listener);
mStore.connectService();
}
void setConnectionStatus(String status) {
DSConnectionStatus = status;
}
}
// create another class in the same package
public class HealthDSConnectionListener implements HealthDataStore.ConnectionListener {
private final HealthRepository repo;
public HealthDSConnectionListener(HealthRepository repo) {
this.repo = repo;
}
#Override public void onConnected() { repo.setConnectionStatus("Connected"); }
#Override public void onDisconnected() { repo.setConnectionStatus("Disconnected"); }
#Override public void onConnectionFailed(HealthConnectionErrorResult error) {
repo.setConnectionStatus("Connection Failed");
}
};
public class SplashViewModel extends AndroidViewModel {
private final HealthRepository repo;
public void connectRepoDataStore() {
// repo is object of class HealthRepository
repo.connectDataStore(mConnectionListener)
// other things to do here
}
private final HealthDSConnectionListener mConnectionListener = new HealthDSConnectionListener(repo) {
#Override public void onConnected() {
super.onConnected();
// ...
}
#Override public void onConnectionFailed(HealthConnectionErrorResult error) {
super.onConnectionFailed(error);
// ...
}
#Override public void onDisconnected() {
super.onDisconnected();
// ...
}
}
}

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

How to test if an image is fully loaded with Picasso

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) {
}
});

How do I call an intent in Retrofit Callback?

I want to display a new activity on success callback of my WebService called by Retrofit.
And I have difficulties to find examples on how to use Retrofit callback result to launch new activity.
Is that a good way to do it ? Do I have to clean up some stuff before?
public void validate(View view) {
RetrofitWebServices.getInstance().webserviceMethod(params,new Callback<MyObject>() {
#Override
public void success(MyObject object, Response response) {
Intent barIntent = new Intent(FooActivity.this, BarActivity.class);
startActivity(barIntent);
}
#Override
public void failure(RetrofitError error) {
...
}
});
}
You can implement Callback with weak reference to Context
public class MyCallback implements Callback<MyObject> {
WeakReference<Context> mContextReference;
public MyCallback(Context context) {
mContextReference = new WeakReference<Context>(context);
}
#Override
public void success(MyObject arg0, Response arg1) {
Context context = mContextReference.get();
if(context != null){
Intent barIntent = new Intent(FooActivity.this, BarActivity.class);
context.startActivity(barIntent);
} else {
// TODO process context lost
}
}
#Override
public void failure(RetrofitError arg0) {
// TODO process error
}
}
Just remember - this solution will not work if Context lost occurred while request in progress but you may don't worry about potential memory leak which may be if you keep strong reference to Context object.
Hi have a solution that seems easier: use the getApplicationContext() function.
I am not 100% sure it is OK, but in my case it works as expected.
Your code would be:
public void validate(View view) {
RetrofitWebServices.getInstance().webserviceMethod(params,new Callback<MyObject>() {
#Override
public void success(MyObject object, Response response) {
Intent barIntent = new Intent(getApplicationContext(), BarActivity.class);
startActivity(barIntent);
}
#Override
public void failure(RetrofitError error) {
...
}
});
}

Categories

Resources