2

I am doing the small Android app in MVVM and Dagger 2. But I don't know how to correctly use Dagger 2 in case when I have the one Activity and two Fragments. Both Fragments are owners of the ViewModels. I've injected ViewModelProvider to Fragment, but I'm still confused about this solution. Maybe someone will improve my code?

Activity:

public class MainActivity extends DaggerAppCompatActivity {

private static final String TAG = "MainActivity";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    BottomNavigationView navView = findViewById(R.id.nav_view);

    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    NavigationUI.setupWithNavController(navView, navController);
}

}

FirstFragment:

public class EventsFragment extends DaggerFragment {

private static final String TAG = "EventsFragment";
private EventsViewModel eventsViewModel;
private final EventsAdapter adapter = new EventsAdapter();
private List<Event> events = new ArrayList<>();

@Inject
ViewModelProviderFactory viewModelProviderFactory;

public View onCreateView(@NonNull LayoutInflater inflater,
                         ViewGroup container, Bundle savedInstanceState) {
    eventsViewModel = ViewModelProviders.of(this, viewModelProviderFactory).get(EventsViewModel.class);
    View root = inflater.inflate(R.layout.fragment_events, container, false);

    RecyclerView recyclerView = root.findViewById(R.id.events_recycler_view);
    recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    recyclerView.setHasFixedSize(true);
    recyclerView.setAdapter(adapter);

    getEvents();

    return root;
}

Second Fragment:

public class ScheduleFragment extends DaggerFragment {

private ScheduleViewModel scheduleViewModel;

public View onCreateView(@NonNull LayoutInflater inflater,
                         ViewGroup container, Bundle savedInstanceState) {
    scheduleViewModel = ViewModelProviders.of(this).get(ScheduleViewModel.class);
    View root = inflater.inflate(R.layout.fragment_schedule, container, false);

    return root;
}

@Override
public void onAttach(Context context) {
    AndroidSupportInjection.inject(this);
    super.onAttach(context);
}
}

In this case both Fragments are work, but I can inject ViewModelFactory in only one. If I'll inject this to the second one: error: cannot find symbol class DaggerAppComponent

ActivityBuildersModule:

@Module
public abstract class ActivityBuildersModule {

@ContributesAndroidInjector(modules = FragmentBuildersModule.class)
abstract MainActivity contributeMainActivity();
}

AppComponent:

   @Singleton
   @Component(
    modules = {
            AndroidSupportInjectionModule.class,
            ActivityBuildersModule.class,
            AppModule.class,
            ViewModelFactoryModule.class
    }
    )
    public interface AppComponent extends AndroidInjector<BaseApplication> {

@Component.Builder
interface Builder {

    @BindsInstance
    Builder application(Application application);

    AppComponent build();
}

}

FragmentBuildersModule:

 @Module
 public abstract class FragmentBuildersModule {

@ContributesAndroidInjector(
        modules = {ViewModelModule.class, EventsModule.class}
)
abstract EventsFragment contributeEventsFragment();

@ContributesAndroidInjector(
        modules = {ViewModelModule.class}
)
abstract ScheduleFragment contributeScheduleFragment();
}

ViewModelFactory:

@Module
public abstract class ViewModelFactoryModule {

@Binds
public abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelProviderFactory modelProviderFactory);
}

ViewModelKey:

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
public @interface ViewModelKey {
Class<? extends ViewModel> value();
}

ViewModelModule:

@Module

