Consider the scenario that I am calling Webservice at Presenter A and holding the response data at the same Presenter. I want to utilize the same response data at Presenter E. But I cant pass the response object to each presenter B, C, D. So, I tried to store my response object at separate Holder class with getter & setter. I initialized Holder class using Dagger Inject constructor annotation and tried to consume it at Presenter E . But I got Empty response instead of my datas . Can any one suggest me to handle this scenario in best way . Thanks in advance
问题:
回答1:
You have a lot of fundamental problems with how you're using Dagger2, but my time is limited, so I'll answer for the time being regarding Mortar - all I can say is that on some devices, getSystemService()
is called sooner in Application
than onCreate()
, which means you should initialize your root mortar scope like this:
@Override
public Object getSystemService(String name) {
if(rootScope == null) {
rootScope = MortarScope.buildRootScope()
.withService(InjectorService.TAG, new InjectorService(this))
.build("Root");
}
if(rootScope.hasService(name)) { // if the additional "Context" service is within Mortar
return rootScope.getService(name);
}
return super.getSystemService(name); // otherwise return application level context system service
}
Personally, I had this in my onCreate()
@Override
public void onCreate() {
super.onCreate();
Fabric.with(this, new Crashlytics());
realmHolder = new RealmHolder();
ApplicationHolder.INSTANCE.setApplication(this);
appConfig = new AppConfig(this);
InjectorService.obtain().inject(this); // <--- this one obtains component
initializeRealm();
}
And in InjectorService:
public static ApplicationComponent obtain() {
return ((InjectorService) MortarScope.getScope(ApplicationHolder.INSTANCE.getApplication())
.getService(TAG)).getComponent();
}
As such, in the worst case scenario, getSystemService()
initialized my RootScope either at start-up, or when the singleton dagger component was created.
This solution is currently not multi-process friendly (so Firebase Crash Reporting would kill it by calling onCreate()
in CustomApplication
twice)
EDIT: Injector Service code
public class InjectorService {
public static final String TAG = "InjectorService";
private ApplicationComponent applicationComponent; //dagger2 app level component
InjectorService(CustomApplication customApplication) {
AppContextModule appContextModule = new AppContextModule(customApplication);
RealmModule realmModule = new RealmModule();
applicationComponent = DaggerApplicationComponent.builder()
.appContextModule(appContextModule)
.realmModule(realmModule)
.build();
}
public ApplicationComponent getInjector() { //return the app component to inject `this` with it
return applicationComponent;
}
public static InjectorService get(Context context) {
//this is needed otherwise the compiler is whining. -_-
//noinspection ResourceType
return (InjectorService) context.getSystemService(TAG);
}
public static ApplicationComponent obtain() {
return ((InjectorService) MortarScope.getScope(ApplicationHolder.INSTANCE.getApplication())
.getService(TAG)).getInjector();
}
}
As for your initial question, it's because you added @Inject
annotation to your constructor, but did not include @Singleton
on the class itself
@Singleton
public class Blah {
@Inject
public Blah() {
}
}
EDIT:
I got home from vacation, so initial error is
Error:(40, 5) error: com.hari.daggerpoc.application.App.Component scoped with @com.hari.daggerpoc.frameworks.dagger.DaggerScope may not reference bindings with different scopes: @Singleton class com.hari.daggerpoc.cache.ResponseCache
It refers to this class in App
:
@dagger.Component(modules = {Module.class})
@DaggerScope(Component.class)
public interface Component extends AppDependencies {
void inject(App app);
}
Which inherits from this class:
@Module(includes = {Utils.class, ResponseCache.class})
public interface AppDependencies {
Utils utils();
ResponseCache responseCache();
}
...which is totally NOT a module, so that annotation is unnecessary, but hey.
Anyways, the issue now is that while the dependency is scoped, it is from a different scope (I didn't know the singleton scope isn't used), so if you change
@Singleton
public class ResponseCache {
@Inject
public ResponseCache(){
}
to
@DaggerScope(App.Component.class)
public class ResponseCache {
@Inject
public ResponseCache(){
}
Then if in ScreenA
you change
public Callback<WeatherResponse> configServiceCallback = new Callback<WeatherResponse>() {
@Override
public void onResponse(Call<WeatherResponse> call, Response<WeatherResponse> response) {
Log.d("ScreenA","Response data -->"+response.body().toString());
Flow.get(context).setHistory(History.single(new ScreenB()), Flow.Direction.FORWARD);
responseCache.setWeatherResponse(response.body());
}
to
public Callback<WeatherResponse> configServiceCallback = new Callback<WeatherResponse>() {
@Override
public void onResponse(Call<WeatherResponse> call, Response<WeatherResponse> response) {
Log.d("ScreenA","Response data -->"+response.body().toString());
responseCache.setWeatherResponse(response.body());
Flow.get(context).setHistory(History.single(new ScreenB()), Flow.Direction.FORWARD);
}
Then it says
08-28 18:12:48.369 31253-31253/com.hari.daggerpoc D/ScreenA: Response data -->WeatherResponse{endpoint=Endpoint{url='http://www.waynedgrant.com/weather/api/weather.json', version=1.7, githubProject='null', copyright='Copyright © 2016 Wayne D Grant (www.waynedgrant.com)'}}
08-28 18:12:48.369 31253-31253/com.hari.daggerpoc D/ScreenB: Response cache -->WeatherResponse{endpoint=Endpoint{url='http://www.waynedgrant.com/weather/api/weather.json', version=1.7, githubProject='null', copyright='Copyright © 2016 Wayne D Grant (www.waynedgrant.com)'}}
回答2:
I can imagine this solution to your problem.
The point is, you need to remove from your presenter the ApiRequest
and use a interactor
, the both presenter receives this Interactor injected in his constructor, in this case they will share the same interactor instance (if he was a singleton). The interactor is responsible for doing the cache, if you are using OkHttpClient
you can make the cache without using a holder class
(up to you), in this solution you will not perform 2 api calls for the same data.
Something like this:
public class PresenterA (){
private UserInteractor userInteractor;
private ViewA view;
public PresenterA(UserInteractor interactor, ViewA view){
this.interactor = interactor;
this.view = view;
}
public void getUser(){
interactor.findFirst(new MyCallback(){
@Override
public void onSuccess(User user){
view.loadUserName(user.getName());
}
});
}
}
public class PresenterB (){
private UserInteractor userInteractor;
private ViewB view;
public PresenterA(UserInteractor interactor, ViewB view){
this.interactor = interactor;
this.view = view;
}
public void getUser(){
interactor.findFirst(new MyCallback(){
@Override
public void onSuccess(User user){
view.loadAddress(user.getAddress().getLine1());
}
});
}
}
public class UserInteractor (){
private MyHolderData holder;
private MyApi api;
public UserInteractor(MyHolderData holder, MyApi api){
this.holder = holder;
this.api = api;
}
public User getUser(){
if(holder.hasCache()){
return holder.getUser();
}
api.requestUser(new MyApiCallback(){
@Override
public void onSuccess(User user){
return user;
}
})
}
}