Dagger2 Binding Missing

Asked by At

This is the first time i'm using this library, even more i have downloaded the project and implemented with new dependencies. First of all, I'm getting cannot resolve symbol error on DaggerApplicationCompoment. And more details about error is:

error: [Dagger/MissingBinding] @javax.inject.Named("movieDB") retrofit2.Retrofit cannot be provided without an @Provides-annotated method.
@javax.inject.Named("movieDB") retrofit2.Retrofit is injected at
free.movies.freemovies.dagger.modules.HttpClientModule.provideFithubApi(restAdapter)
free.movies.freemovies.data.Api.TheMovieDbAPI is injected at
free.movies.freemovies.ui.main.MainFragment.mDbAPI
free.movies.freemovies.ui.main.MainFragment is injected at
free.movies.freemovies.dagger.components.ApplicationComponent.inject(free.movies.freemovies.ui.main.MainFragment)

This is module class:

package free.movies.freemovies.dagger.modules;

import android.app.Application;

import java.io.File;
import java.util.concurrent.TimeUnit;

import javax.inject.Named;

import dagger.Module;
import dagger.Provides;
import free.movies.freemovies.dagger.AppScope;
import free.movies.freemovies.data.Api.TheMovieDbAPI;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.moshi.MoshiConverterFactory;

/**
 * Created by <a href="mailto:[email protected]">Marcus Gabilheri</a>
 *
 * @author Marcus Gabilheri
 * @version 1.0
 * @since 9/4/16.
 */
@Module
public class HttpClientModule {

    private static final long DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB

    public static final String BACKDROP_URL = "http://image.tmdb.org/t/p/w1280";
    public static final String POSTER_URL = "http://image.tmdb.org/t/p/w500";
    public static final String API_URL = "https://api.themoviedb.org/3/";
    public static final String NOW_PLAYING = "movie/now_playing";
    public static final String LATEST = "movie/latest";
    public static final String POPULAR = "movie/popular";
    public static final String TOP_RATED = "movie/top_rated";
    public static final String UPCOMING = "movie/upcoming";
    public static final String MOVIE = "movie/";
    public static final String PERSON = "person/";
    public static final String DISCOVER = "discover/movie/";
    public static final String SEARCH_MOVIE = "search/movie/";
    public static final String TV = "tv/";

    @Provides
    @AppScope
    public OkHttpClient provideOkHttpClient(Application app) {
        File cacheDir = new File(app.getCacheDir(), "http");
        return new OkHttpClient.Builder()
                .readTimeout(1, TimeUnit.MINUTES)
                .connectTimeout(1, TimeUnit.MINUTES)
                .writeTimeout(1, TimeUnit.MINUTES)
                .cache(new okhttp3.Cache(cacheDir, DISK_CACHE_SIZE))
                .build();
    }

    @Provides
    @Named("TVDB") // Name is used in case a second Retrofit api is provided.
    @AppScope
    public Retrofit provideTVDBRestAdapter(MoshiConverterFactory moshiConverterFactory, OkHttpClient okHttpClient) {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        okHttpClient = okHttpClient.newBuilder()
                .addInterceptor(interceptor)
                .build();
        return new Retrofit.Builder()
                .baseUrl(API_URL)
                .client(okHttpClient)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(moshiConverterFactory)
                .build();
    }

    @Provides
    public TheMovieDbAPI provideFithubApi(@Named("movieDB") Retrofit restAdapter) {
        return restAdapter.create(TheMovieDbAPI.class);
    }

    @Provides
    @AppScope
    public MoshiConverterFactory provideMoshiConverterFactory() {
        return MoshiConverterFactory.create();
    }
}

And fragment where i'm using that module:

    package free.movies.freemovies.ui.main;

import android.os.Bundle;
import android.util.SparseArray;

import androidx.core.content.ContextCompat;
import androidx.leanback.app.BrowseSupportFragment;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.ListRowPresenter;
import androidx.leanback.widget.OnItemViewSelectedListener;
import androidx.leanback.widget.Presenter;
import androidx.leanback.widget.Row;
import androidx.leanback.widget.RowPresenter;

