Search code examples
androiddagger-2android-mvvm

Dagger 2, BottomNavigation and Injecting


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

Solution

  • 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.