Dagger2 singleton annotation not working - android

So, a bit of context. I'm using Dagger2, Retrofit and RxAndroid and structuring my app using an MVP architecture.
For now, all I'm doing is making a network request to the API a retrieving some information as soon as my main activity starts. I'm trying to persist my presenters through configuration changes to avoid making a new http request every time I rotate my screen.
MainActivity.java
public class MainActivity extends AppCompatActivity implements ForecastView {
#Inject
Presenter forecastPresenter;
private TextView text;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.weather);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
initializeDependencies();
initializePresenter();
}
private void initializeDependencies() {
DaggerWeatherApiComponent.builder()
.build().inject(this);
}
private void initializePresenter() {
forecastPresenter.attachView(this);
forecastPresenter.onCreate();
}
WeatherApiComponent.java
#Component(modules = {EndpointsModule.class})
#Singleton
public interface WeatherApiComponent {
void inject(MainActivity context);
}
EndpointsModule.java
#Module #Singleton
public class EndpointsModule {
#Provides
#Singleton
WeatherEndpoints provideEndpoints() {
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(new OkHttpClient())
.baseUrl("http://api.openweathermap.org/data/2.5/")
.build();
return retrofit.create(WeatherEndpoints.class);
}
#Provides
#Singleton
Repository providesRepository(RestRepository repository) {
return repository;
}
#Provides
#Singleton
Presenter providesPresenter(ForecastPresenter presenter) {
return presenter;
}
}
RestRespository
public class RestRepository implements Repository {
private WeatherEndpoints endpoints;
static final String API_KEY = "xxxxxxxxxxxxxxxxxxxxx";
#Inject
public RestRepository(WeatherEndpoints endpoints) {
this.endpoints = endpoints;
}
public Observable<Current> getCurrentWeather(String cityName) {
return endpoints.getCurrent(cityName, API_KEY);
}
public Observable<com.feresr.rxweather.models.List> getForecast(String cityName) {
return endpoints.getForecast(cityName, API_KEY).flatMap(new Func1<FiveDays, Observable<com.feresr.rxweather.models.List>>() {
#Override
public Observable<com.feresr.rxweather.models.List> call(FiveDays fiveDays) {
return Observable.from(fiveDays.getList());
}
});
}
}
ForecastPresenter.java
public class ForecastPresenter implements Presenter {
private GetForecastUseCase useCase;
private Subscription forecastSubscription;
private ArrayList<List> lists;
private ForecastView forecastView;
#Inject
public ForecastPresenter(GetForecastUseCase forecastUseCase) {
this.useCase = forecastUseCase;
lists = new ArrayList<>();
}
#Override
public void onStop() {
if (forecastSubscription.isUnsubscribed()) {
forecastSubscription.unsubscribe();
}
}
#Override
public void attachView(View v) {
forecastView = (ForecastView) v;
}
#Override
public void onCreate() {
if (lists.isEmpty()) {
forecastSubscription = useCase.execute().subscribe(new Action1<List>() {
#Override
public void call(List list) {
lists.add(list);
forecastView.addForecast(list.getWeather().get(0).getMain());
}
});
} else {
forecastView.addForecast(lists.get(0).toString());
}
}
The constructor on this class (presenter) keeps calling itself as I rotate my Acitivity. I've annotated with #Singleton most of my classes. I don't know what else to do.
EDIT: Note that I haven't gotten into dagger SCOPES just yet, for now I don't care if this singleton presenter lives as long as my app. I'll fix that later.

It looks like you're recreating the Dagger component every time MainActivity.onCreate(Bundle) is called, and the activity is reinstantiated when you rotate the screen.
Like other scopes, #Singleton means there will be one instance of the object for the lifetime of the component, not for the lifetime of the JVM. You typically have to make sure there is only one instance of the #Singleton component yourself, usually by keeping it in a field in your Application.

You create a new dagger component every time here:
private void initializeDependencies() {
DaggerWeatherApiComponent.builder()
.build().inject(this);
}
A scoped dependency exists as ONE instance PER component.
If you create a new component, it will have its own scope, and it will create its own instance.
You should either invest in Mortar scopes to preserve your component, or you should have some sort of "cache" in your Application instance.

Related

How can i inject my Retrofit DataService using Dagger2

In my MVP architecture i have Retrofit Instance
public class RetrofitInstance {
private static Retrofit retrofit;
private static final String BASE_URL = "http://api.openweathermap.org/data/2.5/";
/**
* Create an instance of Retrofit object
* */
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
retrofit = new retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
return retrofit;
}
}
And DataService for it
public interface GetNoticeDataService {
#GET("weather?appid=0194877ecdcac230396a119c01d46100")
Observable<NoticeList> getNoticeData(#Query("lat") double lat , #Query("lon") double lon );
}
Also i have DataInteractor which is using RxJava Observable service to call api
public class GetNoticeIntractorImpl implements MainContract.GetNoticeIntractor {
private LatLng getloc(){
return currentLocation;
}
#SuppressLint("CheckResult")
#Override
public void getNoticeArrayList(final OnFinishedListener onFinishedListener) {
/** Create handle for the RetrofitInstance interface*/
GetNoticeDataService service = RetrofitInstance.getRetrofitInstance().create(GetNoticeDataService.class);
/** Using RxJava Observable response to handle retrofit api*/
if(currentLocation!=null) {
service.getNoticeData(getloc().latitude, getloc().longitude)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(items -> onFinishedListener.onFinished(items.getNoticeArrayList(), items.getMain(), items.getWind()), onFinishedListener::onFailure);
}
}
}
Here is presenter
public class MainPresenterImpl implements MainContract.presenter, MainContract.GetNoticeIntractor.OnFinishedListener {
private MainContract.MainView mainView;
private MainContract.GetNoticeIntractor getNoticeIntractor;
#Inject
public MainPresenterImpl(MainContract.MainView mainView, MainContract.GetNoticeIntractor getNoticeIntractor) {
this.mainView = mainView;
this.getNoticeIntractor = getNoticeIntractor;
}
#Override
public void onDestroy() {
mainView = null;
}
#Override
public void onRefreshButtonClick() {
if(mainView != null){
mainView.showProgress();
}
getNoticeIntractor.getNoticeArrayList(this);
}
#Override
public void requestDataFromServer() {
getNoticeIntractor.getNoticeArrayList(this);
}
#Override
public void onFinished(ArrayList<Notice> noticeArrayList, Main main, Wind wind) {
if(mainView != null){
mainView.setDataToRecyclerView(noticeArrayList,main,wind);
mainView.hideProgress();
}
}
#Override
public void onFailure(Throwable t) {
if(mainView != null){
mainView.onResponseFailure(t);
mainView.hideProgress();
}
}
}
And MainContract
public interface MainContract {
/**
* Call when user interact with the view and other when view OnDestroy()
* */
interface presenter{
void onDestroy();
void onRefreshButtonClick();
void requestDataFromServer();
}
/**
* showProgress() and hideProgress() would be used for displaying and hiding the progressBar
* while the setDataToRecyclerView and onResponseFailure is fetched from the GetNoticeInteractorImpl class
**/
interface MainView {
void showProgress();
void hideProgress();
void setDataToRecyclerView(ArrayList<Notice> noticeArrayList, Main main, Wind wind);
void onResponseFailure(Throwable throwable);
}
/**
* Intractors are classes built for fetching data from your database, web services, or any other data source.
**/
interface GetNoticeIntractor {
interface OnFinishedListener {
void onFinished(ArrayList<Notice> noticeArrayList, Main main, Wind wind);
void onFailure(Throwable t);
}
void getNoticeArrayList(OnFinishedListener onFinishedListener);
}
}
Adapter for MyActivity
public class NoticeAdapter extends RecyclerView.Adapter<NoticeAdapter.EmployeeViewHolder> {
private static Wind wind;
private static ArrayList<Notice> dataList;
private static Main main;
private Date currentTime = Calendar.getInstance().getTime();
public static String date;
private Context mContext;
private RecyclerItemClickListener recyclerItemClickListener;
public NoticeAdapter(ArrayList<Notice> dataList, Main main, Wind wind, RecyclerItemClickListener recyclerItemClickListener,Context context) {
NoticeAdapter.dataList = dataList;
NoticeAdapter.main = main;
NoticeAdapter.wind = wind;
this.recyclerItemClickListener = recyclerItemClickListener;
this.mContext=context;
}
#NonNull
#Override
public EmployeeViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.single_view_row, parent, false);
return new EmployeeViewHolder(view);
}
#SuppressLint("SetTextI18n")
#Override
public void onBindViewHolder(#NonNull EmployeeViewHolder holder, #SuppressLint("RecyclerView") final int position) {
setDate(currentTime.toString().substring(0,currentTime.toString().length()-18));
if(getAddressMap()!=null){holder.txtNoticeAddress.setText("Loc: "+getAddressMap());}else{holder.txtNoticeAddress.setText("Loc: Unknown location");}
holder.imageIcon.setImageURI(Uri.parse("android.resource://com.locweather/drawable/i"+dataList.get(position).getIcon()));
holder.txtNoticeWind.setText("Wind: "+roundUp(+wind.getSpeed())+"m/s, "+arrow());
holder.txtNoticeTempMain.setText(roundUp(+main.getTemp())+"°C");
holder.txtNoticeWeather.setText(dataList.get(position).getWeather()+" : "+dataList.get(position).getInfo());
holder.txtNoticeTemp.setText("Feels: "+roundUp(+main.getFeelsLike())+"°C ");
holder.txtNoticeTime.setText(date);
holder.txtNoticeHumidity.setText("Humidity: "+main.getHumidity()+"%");
holder.txtNoticePressure.setText("Pressure: "+main.getPressure()+"hPa");
holder.itemView.setOnClickListener(v -> {
recyclerItemClickListener.onItemClick();
saveNoticeList(mContext,dataList); });
holder.saveButton.setOnClickListener(v -> {
recyclerItemClickListener.onItemClick();
saveNoticeList(mContext,dataList); });
}
private static String getAddressMap() {
return MapsActivity.addressMap;
}
private static void setDate(String date) {
NoticeAdapter.date = date;
}
#Override
public int getItemCount() {
return dataList.size();
}
private static LatLng getloc(){
return currentLocation;
}
class EmployeeViewHolder extends RecyclerView.ViewHolder {
ImageView imageIcon;
Button saveButton;
TextView txtNoticeWeather, txtNoticeTempMain,txtNoticeTemp, txtNoticeHumidity,txtNoticeAddress,txtNoticePressure,txtNoticeWind,txtNoticeTime;
EmployeeViewHolder(View itemView) {
super(itemView);
saveButton=itemView.findViewById(R.id.save_button);
imageIcon=itemView.findViewById(R.id.image_icon);
txtNoticeTime= itemView.findViewById(R.id.txt_time);
txtNoticeWind= itemView.findViewById(R.id.txt_notice_wind);
txtNoticeAddress= itemView.findViewById(R.id.txt_notice_title);
txtNoticeWeather = itemView.findViewById(R.id.txt_notice_weather);
txtNoticeTemp = itemView.findViewById(R.id.txt_notice_temp);
txtNoticeHumidity = itemView.findViewById(R.id.txt_notice_humidity);
txtNoticePressure = itemView.findViewById(R.id.txt_notice_pressure);
txtNoticeTempMain = itemView.findViewById(R.id.txt_notice_temp_main);
}
}
private static void saveNoticeList(Context context, List<Notice> noticeList) {
if (context != null && noticeList != null) {
WeatherData weatherData = new WeatherData(getAddressMap(), wind.getSpeed(), wind.getDeg(), dataList.get(0).getIcon(), dataList.get(0).getInfo(), dataList.get(0).getWeather(), main.getTemp(), main.getFeelsLike(), main.getHumidity(), main.getPressure(), date, getloc().latitude, getloc().longitude);
WeatherDatabase.getInstance(context)
.weatherDao()
.save(weatherData);
}
}
How can i inject this DataService to my Interactor by using Dagger2? Should i use Singleton or Component or something else?
I'll be glad of any kind of help.
I'll assume you want to keep your RetrofitInstance singleton available for now, at least until you have migrated all Retrofit uses to Dagger 2.
In order to inject an interface, or a class whose constructor you don't control, you need to use a Module. This tells Dagger how to create instances of that class or interface given some dependencies:
#Module
public class WebServiceModule {
#Singleton
#Provides
Retrofit provideRetrofit() {
// This method tells Dagger all it needs to know about creating
// a Retrofit instance. This will be replaced by something closer
// to BWappsandmore's answer after RetrofitInstance is no longer needed.
return RetrofitInstance.getRetrofitInstance();
}
#Singleton
#Provides
GetNoticeDataService provideGetNoticeDataService(Retrofit retrofit) {
return retrofit.create(GetNoticeDataService.class);
}
This module can then be included in your Component, which will in turn create any objects it knows how to create, either from modules, #BindsInstance values passed into your component factory method, or classes with #Inject constructors. Since you control your interactor's constructor, you can simply annotate it with #Inject:
// #Reusable or #Singleton if you only need one interactor of this type.
public class GetNoticeInteractorImpl implements MainContract.GetNoticeInteractor {
private GetNoticeDataService service
#Inject
GetNoticeInteractorImpl(GetNoticeDataService service) {
this.service = service;
}
private LatLng getloc(){
return currentLocation;
}
#SuppressLint("CheckResult")
#Override
public void getNoticeArrayList(final OnFinishedListener onFinishedListener) {
// Our service was injected in the constructor, so there
// is no need to create it here.
// You might also consider injecting your schedulers in the future
// for unit testing.
if(currentLocation!=null) {
service.getNoticeData(getloc().latitude, getloc().longitude)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(items -> onFinishedListener.onFinished(items.getNoticeArrayList(), items.getMain(), items.getWind()), onFinishedListener::onFailure);
}
}
}
#Module
abstract class AnotherModule { // or interface
#Binds
abstract MainContract.GetNoticeInteractor bindGetNoticeInteractor(GetNoticeInteractorImpl implementation);
}
Of course, this is useless if you can't ultimately access your interactor. Since you don't even create your Activity classes, you need to request injection from a Component. In Android apps, this is typically created within the Application object in onCreate().
#Component(modules = {WebServicesModule.class, AnotherModule.class})
#Singleton
public interface AppComponent {
#Component.Factory
interface Factory {
AppComponent create(#BindsInstance Application application); // or whatever
}
void bindMainActivity(MainActivity activity);
}
// Probably not a singleton, but if you create an activity scope later, this might use that scope.
class MainPresenter {
#Inject
public MainPresenter(MainContract.GetNoticeInteractor interactor) {
this.getNoticeInteractor = interactor
}
// ...
}
class MainActivity {
#Inject
MainPresenter presenter;
#Override
public void onCreate(Bundle savedInstanceState) {
somehowGetComponent().inject(this);
super(savedInstanceState);
// ...
}
// ...
}
Now, since your MainActivity requires a MainPresenter, Dagger will create one for you when you request injection. This in turn requires a GetNoticeInteractor, which requires GetNoticeInteractorImpl, and so forth, and Dagger will take care of all of this behind the scenes.
If your presenter's constructor requires other arguments (such as a View), it might be simpler for now for MainActivity to request injection directly into the presenter. Ultimately, you will want all of your inject(SomeClass target) methods to refer to Activities, Services, and such, since there is no other way to inject those until your minSdkVersion is 28 or higher.
#Module
object WebServiceModule {
#Singleton
#Provides
fun providesGetNoticeDataService(retrofit: Retrofit): GetNoticeDataService =
retrofit.create<GetNoticeDataService>(GetNoticeDataService::class.java)
#Provides
fun providesGsonConverterFactory(): GsonConverterFactory = GsonConverterFactory.create()
#Provides
fun providesOkHttpClient(loggingInterceptor: HttpLoggingInterceptor): OkHttpClient =
OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.addInterceptor(loggingInterceptor)
.build()
#Provides
fun providesOkHttpLoggingInterceptor(): HttpLoggingInterceptor =
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
#Provides
fun provideRxJava2CallAdapterFactory(): RxJava2CallAdapterFactory =
RxJava2CallAdapterFactory.create()
#Provides
fun providesRetrofit(
client: OkHttpClient,
converterFactory: GsonConverterFactory,
adapterFactory: RxJava2CallAdapterFactory
): Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(converterFactory)
.addCallAdapterFactory(adapterFactory)
.client(client)
.build()
}

Dagger 2 returns null after injection

I am trying to make an injection using Dagger 2, but it always returns null. I think I am doing all right, but anyway it does not work.
Here is the application class:
public class ApplicationSA extends Application {
private static AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.create();
}
public static AppComponent getComponent() {
return appComponent;
}
}
The component interface:
#Component(modules = {
SnoreDetectorClass.class,
AudioRecorderClass.class
})
public interface AppComponent {
void injectsMainFunctionalityActivity(Activity activity);
}
An the main class where I am trying to get the object:
public class MainFunctionalityActivity extends AppCompatActivity {
#Inject
AudioRecorderClass audioRecorderClass;
#Inject
SnoreDetectorClass snoreDetectorClass;
#Override
protected void onCreate(Bundle savedInstanceState) {
ApplicationSA.getComponent().injectsMainFunctionalityActivity(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("TESTING","audioRecorderClass= "+audioRecorderClass); // always null
Log.d("TESTING","snoreDetectorClass= "+snoreDetectorClass); // always null
...
}
And here are the module classes:
#Module
public class AudioRecorderClass {
public interface AudioRecorderInterface {
void AudioRecorder_hasUpdate(double amplitude_in_dB);
}
public AudioRecorderInterface delegate = null;
#Provides
AudioRecorderClass provideAudioRecorderClass(Activity activity) {
delegate = (AudioRecorderInterface)activity;
return new AudioRecorderClass();
}
...
#Module
public class SnoreDetectorClass {
#Provides
SnoreDetectorClass provideSnoreDetectorClass() {
return new SnoreDetectorClass();
}
...
What am I doing wrong ? Why the objects are always null ?
Ah, I see what is going on here. You cannot inject into a subclass. So in your AppComponent you cannot have
void injectsMainFunctionalityActivity(Activity activity);
you must inject with
void injectsMainFunctionalityActivity(MainFunctionalityActivity activity);
As a side note I would suggest not combining your injector and your model class. Better to have separation of concerns. Keep them separate
You have to specifically tell dagger which activity will be injected here, not use the super class Activity but rather your own implementation of the Activity class :
void injectsMainFunctionalityActivity(Activity activity);
change to:
void injectsMainFunctionalityActivity(MainFunctionalityActivity activity);

Dagger2 inject everything?

I am reading documentation about Dagger2. The thing is I have clear how inject a class how has Context using this
But my doubt is with the following class for example:
public class SocialControler{
private ImageView twitterLogo;
private ImageView googleLogo;
public SocialControler(ImageView twitter, ImageView google){
twitterLogo = twitter;
googleLogo = google;
}
//Getters and Setters
}
So in my MainActivity I will have something like this
SocialControler mSocial = new SocialControler(mTwitterLogo, mGoogleLogo);
Should I inject this class using #Inject anotation instead doing new , so no more new on our Activity? If yes How?
I am stuck in the #Mudule . How I can provide a View inside de Module ?
#Module
public class AppModuleSocialMediaManager {
#Provides
public MainActivity provideActivity(){
return new MainActivity();
}
#Provides
public SocialMediaClickManager provideMediaManager(MainActivity mainActivity, View twitterLogo, View googleLogo) {
return new SocialMediaClickManager(mainActivity);
}
#Provides
public View provideTwitter(){
return ?
//from here how I can provide a View
}
}
I would not recommend the architecture you're using to build your app, i.e. make your controllers depend on exact views, but if you're fine with that then you can implement your module like this:
#Module
public class AppModuleSocialMediaManager {
private final MainActivity mainActivity;
public AppModuleSocialMediaManager(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
#Provides
#Named("twitter_view")
View provideTwitterView() {
return mainActivity.findViewById(..);
}
#Provides
#Named("google_view")
View provideGoogleView() {
return mainActivity.findViewById(..);
}
#Provides
SocialController provideSocialController(#Named("twitter_view") View twitterView, #Named("google_view") View googleView) {
return new SocialController(twitterView, googleView);
}
}
Then you need to declare another component. Let's say it's MainActivityComponent:
#Component(modules = AppModuleSocialMediaManager.class)
public interface MainActivityComponent {
void inject(MainActivity activity);
}
After that in your activity you can do something like this:
public class MainActivity extends Activity {
#Inject
SocialController socialController;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerMainActivityComponent.Builder()
.appModuleSocialMediaManager(new AppModuleSocialMediaManager(this))
.build()
.inject(this);
socialController.doSomething();
}
}

Understanding Dagger 2 for Android development

Here's my code, which I based on some old tutorial found on the internet. There really should be some examples on the main site of Dagger 2, I found it really difficult to understand how to implement all this.
It's really a lot of work to get such a simple app to run. I have two questions:
Do I have to call DaggerLoggerComponent in every class I want to get some components like my Logger class?
Also how can I make the scope of the Logger class a singleton? Right now every button click creates a new logger instance.
Probably I dont understand some underlying concepts, I've only used dependency injection in Spring before and all of this seems strange to me.
public class MainActivity extends AppCompatActivity {
private Button button;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init(){
button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
LoggerComponent component = DaggerLoggerComponent.builder().loggerModule(new LoggerModule()).build();
component.getLogger().log("Hello!",MainActivity.this);
}
});
}
}
public class Logger {
private static int i = 0;
public Logger(){
i++;
}
public static int getI() {
return i;
}
public void log(String text, Context context){
Toast.makeText(context,text+" "+i,Toast.LENGTH_SHORT).show();
}
}
#Singleton
#Component(modules={LoggerModule.class})
public interface LoggerComponent {
Logger getLogger();
}
#Module
public class LoggerModule {
#Provides
#Singleton
Logger provideLogger(){
return new Logger();
}
}
The answer is
public class MainActivity extends AppCompatActivity {
#OnClick(R.id.button) //ButterKnife
public void onClickButton() {
logger.log("Hello!");
}
#Inject
Logger logger;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Injector.INSTANCE.getApplicationComponent().inject(this);
ButterKnife.bind(this);
}
#Override
protected void onDestroy() {
ButterKnife.unbind(this);
super.onDestroy();
}
}
public class Logger {
private static int i = 0;
private CustomApplication customApplication;
public Logger(CustomApplication application) {
this.customApplication = application;
i++;
}
public static int getI() {
return i;
}
public void log(String text){
Toast.makeText(customApplication, text + " " + i,Toast.LENGTH_SHORT).show();
}
}
public interface LoggerComponent {
Logger logger();
}
#Module
public class ApplicationModule {
private CustomApplication customApplication;
public ApplicationModule(CustomApplication customApplication) {
this.customApplication = customApplication;
}
#Provides
public CustomApplication customApplication() {
return customApplication;
}
}
#Module
public class LoggerModule {
#Provides
#Singleton
Logger provideLogger(){
return new Logger();
}
}
#Singleton
#Component(modules={LoggerModule.class, ApplicationModule.class})
public interface ApplicationComponent extends LoggerComponent {
CustomApplication customApplication();
void inject(MainActivity mainActivity);
}
public class CustomApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Injector.INSTANCE.initializeApplicationComponent(this);
}
}
public enum Injector {
INSTANCE;
private ApplicationComponent applicationComponent;
public ApplicationComponent getApplicationComponent() {
return applicationComponent;
}
void initializeApplicationComponent(CustomApplication customApplication) {
this.applicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(customApplication))
.build();
}
}
This is currently our Dagger2 architecture.
EDIT: This is from our actual code for Retrofit stuff from our application we're making:
public interface RecordingService {
ScheduledRecordsXML getScheduledRecords(long userId)
throws ServerErrorException;
}
public class RecordingServiceImpl
implements RecordingService {
private static final String TAG = RecordingServiceImpl.class.getSimpleName();
private RetrofitRecordingService retrofitRecordingService;
public RecordingServiceImpl(RetrofitRecordingService retrofitRecordingService) {
this.retrofitRecordingService = retrofitRecordingService;
}
#Override
public ScheduledRecordsXML getScheduledRecords(long userId)
throws ServerErrorException {
try {
return retrofitRecordingService.getScheduledPrograms(String.valueOf(userId));
} catch(RetrofitError retrofitError) {
Log.e(TAG, "Error occurred in downloading XML file.", retrofitError);
throw new ServerErrorException(retrofitError);
}
}
}
#Module
public class NetworkClientModule {
#Provides
#Singleton
public OkHttpClient okHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.interceptors().add(new HeaderInterceptor());
return okHttpClient;
}
}
#Module(includes = {NetworkClientModule.class})
public class ServiceModule {
#Provides
#Singleton
public RecordingService recordingService(OkHttpClient okHttpClient, Persister persister, AppConfig appConfig) {
return new RecordingServiceImpl(
new RestAdapter.Builder().setEndpoint(appConfig.getServerEndpoint())
.setConverter(new SimpleXMLConverter(persister))
.setClient(new OkClient(okHttpClient))
.setLogLevel(RestAdapter.LogLevel.NONE)
.build()
.create(RetrofitRecordingService.class));
}
//...
}
public interface RetrofitRecordingService {
#GET("/getScheduledPrograms")
ScheduledRecordsXML getScheduledPrograms(#Query("UserID") String userId);
}
public interface ServiceComponent {
RecordingService RecordingService();
//...
}
public interface AppDomainComponent
extends InteractorComponent, ServiceComponent, ManagerComponent, ParserComponent {
}
#Singleton
#Component(modules = {
//...
InteractorModule.class,
ManagerModule.class,
ServiceModule.class,
ParserModule.class
//...
})
public interface ApplicationComponent
extends AppContextComponent, AppDataComponent, AppDomainComponent, AppUtilsComponent, AppPresentationComponent {
void inject(DashboardActivity dashboardActivity);
//...
}
Do I have to call DaggerLoggerComponent in every class I want to get some components like my Logger class?
Yes for all classes that created by the system like Application, Activity and Service. but for you own classes, you don't need that. just annotate you constructor with #inject and dagger will provide your dependencies.
Also how can I make the scope of the Logger class a singleton? Right
now every button click creates a new logger instance.
Your setup for singleton is correct. but you have to initialize the component one time after the activity is created (onCreate) in order to let dagger to inject all fields. Also you can utilize lazy injection feature if you don't need the Logger object right away.
#Inject
Logger logger;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LoggerComponent component = DaggerLoggerComponent.builder().loggerModule(new LoggerModule()).build();
component.inject(this);
init();
}
Then you can access your object without take the reference from the component:
private void init(){
button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
logger.log("Hello!",MainActivity.this);
}
});
}
In summary:
You have to initialize the component in all classes that use field injections.
UPDATE:
To do the actual injection, you have to declare inject() method into your component and dagger will automatically implement it. This method will take care of provide any object annotated with #Inject.

