Search code examples
javaandroiddependency-injectiondagger-2dagger

Subcomponent.Builder is missing setters


I need help with Dagger2.13 for Android.

I am following several examples on the internet but I am now facing an error that I can not solve.

Error:(23, 14) error: @Subcomponent.Builder is missing setters for required modules or subcomponents: [com.hugothomaz.fipe.Module.DIMarcaModulo]

I thought it best to post the problem classes in GITHub and include the repository link here.

https://github.com/hugothomaz/FIPE_Test_Dagger2.11

-FipeApplication-

public class FipeApplication extends Application implements HasActivityInjector, HasFragmentInjector{

    private static final String URL_SEARCH = "http://fipeapi.appspot.com/api/1/";



    @Inject
    DispatchingAndroidInjector<Fragment> dispatchingAndroidInjectorFragment;

    @Inject
    DispatchingAndroidInjector<Activity> dispatchingAndroidInjectorActivity;


    @Override
    public void onCreate() {
        super.onCreate();
        initializeApplicationComponente();

    }


    @Override
    public void onTerminate() {
        super.onTerminate();
    }



    private void initializeApplicationComponente() {
        Log.i("app", "FipeApplication initializeApplicationComponente");
        //DaggerDIApplicationComponent.builder().(this).build();

    }



    @Override
    public AndroidInjector<Fragment> fragmentInjector() {
        return dispatchingAndroidInjectorFragment;
    }

    @Override
    public AndroidInjector<Activity> activityInjector() {
        return dispatchingAndroidInjectorActivity;
    }
}

-DIApplicationModulo-

@Module(subcomponents = {DIMarcaComponent.class})
public class DIApplicationModulo {

    @Provides
    @Singleton
    GsonConverterFactory provideGsonConverterFactory(){
        GsonConverterFactory factory = GsonConverterFactory.create();
        return factory;
    }

    @Provides
    @Singleton
    OkHttpClient provideOkHttpClient(){
        return new OkHttpClient.Builder()
                .connectTimeout(60, TimeUnit.SECONDS)
                .readTimeout(60, TimeUnit.SECONDS)
                .build();
    }

    @Provides
    @Singleton
    RxJavaCallAdapterFactory provideRxJavaCallAdapterFactory(){
        return RxJavaCallAdapterFactory.create();
    }

    @Provides
    @Singleton
    Retrofit provideRetrofit(OkHttpClient client,
                             GsonConverterFactory converterFactory,
                             RxJavaCallAdapterFactory adapterFactory, String mBaseURL){
        return new Retrofit.Builder()
                .baseUrl(mBaseURL)
                .addConverterFactory(converterFactory)
                .addCallAdapterFactory(adapterFactory)
                .client(client)
                .build();
    }

}

-DIApplicationComponent-

@Singleton
@Component(modules = {
        AndroidInjectionModule.class,
        DIApplicationModulo.class,
        ViewBuilderModule.class
        })
public interface DIApplicationComponent extends AndroidInjector<FipeApplication>{

    @Component.Builder
    interface Builder{
        @BindsInstance
        DIApplicationComponent.Builder baseURL(String mBaseURL);

        DIApplicationComponent build();
    }

}

-ViewBuilderModule-

@Module(subcomponents = {DIMarcaComponent.class})
public abstract class ViewBuilderModule {

    @Binds
    @IntoMap
    @FragmentKey(MarcaFragment.class)
    abstract AndroidInjector.Factory<? extends Fragment> bindMarcaFragment(DIMarcaComponent.Builder bulder);
}

-DIMarcaModulo-

@Module
public class DIMarcaModulo {

    private MarcaFragment mView;
    private MarcaAdapterRecyclerImpl mAdapter;
    public Context mContext;


    public DIMarcaModulo(MarcaFragment view, MarcaAdapterRecyclerImpl adapter){
        this.mView = view;
        this.mAdapter = adapter;
        this.mContext = view.getActivity().getBaseContext();
        Log.i("app", "DIMarcaModulo instanciado");
        if(adapter==null){
            Log.i("app", "DIMarcaModulo - adapter : nao instanciado");
        }else{
            Log.i("app", "DIMarcaModulo - adapter : instancied");
        }
    }


    @Provides
    @PerFragment
    IMarcaAPI provideMarcaApi(Retrofit retrofit){
        Log.i("app", "DIMarcaModulo provideMarcaApi");
        return retrofit.create(IMarcaAPI.class);
    }


