Correct Dagger2 Implementation with Retrofit and Callbacks - android

I'm working on refactoring a project and I want to start with dependency injection and getting Dagger working throughout the project. I have an example posted below for what i've done so far, and it works, but I want to see if its implemented correctly, and if there is a way to have optional fields in the BindsInstance for a component.
My example here follows the path of:
Fragment -> ServiceUtil -> Service
My Fragment creates an instance of a ServiceUtil class and sets one of the Callback properties to a function that exists in the fragment. My ServiceUtil class then uses Retrofit and an instance of my Service interface to make the API call and return the callback.
Questions:
Does the way i've set this up below make sense and is it correct architecturally?
Should I have created two components, one between the Fragment and the ServiceImpl, and one between the ServiceImpl and the Service?
I have some ServiceImpl classes with multiple Callbacks because that ServiceImpl handles multiple defined services in the service class. For example I have a Service for ForgotPassword that requires 3 APIs. So I have 3 APIs defined in the ForgotPasswordService interface, and therefore three callbacks in its implementation class. Is there a way I can have the call backs be optional in my component? Some screens may only use 1 of the 3 api calls.
Fragment:
public class Fragment1 extends BaseFragment
{
#Inject
ForgotPasswordServiceImpl forgotPasswordServiceImpl;
public static Fragment1 newInstance()
{
return new Fragment1();
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment1, container, false);
ComponentA daggerComponentA = DaggerComponentA.builder().apiCallback(apiCallback).baseUrl(Urls.basePreauthUrl).build();
daggerComponentA.inject(this);
}
private Callback<String> apiCallback = new CustomCallback<String>()
{
#Override
public void onResponseReceived(Call<String> call, Response<String> response)
{
int statusCode = response.code();
if (statusCode == HttpCodes.OK && response.body() != null && response.body().getStatus().equals(SUCCESS_INDICATOR) && !response.body().getParticipantID().isEmpty())
{
//Update some stuff
}else{
//Update some stuff
}
}
#Override
public void onSessionExpired(Call<String> call, Response<String> response)
{
//Do something different
}
#Override
public void onFailure(#NonNull Call<String> call, #NonNull Throwable t)
{
//Do Something
}
};
}
ServiceImpl Class
public class MyServiceImpl
{
MyServiceImpl myService;
Callback<String> apiCallback;
public MyServiceImpl(MyServiceImpl myService, Callback<String> apiCallback){
this.myService = myService;
this.apiCallback = apiCallback;
}
public void callApi(String test){
Call<String> api = myService.findUser();
api.enqueue(apiCallback);
}
}
Service Class
public interface MyService
{
#POST(Urls.exampleUrl)
Call<String> findUser();
}
Component Class
#Component(modules = {TestModule.class})
public interface TestComponent
{
#Component.Builder
interface Builder {
#BindsInstance Builder baseUrl(#Named("baseUrl") String baseUrl);
#BindsInstance Builder apiCallback(#Named("apiCallback") Callback<String> apiCallback);
ForgotPasswordComponent build();
}
void inject(Fragment1 fragment1);
}
TestModule Class
#Module
public class TestModule extends PreAuthBaseModule
{
#Provides
public MyService myService(Retrofit retrofit){
return retrofit.create(MyService.class);
}
#Provides
public Callback<String> apiCallback(#Named("apiCallback") Callback<String> apiCallback){
return apiCallback;
}
#Provides
public MyServiceImpl myServiceImpl(MyService myService, Callback<String> apiCallback){
return new MyServiceImpl(myService, apiCallback);
}
}
PreAuthBaseModule Class
#Module(includes = {PreAuthOkHttpClientModule.class, JacksonConverterFactoryModule.class})
public class PreAuthBaseModule
{
#Provides
public Retrofit retrofit(#Named("baseUrl") String baseUrl, OkHttpClient okHttpClient, JacksonConverterFactory factory){
return new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(factory)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
}
}

Related

What can be a good way to call network class other than an activity in android?