Dagger + Otto architecture guidance

I am trying to develop an app with a somewhat modular architecture (nobody knows about no one, but everybody still can communicate). I will begin with an example:
Inside the LoginFragment:
#OnClick
void login() {
bus.post(new AuthorizeEvent(email, password)); // bus is a Bus from Otto
}
#Subscribe
public void onAuthorizedEvent(AuthEvent event) {
// ... user was authorized
}
The Authenticator catches this event and then posts back:
#Subscribe
public void onAuthorizeEvent(AuthorizeEvent event) {
// ... login
bus.post(new AuthEvent(user));
}
Authenticator depends on a lot of stuff through Dagger:
#Inject
public Authenticator(ApiService apiService, Context context, Bus uiBus, Scheduler ioScheduler,
Scheduler uiScheduler) {
this.apiService = apiService;
this.context = context;
this.uiBus = uiBus;
this.ioScheduler = ioScheduler;
this.uiScheduler = uiScheduler;
uiBus.register(this);
}
which is provided by the AuthModule:
#Module(
complete = false,
library = true,
includes = {
ApiModule.class,
ApplicationModule.class
}
)
public class AuthModule {
#Provides
#Singleton
Authenticator provideAuthenticator(ApiService apiService, #ForApplication Context context,
#UIBus Bus uiBus, #IOScheduler Scheduler ioScheduler,
#UIScheduler Scheduler uiScheduler) {
return new Authenticator(apiService, context, uiBus, ioScheduler, uiScheduler);
}
}
My Application class:
public class AbsApplication extends Application {
private ObjectGraph graph;
#Override
public void onCreate() {
super.onCreate();
graph = ObjectGraph.create(getModules());
}
public Object[] getModules() {
return new Object[]{
new ApplicationModule(this),
new ApiModule(),
new AuthModule()
};
}
public void inject(Object object) {
graph.inject(object);
}
}
The question is - where do I instantiate (#Inject) the Authenticator? I don't use it directly in any of my classes. Can it be a field in the AbsApplication class? Should I even use Dagger for the Authenticator? Am I not using Dagger's modules properly?
I know that I can #Inject Authenticator inside the LoginFragment, but I am exploring new patterns. Bear with me, please.

Categories

Resources