I am trying to provide SettingsPresenter to SettingsActivity (View) and got "dagger class could not be bound with key" error, please help me fix it and figure out reason of the error.
error:
Error:(32, 8) error: presenter.ISettingsPresenter could not be bound with key presenter.ISettingsPresenter required by ui.activity.settings.SettingsActivity for dagger.AppModule
ModelsModule provides securityModel and userModel and it's working fine;
My code is:
SettingsActivity:
SettingsActivity implements ISettingsView {
#Inject ISettingsPresenter presenter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
createScopedGraph(new SettingsModule(this)).inject(this);
presenter.onCreate();
//...
}
public ObjectGraph createScopedGraph(Object... modules) {
return objectGraph.plus(modules);
}
}
AppModule:
#Module(
injects = {
App.class,
},
includes = {
AnalyticsModule.class,
ModelsModule.class
})
public class AppModule {
private App app;
public AppModule(App app) {
this.app = app;
}
SettingsModule:
#Module(
injects = SettingsActivity.class,
addsTo = AppModule.class,
complete = false)
public class SettingsModule {
private final ISettingsView view;
public SettingsModule(ISettingsView settingsView) {
this.view = settingsView;
}
#Provides
public ISettingsView provideView() {
return this.view;
}
#Provides
public ISettingsPresenter providePresenter(ISettingsView view, UserModel userModel, SecurityModel securityModel) {
return new SettingsPresenter(view, userModel, securityModel);
}
}
Related
I am trying to implement new architecture (MVVM + RxJava2 + Dagger2 + Retrofit) in my existing project. I have set up whole above architecture and tested on HomeActivity. Dependencies injected in HomeViewModel. So Now I was trying to inject dependencies same as HomeViewModel in a FollowingViewModel of FollowingFragment which is a container Fragment of HomeActivity.But injected dependencies always return null(Not Initiziling).
I am following this project riggaroo/android-arch-components-date-countdown to Inject dependencies but in this sample, only activities are used no fragment. So I don't know what is happening and who to inject deps in multiple ViewModels.
Here is code for some important classes to understand:
AppApplication.class
public class AppApplication extends MultiDexApplication implements HasActivityInjector {
#Inject
DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
#Override
public void onCreate() {
...........................
AppInjector.init(this)
..........................
}
#Override
public AndroidInjector<Activity> activityInjector() {
return dispatchingAndroidInjector;
}
}
AppInjector.class
public class AppInjector {
private AppInjector() {
}
public static void init(AppApplication appApplication) {
DaggerAppComponent.builder()
.application(appApplication)
.build()
.inject(appApplication);
appApplication
.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
#Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
handleActivity(activity);
}
});
}
private static void handleActivity(Activity activity) {
if (activity instanceof HasSupportFragmentInjector) {
AndroidInjection.inject(activity);
}
if (activity instanceof FragmentActivity) {
((FragmentActivity) activity).getSupportFragmentManager()
.registerFragmentLifecycleCallbacks(
new FragmentManager.FragmentLifecycleCallbacks() {
#Override
public void onFragmentCreated(FragmentManager fm, Fragment f,
Bundle savedInstanceState) {
if (f instanceof Injectable) {
AndroidSupportInjection.inject(f);
}
}
}, true);
}
}
}
AppComponent.class
#Singleton
#Component(modules = {AndroidSupportInjectionModule.class,ActivityBuilderModule.class, AppModule.class, NetworkModule.class})
public interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
Builder application(AppApplication application);
AppComponent build();
}
void inject(AppApplication app);
AppModule.class
#Module(includes = { ViewModelModule.class})
public class AppModule {
#Provides
Context provideContext(AppApplication application) {
return application.getApplicationContext();
}
}
ActivityBuilderModule.class
#Module
public abstract class ActivityBuilderModule {
#ContributesAndroidInjector(modules = FragmentBuildersModule.class)
abstract HomeActivity bindHomeActivity();
}
FragmentBuildersModule.class
#Module
public abstract class FragmentBuildersModule {
#ContributesAndroidInjector
abstract FollowingListFragment contributeFollowingListFragment();
}
ViewModule.class
#Module
public abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(FollowingViewModel.class)
abstract ViewModel bindFollowingViewModel(FollowingViewModel followingViewModel);
#Binds
#IntoMap
#ViewModelKey(HomeViewModel.class)
abstract ViewModel bindHomeViewModel(HomeViewModel homeViewModel);
#Binds
abstract ViewModelProvider.Factory bindViewModelFactory(MyViewModelFactory factory);
}
NetworkModule.class
#Module
public class NetworkModule {
public NetworkModule(){}
#Provides
#Singleton
CompositeDisposable getCompositeDisposable() {
return new CompositeDisposable();
}
#Provides
#Singleton
Retrofit provideCall() {
// OKHttps and Retrofit code...
}
#Provides
#Singleton
#SuppressWarnings("unused")
public ApiCallInterface providesNetworkService(
Retrofit retrofit) {
return retrofit.create(ApiCallInterface.class);
}
#Provides
#Singleton
#SuppressWarnings("unused")
public Repository providesService(
ApiCallInterface networkService) {
return new Repository(networkService);
}
}
MyViewModelFactory.class
#Singleton
public class MyViewModelFactory extends ViewModelProvider.NewInstanceFactory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
#Inject
public MyViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;
}
#SuppressWarnings("unchecked")
#Override
public <T extends ViewModel> T create(Class<T> modelClass) {
Provider<? extends ViewModel> creator = creators.get(modelClass);
if (creator == null) {
for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
if (modelClass.isAssignableFrom(entry.getKey())) {
creator = entry.getValue();
break;
}
}
}
if (creator == null) {
throw new IllegalArgumentException("unknown model class " + modelClass);
}
try {
return (T) creator.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
This class is used to create Instances of all viewModels. I have debugged and found only HomeViewModel instance is creating.
Now I have created HomeViewModel which is called from HomeActivity. Which is working fine. Now same implementaion is done in FollowingFragment but doesn't work. Let me show you how I have initialized FollowViewModel from FollowingListFragment.class
public class FollowingListFragment extends BaseFragment implementsInjectable {
#Inject
MyViewModelFactory myViewModelFactory;
private FollowingViewModel followingViewModel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getActivity();
followingViewModel = ViewModelProviders.of(this, plownsViewModelFactory)
.get(FollowingViewModel.class);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_following_list, container, false);
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
followingViewModel.getFollowings(id,curser);
}
}
Here is FollowingViewModel.class
public class FollowingViewModel extends ViewModel {
#Inject
Repository service;
#Inject
CompositeDisposable subscriptions;
#Inject
public FollowingViewModel() {
}
/*void setService(Repository service){
this.service = service;
}*/
void getFollowings(String id,String curser) {
if(service!=null) { **//HERE SERVICE RETURNS NULL**
Disposable subscription = service.getFollowings(id, curser, new IResponseCallback<FollowingResponse.FollowingResult>() {
#Override
public void onSuccess(FollowingResponse.FollowingResult followingResult) {
}
#Override
public void onError(Throwable throwable) {
}
});
subscriptions.add(subscription);
}else {
Log.d("FollowViewModel", "Service is null " );
}
}
Here is HomeActivity and HomeViewModel class code which is working fine.
HomeActivity.class
public class HomeActivity extends BaseActivity implements HasSupportFragmentInjector, Injectable {
#Inject
DispatchingAndroidInjector<Fragment> supportFragmentInjector;
#Inject
MyViewModelFactory viewFactory;
HomeViewModel homeViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
homeViewModel = ViewModelProviders.of(this,viewFactory).get(HomeViewModel.class);
homeViewModel.getFollowings("xyx",null);
}
#Override
public AndroidInjector<Fragment> supportFragmentInjector() {
return supportFragmentInjector;
}
}
HomeViewModel.class
public class HomeViewModel extends ViewModel {
#Inject
Repository service;
#Inject
CompositeDisposable subscriptions;
#Inject
public HomeViewModel() {
}
void getFollowings(String id,String curser) {
if(service!=null) { **// Returing null service**
}
}else {
Log.d("FollowViewModel", "Service is null " );
}
}
}
I'm trying to figure how to deal with dependency injection with dagger 2 and clean architecture in Android. What i want to achieve is when i click a button, a message will be saved to Firebase Database. And show success message to user. When i build my project i'm getting this error:
Error:(10, 1) error: com.example.mvpsample.home.HomeComponent
(unscoped) may not reference scoped bindings: #Provides #Singleton
com.google.firebase.database.FirebaseDatabase
com.example.mvpsample.data.DataModule.provideFirebaseDatabase()
Here my app class:
public class MyApp extends Application {
private static MyApp app;
private HomeComponent homeComponent;
private AuthenticationComponent authenticationComponent;
#Inject
Presenter presenter;
#Override
public void onCreate() {
super.onCreate();
app = this;
homeComponent = DaggerHomeComponent
.builder()
.homeModule(new HomeModule(presenter.getView()))
.build();
}
public HomeComponent getHomeComponent() {
return homeComponent;
}
public static MyApp app() {
return app;
}
}
HomeActivity
public class HomeActivity extends AppCompatActivity implements BaseContract.View {
#Inject
public Presenter presenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
MyApp
.app()
.getHomeComponent()
.inject(this);
}
#OnClick(R.id.tvHello)
public void clickTvHello() {
presenter.writeStringToDatabase("Hi");
}
#Override
public void showSuccessMessage() {
Toast.makeText(this, "Success", Toast.LENGTH_SHORT).show();
}
}
HomeModule
#Module
public class HomeModule {
private final BaseContract.View View;
#Inject
public HomeModule(BaseContract.View View) {
this.View = View;
}
#Provides
public BaseContract.View provideView() {
return View;
}
}
HomeComponent
#Component(modules = {HomeModule.class, DataModule.class})
public interface HomeComponent {
void inject(HomeActivity homeActivity);
}
DataModule
#Module
public class DataModule {
#Provides
#Singleton
public FirebaseDatabase provideFirebaseDatabase() {
return FirebaseDatabase.getInstance();
}
}
BaseContract
public interface BaseContract {
interface View {
void showSuccessMessage();
}
interface Presenter {
View getView();
void writeStringToDatabase(String string);
}
}
Presenter
public class Presenter implements BaseContract.Presenter {
private final BaseContract.View View;
#Inject
FirebaseDatabase firebaseDatabase;
#Inject
public Presenter(BaseContract.View View) {
this.View = View;
}
#Override
public BaseContract.View getView() {
return View;
}
#Override
public void writeStringToDatabase(String string) {
firebaseDatabase.getReference()
.child("messages")
.push()
.child("value")
.setValue(string).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
getView().showSuccessMessage();
}
});
}
}
I followed sample projects and tutorials but did not understand what i'm doing wrong here. Not looking for working project but i want to learn what is best practice of this and how to manage and use modules and components.
The problem is that your HomeComponent is not scoped while your DataModule provides a scoped dependency (i.e the FirebaseDatabase dependency).
A non-scoped component cannot rely on scoped provider. You either have to remove the #Singleton on your provideFirebaseDatabase() provider or add #Singleton on your HomeComponent.
I'm trying to write some tests for fragments which have fields annotated with #Inject. For example, a chunk of my app looks like this:
Module:
#Module
public class PdfFactoryModule {
#Provides #Singleton
PdfFactory providePdfFactory() {
return PdfFactory.getPdfFactory();
}
}
Component:
#Singleton
#Component(modules = PdfFactoryModule.class)
public interface CorePdfComponent {
void inject(PagerFragment pagerFragment);
}
Application:
public class CorePdfApplication extends Application {
#NonNull
private CorePdfComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerCorePdfComponent.builder().build();
}
#NonNull
public CorePdfComponent getComponent() {
return component;
}
}
PagerFragment:
public class PagerFragment extends Fragment {
#Inject PdfFactory pdfFactory;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Dagger 2
((CorePdfApplication) getActivity().getApplication()).getComponent().inject(this);
}
(Note that these are only snippets of my whole code, I'm showing only the essentials for this particular dependency to keep it clear.)
I was trying to do a test like this:
Fake Module:
#Module
public class FakePdfFactoryModule extends PdfFactoryModule {
#Override
PdfFactory providePdfFactory() {
return Mockito.mock(PdfFactory.class);
}
}
Fake Component:
#Singleton
#Component(modules = FakePdfFactoryModule.class)
public interface FakeCorePdfComponent extends CorePdfComponent {
void inject(PagerFragmentTest pagerFragmentTest);
}
Fake Application:
public class FakeCorePdfApplication extends CorePdfApplication {
#NonNull
private FakeCorePdfComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerFakeCorePdfComponent.builder().build();
}
#NonNull
public FakeCorePdfComponent getComponent() {
return component;
}
}
Test:
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21, application = FakeCorePdfApplication.class)
public class PagerFragmentTest {
PagerFragment pagerFragment;
#Before
public void setup() {
pagerFragment = new PagerFragment();
startVisibleFragment(pagerFragment);
}
#Test
public void exists() throws Exception {
assertNotNull(pagerFragment);
}
But the DaggerFakeCorePdfComponent doesn't generate. I may have messed up big time because I never tested with dependency injection. What am I doing wrong?
My advice - "Do not use dagger in tests".
Just change your code to next:
public class FakeCorePdfApplication extends CorePdfApplication {
#NonNull
private CorePdfComponent component = mock(CorePdfComponent.class);
#Override
public void onCreate() {
super.onCreate();
}
#NonNull
public CorePdfComponent getComponent() {
return component;
}
}
And:
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21, application = FakeCorePdfApplication.class)
public class PagerFragmentTest {
PagerFragment pagerFragment;
#Before
public void setup() {
pagerFragment = new PagerFragment();
CorePdfComponent component = ((CorePdfApplication)RuntimeEnvironment.application).getComponent();
doAnswer( new Answer() {
Object answer(InvocationOnMock invocation) {
fragment. pdfFactory = mock(PdfFactory.class);
return null;
}
}).when(component).inject(pageFragment);
startVisibleFragment(pagerFragment);
}
#Test
public void exists() throws Exception {
assertNotNull(pagerFragment);
}
}
You may try:
androidTestApt "com.google.dagger:dagger-compiler:<version>"
I was having similar problem, it worked for me.
Hello so I've been going crazy trying to figure out what I haven't configured properly to get a non null dependency when injecting into a class. Below is my current code
public interface DaggerGraph {
void inject(SplashActivity splashActivity);
}
DaggerGraph to provide interface for injecting
#Singleton
#Component(modules = {MainModule.class})
public interface DaggerComponent extends DaggerGraph {
final class Initializer {
private Initializer() {
}
public static DaggerComponent init(Application app) {
return DaggerDaggerComponent.builder()
.mainModule(new MainModule(app))
.build();
}
}
}
The Dagger component
#Module
public class MainModule {
private final Application app;
public MainModule(Application app) {
this.app = app;
}
#Provides
#Singleton
Application provideApplication() {
return app;
}
#Provides
#Singleton
Resources provideResources() {
return app.getResources();
}
}
The Main Module
public class App extends Application {
private static DaggerComponent daggerComponent;
#Override
public void onCreate() {
super.onCreate();
daggerComponent = DaggerComponent.Initializer.init(this);
}
public static DaggerComponent component() {
return daggerComponent;
}
}
Application
public class SplashActivity extends BaseActivity {
#Inject
Resources resources;
#Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
App.component().inject(this);
}
#Override
protected void onResume() {
super.onResume();
Timber.d(Boolean.toString(resources == null)); //Always true
}
}
In this activity Resources is ALWAYS null and its driving me crazy. Any help is much appreciated.
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.