    @Provides
    @PerFragment
    BaseView provideMarcaFragment(){
        Log.i("app", "DIMarcaModulo provideMarcaFragment");
        return mView;
    }


    @Provides
    @PerFragment
    IMarcaAdapterModel provideMarcaAdapterModel(){
        Log.i("app", "DIMarcaModulo provideMarcaAdapterModel");
        return mAdapter;
    }


    @Provides
    @PerFragment
    IMarcaPresenter provideMarcaPresenter(){
        Log.i("app", "DIMarcaModulo provideMarcaPresenter");
        return new MarcaPresenterImpl();
    }


    @Provides
    @PerFragment
    ControllerServiceAPIRest provideControllerServiceAPIRest(){
        Log.i("app", "DIMarcaModulo ControllerServiceAPIRest");
        return new ControllerServiceAPIRest();
    }

    @Provides
    @PerFragment
    Context exposeContext() {
        return mContext;
    }

}

-DIMarcaComponent-

@PerFragment
@Subcomponent(modules = {DIMarcaModulo.class})
public interface DIMarcaComponent extends AndroidInjector<MarcaFragment> {

   @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<MarcaFragment>{

   }

}

-MarcaFragment-

public class MarcaFragment extends BaseFragment implements IMarcaView, HasFragmentInjector{


    private MarcaAdapterRecyclerImpl mMarcaAdapter;
    private LinearLayoutManager llm;
    private View view = null;


    @Inject
    DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;

    @Inject
    public IMarcaPresenter mMarcaPresenter;

    @BindView(R.id.rv_marca)
    protected RecyclerView mRecyclerMarca;
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {



        if(view==null){
             view = inflater.inflate(R.layout.fragment_marca, container, false);
         }
         setUnBinder(ButterKnife.bind(this, view));

        return view;
    }

    @Override
    protected void onViewReady(Bundle saveInstanceState, Intent intent) {
        initializeRecyclerMarca();
        super.onViewReady(saveInstanceState, intent);

        if(mMarcaPresenter != null){
            Log.i("app", "MarcaFragment - Presenter nao esta vazio");
        }else{
            Log.i("app", "MarcaFragment - Presenter esta vazio");
        }

        mMarcaPresenter.initSerice();
    }

    private void initializeRecyclerMarca() {
        if(mMarcaAdapter==null){
            mMarcaAdapter = new MarcaAdapterRecyclerImpl();
        }

        mRecyclerMarca.setHasFixedSize(true);
        llm = new LinearLayoutManager(getActivity().getBaseContext());
        llm.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerMarca.setLayoutManager(llm);
        mRecyclerMarca.setAdapter(mMarcaAdapter);
    }


    @Override
    public void onOpenVehicleFragmentByMarcaClicked(Marca marca) {
        // recebendo o item clicado para chamar proxima tela com a Marca clicada.
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        AndroidInjection.inject(this);
    }

    @Override
    public AndroidInjector<Fragment> fragmentInjector() {
        return fragmentDispatchingAndroidInjector;
    }
}

-MarcaPresenterImpl-

public class MarcaPresenterImpl extends BasePresenter<IMarcaView> implements IMarcaPresenter{

    private static final String TAG_MARCA_PRESENTER = "MarcaPresenterImpl";

    @Inject
    public IMarcaAdapterModel mMarcaAdapterModel;

    @Inject
    public ControllerServiceAPIRest mControllerServiceAPIRest;



    @Inject
    public MarcaPresenterImpl(){

    }

    @Override
    public void onShowMessage(String message) {
        getView().onShowMessage(message);
    }

    @Override
    public void onHideMessage() {
        getView().onHideMessage();
    }

    @Override
    public void onShowProgress(String message) {
        getView().onShowProgress(message);
    }

    @Override
    public void onHideProgress() {
        getView().onHideProgress();
    }

    @Override
    public void onShowToast(String message) {
        getView().onShowToast(message);
    }

    public void refresh() {
        mMarcaAdapterModel.refresh();
    }

    @Override
    public void refreshItem(int id) {

    }

    @Override
    public void listMarcaByServiceForView(List<Marca> listMarca) {
        mMarcaAdapterModel.setListMarca(listMarca);
    }

    @Override
    public void initSerice() {
        mControllerServiceAPIRest.getMarca();
    }

    @Override
    public void getMarcaClicked(@NonNull Marca marca) {
        getView().onOpenVehicleFragmentByMarcaClicked(marca);
    }


}