I have created a network client app (using Retrofit) where I call for network request and response in the activity. I learned that it is a bad practice. If anyone can suggest that what can be a good design pattern that I can follow to do such an operation?
For Start if you have to create an app from scratch, possibly try to follow one architecture since you are looking for network calls,
You can Use MVVM for best practice and it also handle the api request in best way possible As you can see from figure,
This architecture, basically separates the view(UI) from view Model(logic of view)
It's up to you how you want to develop the app, means you can skip the repository and handle the network calls in view models or else you can create a single repository class and place all the network related stuffs i.e: network call and similar stuff.
reference tutorial : https://learntodroid.com/consuming-a-rest-api-using-retrofit2-with-the-mvvm-pattern-in-android/
Create a RetrofitClient Class like this
public class RetrofitClient {
public static final String BASE_URL = "YOUR BASE URL HERE";
public static RetrofitClient mInstance;
private Retrofit retrofit;
public static RetrofitClient getInstance() {
if (mInstance == null)
mInstance = new RetrofitClient();
return mInstance;
}
private RetrofitClient() {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
public ApiInterface getApiInterface() {
return retrofit.create(ApiInterface.class);
}
public interface ApiInterface {
#GET("api_name_here")
Call<ResponseBody> methodName();
}
}
Repository class
public class MyRepository {
private static MyRepository mInstance;
public static MyRepository getInstance() {
if (mInstance == null)
mInstance = new MyRepository();
return mInstance;
}
private MyRepository(){
}
private LiveData<T> getData(){
MutableLiveData<T> liveData = new MutableLiveData<>();
RetrofitClient.getInstance()
.getApiInterface()
.methodName()
.enqueue(new Callback<T>() {
#Override
public void onResponse(#NonNull Call<T> call, #NonNull Response<T> response) {
liveData.setValue(response.body());
}
#Override
public void onFailure(#NonNull Call<T> call, #NonNull Throwable t) {
// handleFailure
}
});
return liveData;
}
}
ViewModel Class
public class MyViewModel extends ViewModel{
private MyRepository myRepository;
public MyViewModel(){
myRepository = MyRepository.getInstance();
}
public LiveData<T> getData(){
return myRepository.getData();
}
}
Then in your Activity or fragment
MyViewModel myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
myViewModel.getData().observe(this, new Observer<T>() {
#Override
public void onChanged(T t) {
// handle your data here...
}
});

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()
}

Dagger2 singleton annotation not working

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.

Dagger inject not working for Retrofit