public abstract class ViewModelModule {

@Binds
@IntoMap
@ViewModelKey(EventsViewModel.class)
public abstract ViewModel bindEventsViewModel(EventsViewModel eventsViewModel);

@Binds
@IntoMap
@ViewModelKey(ScheduleViewModel.class)
public abstract ViewModel bindScheduleViewmodel(ScheduleViewModel scheduleViewModel);

BaseApplication:

public class BaseApplication extends DaggerApplication {


@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
    return DaggerAppComponent.builder().application(this).build();
}
Aalap Patel
  • 2,058
  • 2
  • 17
  • 31
beginner992
  • 659
  • 1
  • 9
  • 28
  • You need to be sure the AppComponent has the method Inject(FRAGMENTCLASS fragment) for both fragments you want to inject. and don´t forget to do the injection in both as the first fragment : @Override public void onAttach(Context context) { AndroidSupportInjection.inject(this); super.onAttach(context); } – Bonestack Oct 29 '19 at 18:27

1 Answers1

1

Just do it like in first fragment. Try this



public class ScheduleFragment extends DaggerFragment {

private ScheduleViewModel scheduleViewModel;

@Inject
ViewModelProviderFactory viewModelProviderFactory;

public View onCreateView(@NonNull LayoutInflater inflater,
                         ViewGroup container, Bundle savedInstanceState) {
    scheduleViewModel = ViewModelProviders.of(this, viewModelProviderFactory).get(ScheduleViewModel.class);
    View root = inflater.inflate(R.layout.fragment_schedule, container, false);

    return root;
}
}

Also a way to provide Api by adding this method like this in AppModule

@Singleton
    @Provides
    public RemoteApi provideRemoteApi(){
        return Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .baseUrl(Constants.API_BASE_URL)
            .build()
            .create(RemoteApi::class.java)
    }

where you should replace RemoteApi with you Api class.

Milan Kundacina
  • 309
  • 1
  • 8
  • I've tried your solution and there is still same Error. – beginner992 Oct 29 '19 at 18:28
  • Can you post your DI files? Component and modules? – Milan Kundacina Oct 29 '19 at 18:29
  • Sure. Wait a second. – beginner992 Oct 29 '19 at 18:30
  • Done. Sorry for terrible edit, but the SO editor is strange. – beginner992 Oct 29 '19 at 18:38
  • Try to remove `onAttach` in both fragments. It should inject without that. – Milan Kundacina Oct 29 '19 at 18:47
  • Still same and: error: [Dagger/MissingBinding] com.example.codechallenge.data.network.EventsApi cannot be provided without an @Provides-annotated method. A binding with matching key exists in component: com.example.codechallenge.di.FragmentBuildersModule_ContributeEventsFragment.EventsFragmentSubcomponent – beginner992 Oct 29 '19 at 18:49
  • Also did you use Builder and injected into Application? – Milan Kundacina Oct 29 '19 at 18:50
  • It is not same error, that is not error for Viewmodel, but EventsApi. Viewmodel is injected. – Milan Kundacina Oct 29 '19 at 18:50
  • Yes, I also pasted BaseApplication. – beginner992 Oct 29 '19 at 18:52
  • You should now provide EventsApi in AppModule. – Milan Kundacina Oct 29 '19 at 18:53
  • Also your viewmodels need to be declared with @Inject to be injected properly. Dagger is pretty difficult component, but also useful. – Milan Kundacina Oct 29 '19 at 18:56
  • ViewModels are injected, but I don't know how to provide EventsApi. I have Retrofit in AppModule, but I've never hear before about providing api. Sorry for that, but I am still learning this tool. – beginner992 Oct 29 '19 at 18:57
  • I edit my answer so code could be formatted. Try this, just replace RemoteApi with your EventsApi – Milan Kundacina Oct 29 '19 at 19:02
  • I can't set Api like you :/ I can set api like this: .create(EventsApi.class); But then there is Error and required is Retrofit, not Api. – beginner992 Oct 29 '19 at 19:09
  • 1
    This was translated from Kotlin, not so great with Java. :) Point is that you created EventApi somewhere in code, you jest need to move that logic to provide function in AppModule so that EventApi can be injected. If you had project on git that I could look at, I could help more, but posting here every class could be spamming. :) – Milan Kundacina Oct 29 '19 at 19:17
  • Woorks! I created special module for the ScheduleFragment and then I wrote this: @Provides static EventsApi provideEventsApi(Retrofit retrofit) { return retrofit.create(EventsApi.class); } Thank you so much for the help! You are amazing :) – beginner992 Oct 29 '19 at 19:26