-ControllerServiceAPIRest-

public class ControllerServiceAPIRest implements Observer<List<Marca>> {

    @Inject
    public IMarcaPresenter mPresenter;

    @Inject
    public IMarcaAPI mMarcaAPI;

    private List<Marca> listMarca;

    @Inject
    public ControllerServiceAPIRest() {

    }



    protected void getMarca(){
        if(listMarca == null){
            listMarca = new ArrayList<>();
        }
        mPresenter.onShowProgress("Carregando Marca de Veículos...");

        Observable<List<Marca>> cakesResponseObservable = mMarcaAPI.getAllMarca();
        subscribe(cakesResponseObservable, this);
    }

    private void subscribe(Observable<List<Marca>> observable, Observer<List<Marca>> observer){

        observable.subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(observer);
    }


    @Override
    public void onError(Throwable e) {
        mPresenter.onHideMessage();
        mPresenter.onHideProgress();
        mPresenter.onShowToast("Erro ao carregar Marcas!");
        Log.e("app", "Falha no carregamento de Marcas: " + e.getMessage());
        new Exception(e.getMessage());
    }

    @Override
    public void onComplete() {
        mPresenter.onHideMessage();
        mPresenter.onHideProgress();

        Log.i("app", "ControllerServiceAPIRest - listMarca Position 0: " + listMarca.get(0));
        if (listMarca==null && !listMarca.isEmpty() && listMarca.get(0)!=null){
            mPresenter.listMarcaByServiceForView(listMarca);
            mPresenter.onShowToast("Marcas carregadas!");
        }else{
            mPresenter.onShowToast("Lista não foi carregada!");
        }

    }

    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(List<Marca> list) {
        listMarca = list;
    }
}

Solution

  • You require your Module:

    @PerFragment
    @Subcomponent(modules = {DIMarcaModulo.class})
    public interface DIMarcaComponent extends AndroidInjector<MarcaFragment> {
    

    And Dagger can't create it, because it doesn't have a public parameterless constructor:

    @Module
    public class DIMarcaModulo {
        // ...
        public DIMarcaModulo(MarcaFragment view, MarcaAdapterRecyclerImpl adapter){
    

    But you bind in your Builder directly:

    @Module(subcomponents = {DIMarcaComponent.class})
    public abstract class ViewBuilderModule {
    
        @Binds
        @IntoMap
        @FragmentKey(MarcaFragment.class)
        abstract AndroidInjector.Factory<? extends Fragment>
            bindMarcaFragment(DIMarcaComponent.Builder bulder);
    }
    

    So when dagger.android tries to create your object:

    // AndroidInjector.Builder
    abstract class Builder<T> implements AndroidInjector.Factory<T> {
      @Override
      public final AndroidInjector<T> create(T instance) {
        seedInstance(instance);
        return build();
      }
    

    It notices that you haven't provided a DIMarcaModulo instance and fails. You'll need to follow the advice in the SO question Dagger 2.10 Android subcomponents and builders, which means either giving DIMarcaModulo a public parameterless constructor that can inject MarcaFragment, or by overriding DIMarcaComponent.Builder#seedInstance:

    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<MarcaFragment> {
    
      // This method tells Dagger you need to supply your own DIMarcoModulo.
      public abstract void diMarcoModulo(DIMarcaModulo modulo);
    
      // dagger.android calls this method automatically, but only this method, so
      // you'll need to call diMarcoModulo from it.
      @Override public void seedInstance(MarcaFragment fragment) {
        diMarcoModulo(fragment, fragment.getMMarcaAdapter());
        bindMarcaFragment(fragment);  // OPTIONAL: See below
      }
    
      // If you want MarcaFragment to remain injectable, you might need to call into
      // a different @BindsInstance method you define, because you've prevented
      // seedInstance from doing that for you.
      @BindsInstance public abstract void bindMarcaFragment(MarcaFragment fragment);
    }
    

    As you can see, MarcaFragment will automatically be available in the graph that DIMarcoModulo installs, so if you can avoid the constructor parameters and instead receive the fragment as a parameter to @Provides methods, your code might be easier to read. You also will have trouble with the method I called fragment.getMMarcaAdapter(), because you inject onAttach and you get access to the RecyclerView onCreateView. However, this shouldn't be a big problem if you remove the constructor parameters and if you can make sure you don't try to access the RecyclerView until after Android has a chance to create it.