import javax.inject.Inject;

import free.movies.freemovies.App;
import free.movies.freemovies.Config;
import free.movies.freemovies.R;
import free.movies.freemovies.dagger.modules.HttpClientModule;
import free.movies.freemovies.data.Api.TheMovieDbAPI;
import free.movies.freemovies.data.models.Movie;
import free.movies.freemovies.data.models.MovieResponse;
import free.movies.freemovies.ui.base.GlideBackgroundManager;
import free.movies.freemovies.ui.movies.MoviePresenter;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;

/**
 * Created by <a href="mailto:[email protected]">Marcus Gabilheri</a>
 *
 * @author Marcus Gabilheri
 * @version 1.0
 * @since 10/8/16.
 */
public class MainFragment extends BrowseSupportFragment implements OnItemViewSelectedListener {

    @Inject
    TheMovieDbAPI mDbAPI;

    private GlideBackgroundManager mBackgroundManager;
    private CompositeDisposable compositeDisposable = new CompositeDisposable();

    private static final int NOW_PLAYING = 0;
    private static final int TOP_RATED = 1;
    private static final int POPULAR = 2;
    private static final int UPCOMING = 3;

    SparseArray<MovieRow> mRows;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        App.instance().appComponent().inject(this);

        // The background manager allows us to manage a dimmed background that does not interfere with the rows
        // It is the preferred way to set the background of a fragment
        mBackgroundManager = new GlideBackgroundManager(getActivity());

        // The brand color will be used as the background for the Headers fragment
        setBrandColor(ContextCompat.getColor(getActivity(), R.color.primary_transparent));
        setHeadersState(HEADERS_ENABLED);
        setHeadersTransitionOnBackEnabled(true);

        // The TMDB logo on the right corner. It is necessary to show based on their API usage policy
        setBadgeDrawable(ContextCompat.getDrawable(getActivity(), R.drawable.powered_by));