I am trying to use Dagger 2 for instantiating a Retrofit interface. The CloudContactDataStore class injects the RestClient and calls its methods.
When I instantiate a CloudContactDataStore object, its RestClient attribute has null value.
public class CloudContactDataStore implements ContactDataStore {
#Inject RestClient restClient;
public CloudContactDataStore() {
this.initializeInjector();
}
private void initializeInjector() {
DaggerApiComponent.builder()
.apiModule(new ApiModule())
.build()
.inject(this);
}
#Override
public Observable<ContactEntity> contactLogin(String contactId) {
return this.restClient.contactLogin(contactId); // Here restClient is null!
}
}
Here is how I create the Dagger Module and Component:
#Singleton
#Component(modules = ApiModule.class)
public interface ApiComponent {
void inject(ContactDataStore contactDataStore);
}
#Module
public class ApiModule {
#Provides public RestClient provideRestClient(ApiService apiService) {
return new RestClientImpl(apiService);
}
#Provides public ApiService provideApiService(RestAdapter restAdapter) {
return restAdapter.create(ApiService.class);
}
#Provides public RestAdapter provideRestAdapter() {
return RestApiAdapter.getInstance();
}
}
Now, the RestClient class and its implementation:
public interface RestClient {
Observable<ContactEntity> contactLogin(String contactId);
}
public class RestClientImpl implements RestClient {
ApiService apiService;
#Inject
public RestClientImpl(ApiService apiService) {
this.apiService = apiService;
}
#Override
public Observable<ContactEntity> contactLogin(String contactId) {
return apiService.login(contactId, "xxx-xxx-xxx");
}
}
The ApiService interface is the Retrofit interface:
public interface ApiService {
String API_BASE_URL = "http://192.168.1.2";
#POST("/login")
Observable<ContactEntity> login(#Body String id, #Header("Key") String key);
}
And finally, the RestApiAdapter:
public class RestApiAdapter {
private static RestAdapter sharedInstance = null;
public static RestAdapter getInstance() {
if (sharedInstance == null){
sharedInstance = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(ApiService.API_BASE_URL)
.build();
}
return sharedInstance;
}
}
Can anyone see what I am doing wrong?
Thanks!
This has the same problem as in Why Dagger inject is not working but component.getObject yes and the same solution. That is you need to either change your inject(ContactDataStore) method to inject(CloudContactDataStore) so it can see the field that needs injecting, or you need to add a method in ContactDataStore that allows you to inject the method yourself.

Android IOC Dagger Framework - How to inject a nested field ?

I'm using Dagger for Android for dependency injections.
I have a UserService object in a Main Class:
public class Main implements Runnable {
#Inject
UserService service;
#Override
public void run() {
for (User f : service.getUserByName("toto")) {
System.out.print(f.getM_Nom());
}
}
public static void main(String[] args) {
ObjectGraph objectGraph = ObjectGraph.create(new UserModule());
Main m = objectGraph.get(Main.class);
m.run();
}
}
I managed to inject the "service" field and to call the method "getUserByName("")".
But in my "UserService", I have an other custom object ("RepositoryUser" class):
public class UserService implements IUserService {
#Inject
RepositoryUser m_Repository;
#Override
public List<User> getUserByName(String name) {
return m_Repository.getAll();
}
}
My problem is that this field is not inject: the "m_Repository" field is null and I get a null pointer exception when I try to use my RepositoryUser object.
Here is my Provider:
#Module(
injects = {UserService.class, Main.class, RepositoryUser.class}
)
public class UserModule {
#Provides
RepositoryUser provideRepositoryUser() {
return new RepositoryUser();
}
#Provides
UserService provideUserService() {
return new UserService();
}
}
Any idea ?
Thanks in advance !
It is preferrable to use Constructor Injection in this case. This can be achieved as follows:
Main:
public class Main implements Runnable {
private final IUserService service;
#Inject
public Main(IUserService service) {
this.service = service;
}
#Override
public void run() {
for (User f : service.getUserByName("toto")) {
System.out.print(f.getM_Nom());
}
}
public static void main(String[] args) {
ObjectGraph objectGraph = ObjectGraph.create(new UserModule());
Main m = objectGraph.get(Main.class);
m.run();
}
}
UserService:
public class UserService implements IUserService {
private final RepositoryUser m_Repository;
#Inject
public UserService(RepositoryUser repository) {
m_Repository = repository;
}
#Override
public List<User> getUserByName(String name) {
return m_Repository.getAll();
}
}
RepositoryUser:
public class RepositoryUser {
#Inject
public RepositoryUser() {
}
/* ... */
}
UserModule:
#Module(injects = Main.class)
public class UserModule {
#Provides
IUserService provideIUserService(UserService userService){
return userService;
}
}
Everywhere the #Inject annotation is present on a constructor, Dagger can automatically create an instance of that item. So when you request a RepositoryUser instance in the UserService constructor, Dagger will see the #Inject annotation on RepositoryUser's constructor, and use that to create a new instance. We do not need an #Provides method here.
The IUserService parameter on the Main constructor cannot be instantiated, since it is an interface. Using the provideIUserService method in the module, we tell Dagger that we want it to create a new UserService instance.
We do have an #Inject annotation on the Main constructor, but we request it using ObjectGraph.get(Class<T> clzz). Therefore, we need to add injects = Main.class to our module.

Categories

Resources