I have a BehaviorRelay object in ExecutionStream class which handles network calls. Please refer to ExecutionStream class.
I can call requestTrackingAndExecution() method from any activity. I have implemented Dagger2 dependency such that I can inject ExecutionStream instance in any activity.
My dagger2 configuration:
#PerApplication
#Provides
public ExecutionStream provideExecutionStream(PmsApi pmsApi) {
return new ExecutionStream(pmsApi);
}
#PerApplication annotation
#Scope
#Retention(RUNTIME)
public #interface PerApplication { }
WHAT I NEED TO DO:
I want to call requestTrackingAndExecution() method from Activity A and subscribe to its emitted data in Activity B.
Currently, suubscriber in Activity B is not getting any data emitted from activity A<--- SEE HERE
I have injected ExecutionStream class in both activities like #Inject ExecutionStream executionStream;
For emitting observable, I am calling internshipAndTrackingRelay.accept(data); in requestTrackingAndExecution() method after getting data from a network call.
Code for subscribing to relay:
executionStream.internshipAndTracking()
.subscribe(
new Consumer<ExecutionStream.InternshipAndTrackingContainer>() {
#Override
public void accept(ResponseData data){
//do some stuff with responsedata
}
});
My ExecutionStream class:
public class ExecutionStream {
#NonNull private PmsApi pmsApi;
#NonNull private final BehaviorRelay<InternExecutionContainer> internExecutionRelay = BehaviorRelay.create();
#NonNull private final BehaviorRelay<InternshipAndTrackingContainer> internshipAndTrackingRelay = BehaviorRelay.create();
public ExecutionStream(#NonNull PmsApi pmsApi) {
this.pmsApi = pmsApi;
}
#NonNull
public Observable<InternshipAndTrackingContainer> internshipAndTracking() {
return internshipAndTrackingRelay.hide();
}
public void requestTrackingAndExecution(String internshipExecutionId, String internExecutionId) {
// Do some network call
// Get response
internshipAndTrackingRelay.accept(new InternshipAndTrackingContainer(responseData));
}
});
}
/**
* This function returns combined response of both apis
* This returns when both apis are finished calling
* #return Observable response
*/
private BiFunction<
InternshipExecutionResponse,
TrackingDataResponse,
TrackingAndExecution>
getMergingBiFuntionForTrackingAndExecution() {
return new BiFunction<InternshipExecutionResponse, TrackingDataResponse, TrackingAndExecution>() {
#Override
public TrackingAndExecution apply(#io.reactivex.annotations.NonNull InternshipExecutionResponse internshipExecutionResponse, #io.reactivex.annotations.NonNull TrackingDataResponse trackingDataResponse) throws Exception {
return new TrackingAndExecution(internshipExecutionResponse,trackingDataResponse);
}
};
}
public class InternshipAndTrackingContainer {
public boolean isError;
public boolean isEmpty;
public TrackingAndExecution trackingAndExecution;
public InternshipAndTrackingContainer() {
this.isError = true;
this.trackingAndExecution = null;
this.isEmpty = false;
}
public InternshipAndTrackingContainer(TrackingAndExecution trackingAndExecution) {
this.trackingAndExecution = trackingAndExecution;
this.isError = false;
this.isEmpty = false;
}
public InternshipAndTrackingContainer(boolean isEmpty) {
this.trackingAndExecution = null;
this.isError = false;
this.isEmpty = isEmpty;
}
}
}
Finally found a solution.
I was reinitializing my ApplicationModule again and again.
Changed this:
public ApplicationComponent getComponent() {
ApplicationComponent component = DaggerApplicationComponent.builder()
.networkModule(new NetworkModule())
.ApplicationModule(new ApplicationModule(this))
.build();
return component;
}
To this:
public synchronized ApplicationComponent getComponent() {
if(component == null) {
component = DaggerApplicationComponent.builder()
.networkModule(new NetworkModule())
.ApplicationModule(new ApplicationModule(this))
.build();
}
return component;
}
Does Activity B subscribe to the internshipAndTrackingRelay before Activity A runs the requestTrackingAndExecution() method?
Related
There is the DataAccessInterface class that is in charge of managing the database:
public class DataAccessInterface {
private DaoSession daoSession;
public DataAccessInterface () {
}
...
public void saveCar (Car car) {
daoSession.getCarDao (). insert (car);
}
}
DataAccessInterface injection is used in several Fragments with success. Example:
public class LoginFragment extends BaseFragment {
#Inject
DataAccessInterface dataAccessInterface;
...
public boolean initDatabase () throws SyncDataBaseException {
try {
dataAccessInterface.openSession (currentUser.getUsername ());
} catch (Exception e) {
throw new SyncDataBaseException ();
}
return true;
}
...
}
There is a BackendImp class (No Fragment or Activity) that in the background queries a rest service and saves the response in the database. The injection does not work, it is always null:
public class BackendImp {
#Inject
DataAccessInterface dataAccessInterface;
public void save () {
Car car = unloadCar ()
dataAccessInterface.saveCar (car);
}
The AbstractActivityComponent looks like this
#PerActivity
#Component (dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface AbstractActivityComponent {
Activity activity ();
final class Initializer {
public static AbstractActivityComponent init (Activity activity) {
return DaggerAbstractActivityComponent.builder ()
.applicationComponent (DaggerManager.getInstance (). appComponent ())
.activityModule (new ActivityModule (activity))
.build ();
}
}
void inject (LoginFragment inject);
void inject (BackendImp inject);
}
ApplicationModule:
#Module
public class ApplicationModule {
private final Application application;
private final User currentUser;
public ApplicationModule (Application application) {
this.application = application;
this.currentUser = getUser ();
}
#Provides
#Singleton
DataAccessInterface dataAccessInterface () {
return new DataAccessInterface (userProfile ());
}
}
And ApplicationComponent
#Singleton
#Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
void inject(Application application);
final class Initializer {
public static ApplicationComponent init(Application app) {
return DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(app))
.build();
}
}
Application application();
Context context();
DataAccessInterface dataAccessInterface();
}
Error:
W/System.err: java.lang.NullPointerException: Attempt to invoke virtual method 'void com.service.DataAccessInterface.saveCar(Car)' on a null object reference
Edit:
Based on Nitrodon's question in the comments:
The BackendImp functions are called from a Worker, since they will be done every hour. I wanted a single instance so I did the following which is probably wrong:
public class MainApp extends Application {
public static BackendService backendService;
#Override
public void onCreate () {
super.onCreate ();
backendService = new BackendImp ();
}
public static void callWorker () {
...
workManager.enqueue (updateWorkRequest);
}
And the Worker:
public class updateWorker extends Worker {
...
#Override
public Result doWork () {
Result result = Result.retry ();
try {
backend = MainApp.backendService;
backend.save ();
result = Result.success ();
} catch (Exception e) {
result = Result.retry ();
}
return result;
}
Dagger doesn't hook into constructor calls. You have to tell Dagger to inject dependencies into BackendImp.
You have an inject(BackendImp inject) method in your activity component. This would work, but it's in the wrong place, so your application class can't access it. Putting this method in the application component would work:
#Override
public void onCreate () {
super.onCreate ();
backendService = new BackendImp();
// I assume you created the application component somewhere in here.
component.inject(backendService);
}
However, members injection methods are generally discouraged where they can be avoided. There's no other choice in Activity subclasses, since they are instantiated by the framework, but something like BackendImp is entirely under your control, so you can and should let Dagger create it for you.
To do this, place BackendImp itself in the application component by giving it the #Singleton scope and an #Inject constructor:
#Singleton
public class BackendImp {
final DataAccessInterface dataAccessInterface;
#Inject
BackendImp(DataAccessInterface dataAccessInterface) {
this.dataAccessInterface = dataAccessInterface;
}
// ...
}
#Singleton
#Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
// ...
// Use this in your application instead of new BackendImp()
BackendImp backendImp();
// Many tutorials instead suggest making an #Inject field in
// your application and calling inject(application).
}
I am developing an application with the help of Model View Presenter pattern.
I make use of Retrofit and so I have a ApiClient and ApiInterface with endpoints. I implement the interface in a RemoteDataSource class which I call in the Repository class.
My questions is - how do I make use of an Interactor class to make the repository communicate with the Presenter?
Here is my code until now:
ApiInterface
public interface ApiInterface {
#GET("?")
Call<ArrayList<Movie>> getMoviesByTitle(#Query("t") String title,#Query("apiKey") String apiKey);
}
RemoteDataSource class
private static MovieRemoteDataSource instance;
private final ApiInterface service;
public MovieRemoteDataSource(ApiInterface movieApi) {
service = ApiClient.createService(ApiInterface.class);
}
public static MovieRemoteDataSource getInstance(ApiInterface movieApi) {
if (instance == null) {
instance = new MovieRemoteDataSource(movieApi);
}
return instance;
}
#Override
public void getMovies(String title, String apiKey, final LoadMovieCallBack callback) {
service.getMoviesByTitle(title,apiKey).enqueue(new Callback<ArrayList<Movie>>() {
#Override
public void onResponse(Call<ArrayList<Movie>> call, Response<ArrayList<Movie>> response) {
ArrayList<Movie> movies = response.body();// != null ? //response.body().getTitle() : null;
if (movies != null && !movies.isEmpty()) {
callback.onMoviesLoaded(movies);
} else {
callback.onDataNotAvailable();
}
}
#Override
public void onFailure(Call<ArrayList<Movie>> call, Throwable t) {
callback.onError();
}
});
}
DataSource interface with a callback
public interface MovieDataSource {
interface LoadMovieCallBack{
void onMoviesLoaded(ArrayList<Movie> movies);
void onDataNotAvailable();
void onError();
}
void getMovies(String title, String apiKey,LoadMovieCallBack callback);
}
Repository
private MovieRemoteDataSource movieRemoteDataSource;
public MoviesRepository() {//ApiInterface movieApi) {
//this.service = ApiClient.createService(ApiInterface.class);
}
public static MoviesRepository getInstance(ApiInterface service) {
if (instance == null) {
instance = new MoviesRepository();
}
return instance;
}
public void getMovies(String title, String apiKey ) {
movieRemoteDataSource.getMovies(title,apiKey,this);
}
In MoviesRepository you should declare a function with Callback. Your Presenter
should implement MovieDataSource.LoadMovieCallBack and pass it when you call MoviesRepository
public void getMovies(String title, String apiKey,MovieDataSource.LoadMovieCallBack callback) {
movieRemoteDataSource.getMovies(title,apiKey,callback);
}
Here is Google MVP already done for todo app sample, you can refer it. But now it deprecated because Google recommends MVVM
I try to make sample login page with two fields (username, password) and save button with android architecture component, using android data binding, validating the data in viewmodel and from view model I make call to repository for remote server call as mentioned in official doc, remote server return me userid with success so how can I start new fragment from view model using this success? I learn something about singleLiveEvent and EventObserver, but I'm not able to find there clear usage example:
LoginViewModel
private MutableLiveData<String> snackbarStringSingleLiveEvent= new MutableLiveData<>();
#Inject
public LoginViewModel(#NonNull AppDatabase appDatabase,
#NonNull JobPortalApplication application,
#NonNull MyApiEndpointInterface myApiEndpointInterface) {
super(application);
loginRepository = new LoginRepository(application, appDatabase, myApiEndpointInterface);
snackbarStringSingleLiveEvent = loginRepository.getLogin(username.get(), password.get(), type.get());
}
public MutableLiveData<String> getSnackbarStringSingleLiveEvent() {
return snackbarStringSingleLiveEvent;
}
Repository
public SingleLiveEvent<String> getLogin(String name, String password, String type) {
SingleLiveEvent<String> mutableLiveData = new SingleLiveEvent<>();
apiEndpointInterface.getlogin(name, password, type).enqueue(new Callback<GenericResponse>() {
#Override
public void onResponse(Call<GenericResponse> call, Response<GenericResponse> response) {
mutableLiveData.setValue(response.body().getMessage());
}
#Override
public void onFailure(Call<GenericResponse> responseCall, Throwable t) {
mutableLiveData.setValue(Constant.FAILED);
}
});
return mutableLiveData;
}
Login Fragment
private void observeViewModel(final LoginViewModel viewModel) {
// Observe project data
viewModel.getSnackbarStringSingleLiveEvent().observe(this, new Observer<String>() {
#Override
public void onChanged(String s) {
}
});
}
How can I use EventObserver in above case? Any practical example?
Check out below example about how you can create single LiveEvent to observe only one time as LiveData :
Create a class called Event as below that will provide our data once and acts as child of LiveData wrapper :
public class Event<T> {
private boolean hasBeenHandled = false;
private T content;
public Event(T content) {
this.content = content;
}
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return content;
}
}
public boolean isHandled() {
return hasBeenHandled;
}
}
Then declare this EventObserver class like below so that we don't end up placing condition for checking about Event handled every time, everywhere :
public class EventObserver<T> implements Observer<Event<T>> {
private OnEventChanged onEventChanged;
public EventObserver(OnEventChanged onEventChanged) {
this.onEventChanged = onEventChanged;
}
#Override
public void onChanged(#Nullable Event<T> tEvent) {
if (tEvent != null && tEvent.getContentIfNotHandled() != null && onEventChanged != null)
onEventChanged.onUnhandledContent(tEvent.getContentIfNotHandled());
}
interface OnEventChanged<T> {
void onUnhandledContent(T data);
}
}
And How you can implement it :
MutableLiveData<Event<String>> data = new MutableLiveData<>();
// And observe like below
data.observe(lifecycleOwner, new EventObserver<String>(data -> {
// your unhandled data would be here for one time.
}));
// And this is how you add data as event to LiveData
data.setValue(new Event(""));
Refer here for details.
Edit for O.P.:
Yes, data.setValue(new Event("")); is meant for repository when you've got response from API (Remember to return same LiveData type you've taken in VM instead of SingleLiveEvent class though).
So, let's say you've created LiveData in ViewModel like below :
private MutableLiveData<Event<String>> snackbarStringSingleLiveEvent= new MutableLiveData<>();
You provide value to this livedata as Single Event from repository like below :
#Override
public void onResponse(Call<GenericResponse> call, Response<GenericResponse> response) {
mutableLiveData.setValue(new Event(response.body().getMessage())); // we set it as Event wrapper class.
}
And observe it on UI (Fragment) like below :
viewModel.getSnackbarStringSingleLiveEvent().observe(this, new EventObserver<String>(data -> {
// your unhandled data would be here for one time.
}));
Event.java
public class Event<T> {
private T content;
private boolean hasBeenHandled = false;
public Event(T content) {
this.content = content;
}
/**
* Returns the content and prevents its use again.
*/
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return content;
}
}
/**
* Returns the content, even if it's already been handled.
*/
public T peekContent() {
return content;
}
}
EventObserver.java
public class EventObserver<T> implements Observer<Event<? extends T>> {
public interface EventUnhandledContent<T> {
void onEventUnhandledContent(T t);
}
private EventUnhandledContent<T> content;
public EventObserver(EventUnhandledContent<T> content) {
this.content = content;
}
#Override
public void onChanged(Event<? extends T> event) {
if (event != null) {
T result = event.getContentIfNotHandled();
if (result != null && content != null) {
content.onEventUnhandledContent(result);
}
}
}
}
Example, In ViewModel Class
public class LoginViewModel extends BaseViewModel {
private MutableLiveData<Event<Boolean>> _isProgressEnabled = new MutableLiveData<>();
LiveData<Event<Boolean>> isProgressEnabled = _isProgressEnabled;
private AppService appService;
private SchedulerProvider schedulerProvider;
private SharedPreferences preferences;
#Inject
LoginViewModel(
AppService appService,
SchedulerProvider schedulerProvider,
SharedPreferences preferences
) {
this.appService = appService;
this.schedulerProvider = schedulerProvider;
this.preferences = preferences;
}
public void login(){
appService.login("username", "password")
.subscribeOn(schedulerProvider.executorIo())
.observeOn(schedulerProvider.ui())
.subscribe(_userLoginDetails::setValue,
_userLoginDetailsError::setValue,
() -> _isProgressEnabled.setValue(new Event<>(false)),
d -> _isProgressEnabled.setValue(new Event<>(true))
)
}
}
In Login Fragment,
viewModel.isProgressEnabled.observe(this, new EventObserver<>(hasEnabled -> {
if (hasEnabled) {
// showProgress
} else {
// hideProgress
}
}));
Using Event and EventObserver class we can achieve the same like SingleLiveEvent class but if you are thinking a lot of boilerplate code just avoid this method. I hope it would help you and give some idea about why we are using SingleEvent in LiveData.
I understand that Google gives the guidelines to use LiveData between the ViewModel and UI but there are edge cases where using LiveData as a SingleLiveEvent is like reinventing the wheel. For single time messaging between the view model and user interface we can use the delegate design pattern. When initializing the view model in the activity we just have to set the activity as the implementer of the interface. Then throughout our view model we can call the delegate method.
Interface
public interface Snackable:
void showSnackbarMessage(String message);
UI
public class MyActivity extends AppCompatActivity implements Snackable {
private MyViewModel myViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_layout);
this.myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
this.myViewModel.setListener(this);
}
#Override
public void showSnackbarMessage(String message) {
Toast.makeText(this, "message", Toast.LENGTH_LONG).show();
}
}
View Model
public class MyViewModel extends AndroidViewModel {
private Snackable listener;
public MyViewModel(#NonNull Application application) {
super(application);
}
public void setListener(MyActivity activity){
this.listener = activity;
}
private void sendSnackbarMessage(String message){
if(listener != null){
listener.showSnackbarMessage(message);
}
}
private void anyFunctionInTheViewModel(){
sendSnackbarMessage("Hey I've got a message for the UI!");
}
}
I don't have any type of error when I run the application, but the problem is I don't retrieve the data from my interface.
I use Dagger to inject dependencies, and retrofit to retrieve the data from Last.fm Api.
When I try to use a log to check if the data is null, the message is not displayed in logcat and don't work with a toast message.
Service interface
public interface GenreTopTracksService {
#GET("?method=tag.gettoptracks&format=json")
Single<GenreTopTracksResponse> getGenreTopTracks(#Query("tag") String user, #Query("limit") int limit, #Query("api_key") String apiKey);
}
Presenter interface
public interface GenreTopTracksPresenter {
void getGenreTopTracks(String tag, int limit, String apiKey);
}
Interactor interface
public interface GenreTopTracksInteractor {
Single<GenreTopTracksResponse> getGenreTopTracks(String tag, int limit, String apiKey);
}
View interface -- here i have the update data method
public interface GenreTopTracksView {
void updateData(List<Track> tracks);
}
this class is an a implementation of the GenreTopTrackPresneter
public class GenreTopTracksPrensenterImpl implements GenreTopTracksPresenter {
Disposable mDisposable;
GenreTopTracksInteractor mInteractor;
GenreTopTracksView mGenreViewData;
public GenreTopTracksPrensenterImpl(GenreTopTracksInteractor mInteractor, GenreTopTracksView mGenreViewData) {
this.mInteractor = mInteractor;
this.mGenreViewData = mGenreViewData;
}
#Override
public void getGenreTopTracks(String tag, int limit, String apiKey) {
disposeRequest();
mDisposable = mInteractor.getGenreTopTracks(tag, limit, apiKey)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(new Function<GenreTopTracksResponse, List<Track>>() {
#Override
public List<Track> apply(#NonNull GenreTopTracksResponse topTracksResponse) throws Exception {
if (topTracksResponse != null && topTracksResponse.getTopTracks() != null && topTracksResponse.getTopTracks().getTracks() != null) {
return topTracksResponse.getTopTracks().getTracks();
}
return new ArrayList<Track>();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<Track>>() {
#Override
public void accept(#NonNull List<Track> tracks) throws Exception {
if (tracks.size() == 0) {
// NO se muestra nada
}else{
mGenreViewData.updateData(tracks);
}
}
}, new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
Log.d("ERRORGENRE", "Errorxd");
}
});
}
private void disposeRequest() {
if (mDisposable != null && !mDisposable.isDisposed()) {
mDisposable.dispose();
}
}
}
THis is my module class to inject dependencies to my main activity
#Module
public class GenreTopTracksModule {
GenreTopTracksView mView;
public GenreTopTracksModule(GenreTopTracksView view) {
mView = view;
}
// provides the view to create the top tracks presenter
#Singleton
#Provides
public GenreTopTracksView providesTopTracksView() {
return this.mView;
}
// provides a converter factory to create the retrofit instance
#Singleton
#Provides
public Converter.Factory providesConverterFactory() {
return GsonConverterFactory.create();
}
// provides a call adapter factory needed to integrate rxjava with retrofit
#Singleton
#Provides
public CallAdapter.Factory providesCallAdapterFactory() {
return RxJava2CallAdapterFactory.create();
}
// provides a retrofit instance to create the top tracks interactor
#Singleton
#Provides
public Retrofit providesRetrofit(Converter.Factory converter, CallAdapter.Factory adapter) {
return new Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addCallAdapterFactory(adapter)
.addConverterFactory(converter)
.build();
}
// provides top tracks interactor to make an instance of the presenter
#Singleton
#Provides
public GenreTopTracksInteractor providesTopTopTracksInteractor(Retrofit retrofit) {
return new GenreTopTracksInteractorImplementation(retrofit);
}
// provides top track presenter
#Singleton
#Provides
public GenreTopTracksPresenter providesTopTracksPresenter(GenreTopTracksInteractor interactor, GenreTopTracksView mView) {
return new GenreTopTracksPrensenterImpl(interactor, mView);
}
}
And this is my main activity
public class SelectionActivity extends AppCompatActivity implements GenreTopTracksView{
#Inject
GenreTopTracksPresenter mPresenter;
Button mButton;
EditText mEditText;
String tag;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_selection);
DaggerGenreTopTracksComponent.builder().genreTopTracksModule(new GenreTopTracksModule(this)).build().inject(this);
mButton = (Button) findViewById(R.id.button_prueba);
mEditText = (EditText) findViewById(R.id.edit_prueba);
mButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
tag = mEditText.getText().toString();
mPresenter.getGenreTopTracks(tag, Constants.TOP_ITEMS_LIMIT, Constants.API_KEY);
}
});
}
#Override
public void updateData(List<Track> tracks) {
if(tracks != null){
for(int x = 0; x<tracks.size(); x++){
Log.d("datasetTrack", tracks.get(x).getName());
Toast.makeText(SelectionActivity.this, tracks.get(x).getName(), Toast.LENGTH_SHORT).show();
}
}else if(tracks == null){
Log.d("datasetTrack", "datos nulos :(");
Toast.makeText(SelectionActivity.this, "Datos nulos", Toast.LENGTH_SHORT).show();
}
}
}
I need to see the data in the "updateData" method.
First of all recommend you add retrofit client interceptor
Add dependency in build.gradle:
compile 'com.squareup.okhttp3:logging-interceptor:$your_version'
in GenreTopTrackModule change method
#Singleton
#Provides
public Retrofit providesRetrofit(Converter.Factory converter, CallAdapter.Factory adapter) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
return new Retrofit.Builder()
.client(client)
.baseUrl(Constants.BASE_URL)
.addCallAdapterFactory(adapter)
.addConverterFactory(converter)
.build();
}
As a result in Logcat you will see response what last.fm api return. If response return right check you implementation
I've created a custom implementation of Call<T>, here is the custom class without the custom code, just the forward code for you to see.
public class CachedCall<T> implements Call<T> {
private final Call<T> delegate;
public CachedCall(Call<T> delegate) {
this.delegate = delegate;
}
#Override
public Response<T> execute() throws IOException {
return delegate.execute();
}
#Override
public void enqueue(Callback<T> callback) {
delegate.enqueue(callback);
}
public void enqueueWithCache(final CachedCallback<T> callback) {
delegate.enqueue(callback);
}
#Override
public boolean isExecuted() {
return delegate.isExecuted();
}
#Override
public void cancel() {
delegate.cancel();
}
#Override
public boolean isCanceled() {
return delegate.isCanceled();
}
#Override
public Call<T> clone() {
return new CachedCall<>(delegate.clone());
}
#Override
public Request request() {
return delegate.request();
}
}
And then in my ApiService, I used this custom implementation on some of my call, and the default one on some other, exemple:
public interface APIService {
#GET("categories")
Call<List<Categorie>> categories(#Query("tag") String tag);
#GET("categories/{categorie}/quotes")
CachedCall<List<Gif>> gifs(#Path("categorie") String categorie);
When methods with the custom one are called, I got a crash:
Caused by: java.lang.IllegalArgumentException: Could not locate call adapter for CustomClass.
Tried:
* retrofit2.adapter.rxjava.RxJavaCallAdapterFactory
* retrofit2.ExecutorCallAdapterFactory
at retrofit2.Retrofit.nextCallAdapter(Retrofit.java:237)
at retrofit2.Retrofit.callAdapter(Retrofit.java:201)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:232)
... 21 more
Do I need to register my custom implementation with Retrofit somewhere?
I've solved my own issue.
You need to create and register your own CallAdapter.Factory:
public class CachedCallAdapterFactory extends CallAdapter.Factory {
final Executor callbackExecutor;
public CachedCallAdapterFactory(Executor callbackExecutor) {
this.callbackExecutor = callbackExecutor;
}
#Override
public CallAdapter<Call<?>> get(final Type returnType, final Annotation[] annotations, final Retrofit retrofit) {
if (getRawType(returnType) != CachedCall.class) {
return null;
}
final Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);
return new CallAdapter<Call<?>>() {
#Override public Type responseType() {
return responseType;
}
#Override public <R> Call<R> adapt(Call<R> call) {
return new CachedCall<>(callbackExecutor, call, responseType, retrofit, annotations);
}
};
}
}
And then register it when you create your Retrofit instance:
retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(URL)
.addCallAdapterFactory(new CachedCallAdapterFactory(new DefaultExecutor()))
.build();
Your DefaultExecutor just need to run its Runnable
private class DefaultExecutor implements Executor {
#Override
public void execute(#NonNull Runnable runnable) {
runnable.run();
}
}