        createDataRows();
        createRows();
        prepareEntranceTransition();
        fetchNowPlayingMovies();
        fetchTopRatedMovies();
        fetchPopularMovies();
        fetchUpcomingMovies();
    }

    /**
     * Creates the data rows objects
     */
    private void createDataRows() {
        mRows = new SparseArray<>();
        MoviePresenter moviePresenter = new MoviePresenter();
        mRows.put(NOW_PLAYING, new MovieRow()
                .setId(NOW_PLAYING)
                .setAdapter(new ArrayObjectAdapter(moviePresenter))
                .setTitle("Now Playing")
                .setPage(1)
        );
        mRows.put(TOP_RATED, new MovieRow()
                .setId(TOP_RATED)
                .setAdapter(new ArrayObjectAdapter(moviePresenter))
                .setTitle("Top Rated")
                .setPage(1)
        );
        mRows.put(POPULAR, new MovieRow()
                .setId(POPULAR)
                .setAdapter(new ArrayObjectAdapter(moviePresenter))
                .setTitle("Popular")
                .setPage(1)
        );
        mRows.put(UPCOMING, new MovieRow()
                .setId(UPCOMING)
                .setAdapter(new ArrayObjectAdapter(moviePresenter))
                .setTitle("Upcoming")
                .setPage(1)
        );
    }

    /**
     * Creates the rows and sets up the adapter of the fragment
     */
    private void createRows() {
        // Creates the RowsAdapter for the Fragment
        // The ListRowPresenter tells to render ListRow objects
        ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
        for (int i = 0; i < mRows.size(); i++) {
            MovieRow row = mRows.get(i);
            // Adds a new ListRow to the adapter. Each row will contain a collection of Movies
            // That will be rendered using the MoviePresenter
            HeaderItem headerItem = new HeaderItem(row.getId(), row.getTitle());
            ListRow listRow = new ListRow(headerItem, row.getAdapter());
            rowsAdapter.add(listRow);
        }
        // Sets this fragments Adapter.
        // The setAdapter method is defined in the BrowseFragment of the Leanback Library
        setAdapter(rowsAdapter);
        setOnItemViewSelectedListener(this);
    }

    /**
     * Fetches now playing movies from TMDB
     */
    private void fetchNowPlayingMovies() {
        Disposable disposable = mDbAPI.getNowPlayingMovies(Config.API_KEY_URL, mRows.get(NOW_PLAYING).getPage())
                .subscribeOn(io.reactivex.schedulers.Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<MovieResponse>() {
                    @Override
                    public void accept(MovieResponse response) {
                        MainFragment.this.bindMovieResponse(response, NOW_PLAYING);
                        MainFragment.this.startEntranceTransition();
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable e) {
                        Timber.e(e, "Error fetching now playing movies: %s", e.getMessage());
                    }
                });

        compositeDisposable.add(disposable);
    }

    /**
     * Fetches the popular movies from TMDB
     */
    private void fetchPopularMovies() {
        Disposable disposable = mDbAPI.getPopularMovies(Config.API_KEY_URL, mRows.get(POPULAR).getPage())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<MovieResponse>() {
                    @Override
                    public void accept(MovieResponse response) {
                        MainFragment.this.bindMovieResponse(response, POPULAR);
                        MainFragment.this.startEntranceTransition();
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable e) {
                        Timber.e(e, "Error fetching popular movies: %s", e.getMessage());
                    }
                });

        compositeDisposable.add(disposable);
    }

    /**
     * Fetches the upcoming movies from TMDB
     */
    private void fetchUpcomingMovies() {
        Disposable disposable = mDbAPI.getUpcomingMovies(Config.API_KEY_URL, mRows.get(UPCOMING).getPage())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<MovieResponse>() {
                    @Override
                    public void accept(MovieResponse response) {
                        MainFragment.this.bindMovieResponse(response, UPCOMING);
                        MainFragment.this.startEntranceTransition();
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable e) {
                        Timber.e(e, "Error fetching upcoming movies: %s", e.getMessage());
                    }
                });

        compositeDisposable.add(disposable);
    }

    /**
     * Fetches the top rated movies from TMDB
     */
    private void fetchTopRatedMovies() {
        Disposable disposable = mDbAPI.getTopRatedMovies(Config.API_KEY_URL, mRows.get(TOP_RATED).getPage())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<MovieResponse>() {
                    @Override
                    public void accept(MovieResponse response) {
                        MainFragment.this.bindMovieResponse(response, TOP_RATED);
                        MainFragment.this.startEntranceTransition();
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable e) {
                        Timber.e(e, "Error fetching top rated movies: %s", e.getMessage());
                    }
                });

        compositeDisposable.add(disposable);
    }

    /**
     * Binds a movie response to it's adapter
     * @param response
     *      The response from TMDB API
     * @param id
     *      The ID / position of the row
     */
    private void bindMovieResponse(MovieResponse response, int id) {
        MovieRow row = mRows.get(id);
        row.setPage(row.getPage() + 1);
        for(Movie m : response.getResults()) {
            if (m.getPosterPath() != null) { // Avoid showing movie without posters
                row.getAdapter().add(m);
            }
        }
    }

    @Override
    public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
        // Check if the item is a movie
        if (item instanceof Movie) {
            Movie movie = (Movie) item;
            // Check if the movie has a backdrop
            if(movie.getBackdropPath() != null) {
                mBackgroundManager.loadImage(HttpClientModule.BACKDROP_URL + movie.getBackdropPath());
            } else {
                // If there is no backdrop for the movie we just use a default one
                mBackgroundManager.setBackground(ContextCompat.getDrawable(getActivity(), R.drawable.material_bg));
            }
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        compositeDisposable.dispose();
    }
}

I saw people had similar problem, but I don't understand what should I do to solve this. If someone could explain me or give me a hint what should I do.

1 Answers

0
Archie G. QuiƱones On

The error message states that there is no module that provides @Named("movieDB") Retrofit restAdapter for your

@Inject
TheMovieDbAPI mDbAPI;

to be injected since this is @Named("movieDB") Retrofit restAdapter it's dependency