Retrofit2 MVP Android

Я использую модификацию в своем проекте, и мне интересно, есть ли способ разделить вызовы API на разные классы, например: Только активность входа /api/users/login Только активность фильмов /api/movies/all I have все на одном и том же интерфейсе, и я вижу, что это не очень хороший подход ... не могли бы вы подсказать мне, как сделать его чище, пожалуйста? Я использую архитектуру MVP, чтобы сделать ее чистой.

Это мой NetworkService.class

public class NetworkService {

    private NetworkAPI networkAPI;
    private OkHttpClient okHttpClient;
    private LruCache<Class<?>, Observable<?>> apiObservables;

    public NetworkService() {
        this(BASE_URL);
    }

    public NetworkService(String baseUrl) {
        okHttpClient = buildClient();
        apiObservables = new LruCache<>(10);

        Gson gson = new GsonBuilder()
                .setLenient()
                .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
                .create();
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        // set your desired log level
        //logging.setLevel(Level.BASIC);
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);

        Builder httpClient = new Builder()
                .connectTimeout(100, TimeUnit.SECONDS)
                .readTimeout(100, TimeUnit.SECONDS);

        httpClient.addInterceptor(logging);
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .client(httpClient.build())
                .build();

        networkAPI = retrofit.create(NetworkAPI.class);
    }

    /**
     * Method to return the API interface.
     *
     * @return
     */
    public NetworkAPI getAPI() {
        return networkAPI;
    }


    /**
     * Method to build and return an OkHttpClient so we can set/get
     * headers quickly and efficiently.
     *
     * @return
     */
    public OkHttpClient buildClient() {

        Builder builder = new Builder();

        builder.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Response response = chain.proceed(chain.request());
                // Do anything with response here
                //if we want to grab a specific cookie or something..
                return response;
            }
        });

        builder.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                //this is where we will add whatever we want to our request headers.
                Request request = chain.request().newBuilder().addHeader("Accept", "application/json").build();
                return chain.proceed(request);
            }
        });

        return builder.build();
    }

    /**
     * Method to clear the entire cache of observables
     */
    public void clearCache() {
        apiObservables.evictAll();
    }
}

И у моего NetworkAPI.class есть это

public interface NetworkAPI {


    @POST(LOGIN)
    Call<LoginResponse> login(@Body LoginRequest loginRequest);
    //And more calls...
}

Вы знаете, ребята, если я могу сделать это чище?


person StuartDTO    schedule 30.08.2018    source источник
comment
Я бы действительно посоветовал вам взглянуть на некоторые проекты с открытым исходным кодом. На GitHub есть тысячи проектов с этим. Удачи!   -  person Sunny    schedule 30.08.2018


Ответы (3)


Сделайте экземпляр retrofit singleton, после чего вы сможете создавать сервисы для фильмов входа в систему, например

   LoginService login =  retrofit.create(LoginService.class);
   login.login(loginRequest)

   MovieService movie =  retrofit.create(MovieService.class);
   movie.movies(movieRequest)

Вот так выглядит ваш интерфейс

public interface LoginService {


    @POST(LOGIN)
    Call<LoginResponse> login(@Body LoginRequest loginRequest);
    //And more calls...
}

public interface MovieService {


    @POST(MOVIE)
    Call<MovieResponse> movies(@Body MovieRequest movieRequest);
    //And more calls...
}
person Sunny    schedule 30.08.2018
comment
Как я могу изменить NetworkService, чтобы сделать его чистым? Я тебя понял с интерфейсами, это хороший момент, но NetworkService это хорошо? - person StuartDTO; 30.08.2018
comment
Также иногда я вижу, что люди делают свои собственные .addConverterFactory(), должен ли я? какова цель этого? - person StuartDTO; 30.08.2018
comment
Это не хорошо. RetrofitBuilder нуждается в базовом URL-адресе (который отличается для каждого интерфейса API), у вас не может быть такой вещи, как retrofit instance singleton, если вы не передадите полный URL-адрес для каждого API. - person Mạnh Quyết Nguyễn; 30.08.2018
comment
Эта цель состоит в том, чтобы преобразовать ваши данные в json в объект. например, если вы хотите использовать rxjava, вам нужно использовать адаптер для этого, для сопрограммы kotlin вам нужен другой адаптер - person Sunny; 30.08.2018
comment
У вас нет синглтона экземпляра retrofit, и я также не знаю, какого типа и как вы его создаете. - person Mạnh Quyết Nguyễn; 30.08.2018
comment
базовый URL-адрес будет таким же, я думаю, как localhost:8080/api/v1 В классе обслуживания он может добавить /логин или /фильм - person Sunny; 30.08.2018
comment
Что произойдет, если вы вызовете другой API с другой базой? Например: из Stackoverflow? Предположим, что все API имеют один и тот же базовый URL-адрес, что делает ваш код очень хрупким. - person Mạnh Quyết Nguyễn; 30.08.2018
comment
тогда ему нужен еще один модифицированный объект с этим URL-адресом. или измените URL-адрес в синглтоне. Но я не думаю, что мне когда-либо понадобилось так много API, также использование singleton будет намного быстрее, чем создание объекта Retrofit в каждом действии и фрагменте. - person Sunny; 30.08.2018
comment
Да, именно поэтому вам нужен singleton RetrofitBuilder, а не singleton Retrofit. - person Mạnh Quyết Nguyễn; 30.08.2018

Одна вещь определенно нуждается в оптимизации: вы должны повторно использовать OkHttpClient вместо создания одного для каждого API.

Создание модифицированного API также должно быть преобразовано в служебный класс:

public class RetrofitFactory {

    private static OkHttpClient baseClient = new Builder().addInterceptor(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Response response = chain.proceed(chain.request());
            // Do anything with response here
            //if we want to grab a specific cookie or something..
            return response;
        }
    }).addInterceptor(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            //this is where we will add whatever we want to our request headers.
            Request request = chain.request().newBuilder().addHeader("Accept", "application/json").build();
            return chain.proceed(request);
        }
    }).build();

    private static Gson gson = new GsonBuilder()
            .setLenient()
            .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
            .create();

    private static Retrofit.Builder baseRetrofitBuilder = new Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create(gson));


    public Builder baseClientBuilder() {
        return baseClient.newBuilder();
    }

    public static <T> T createApi(String url, Class<T> apiClass) {
        return baseRetrofitBuilder.baseUrl(url).build().create(apiClass);
    }

    public static <T> T createApi(String url, Class<T> apiClass, OkHttpClient client) {
        return baseRetrofitBuilder.baseUrl(url).client(client).build().create(apiClass);
    }
}

Чтобы создать NetworkAPI с дополнительным перехватчиком, вы должны сделать:

    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    // set your desired log level
    //logging.setLevel(Level.BASIC);
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);

    OkHttpClient httpClient = RetrofitFactory.baseClientBuilder()
            .connectTimeout(100, TimeUnit.SECONDS)
            .readTimeout(100, TimeUnit.SECONDS)
            .addInterceptor(logging);
    networkApi = RetrofitFactory.createApi(url, NetworkApi.class, httpClient);

Чтобы создать XApi с настройкой по умолчанию, выполните следующие действия:

    xApi = RetrofitFactory.createApi(url, XApi.class);
person Mạnh Quyết Nguyễn    schedule 30.08.2018
comment
Да, но поскольку я делаю чистый код, вы не думаете, что это плохой подход? - person StuartDTO; 30.08.2018
comment
В вашем коде есть проблема: OkHttpClient / Gson необходимо использовать совместно для повышения производительности. Код для создания API находится внутри сервиса, что не очень хорошо (поскольку, например, если вы хотите добавить еще один общий перехватчик во все API, вам следует переписать все остальные места). Рефакторинг в служебный класс RetrofitFactory, такой как мой, чтобы сделать его чистым. - person Mạnh Quyết Nguyễn; 30.08.2018
comment
Не могли бы вы опубликовать пример того, что вы говорите? - person StuartDTO; 30.08.2018
comment
Извините, но хорошо ли использовать статические методы? Нет ли другого способа сделать это? - person StuartDTO; 30.08.2018
comment
И каждый раз, когда я хочу сделать вызов, например войти в систему или получить фильмы, я должен вызывать KttpLogginInterceptor и OkHttpClient? а затем создать networkApi с помощью retrofitFactory? - person StuartDTO; 30.08.2018
comment
Почему вы воздерживаетесь от статического метода? Статический метод отлично подходит для программирования, так как почти гарантирует неизменность поведения метода. - person Mạnh Quyết Nguyễn; 30.08.2018
comment
Вы создаете его только один раз в своем сервисе. Если вам нужно поделиться им между несколькими классами сервисов, тогда инициализируйте его статический конечный экземпляр и поделитесь - person Mạnh Quyết Nguyễn; 30.08.2018
comment
Итак, пожалуйста, не могли бы вы сказать мне, например, куда я могу позвонить тем, кто находится в разделе «Вход в систему» ​​и «Фильмы»? Или на каждом ведущем я должен это делать? один для loginPresenter и другой для фильмовPresenter? - person StuartDTO; 30.08.2018
comment
Если у вас есть контейнер (например, Spring), вы можете позволить контейнеру инициализировать экземпляр для вас. Если у вас его нет, то инициализируйте статический экземпляр в этом интерфейсе: public interface A { ...your api definition... A instance = RetrofitFactory.createApi(url, A.class); } И ссылайтесь на него через: A.instance. Надеюсь, теперь понятно, так как это довольно просто - person Mạnh Quyết Nguyễn; 30.08.2018
comment
Что вы имеете в виду под таким контейнером, как Spring? если у меня его нет, я могу вызывать этот код каждый раз, когда хочу позвонить, верно? - person StuartDTO; 30.08.2018
comment
Кроме того, я могу разделить интерфейс на два? один для входа в систему, а другой для фильмов? если да, то как я могу позвонить им? потому что я этого не понимаю : xApi = RetrofitFactory.createApi(url, XApi.class); - person StuartDTO; 30.08.2018
comment
Один вопрос, что такое HttpLoggingInterceptor, могу ли я поместить его в тот же класс, что и Retrofit? - person StuartDTO; 07.09.2018

Возможно, вы могли бы создать разные интерфейсы и изменить свой NetworkService для создания адаптера Retrofit в зависимости от интерфейса, который вы хотели бы использовать в разных случаях.

Покажу идею:

Интерфейсы:

  public interface LoginInterface { 

        @POST(LOGIN) 
        Call<LoginResponse> login(@Body LoginRequest loginRequest); 
    }

    public interface MovieInterface {

        @GET(MOVIE) 
        Call<MovieResponse> getMovies(); 
    }

Класс NetworkService

Здесь вы можете увидеть Singleton для NetworkService и метод, созданный с помощью этого метода, вы можете создавать разные интерфейсы, используя экземпляр singleton:

public class NetworkService {
    private final String API_BASE_URL = "";

    private static NetworkService INSTANCE = null;
    private static Retrofit retrofit;

    private OkHttpClient okHttpClient;
    private LruCache<Class<?>, Observable<?>> apiObservables;


    public NetworkService() {
        createInstance();
    }

    public static NetworkService getInstance() {

        if(INSTANCE == null){
            return new NetworkService();
        }

        return INSTANCE;
    }

    private void createInstance(){
        okHttpClient = buildClient();
        apiObservables = new LruCache<>(10);

        Gson gson = new GsonBuilder()
                .setLenient()
                .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
                .create();

        retrofit = new Retrofit.Builder()
                .baseUrl(API_BASE_URL)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .client(httpClient.build())
                .build();


        }

    public static <T> T create(Class<T> apiClass) {
        return retrofit.create(apiClass);
    }

    private OkHttpClient buildClient() {

        HttpLoggingInterceptor loggingInterceptor = new     HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.addInterceptor(loggingInterceptor)
                .connectTimeout(100, TimeUnit.SECONDS)
                .readTimeout(100, TimeUnit.SECONDS);

        builder.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Response response = chain.proceed(chain.request());
                // Do anything with response here
                //if we want to grab a specific cookie or something..
                return response;
            }
        });

        builder.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                //this is where we will add whatever we want to our request headers.
                Request request = chain.request().newBuilder().addHeader("Accept", "application/json").build();
                return chain.proceed(request);
            }
        });


        return builder.build();
    }

    /**
     * Method to clear the entire cache of observables
     */
    public void clearCache() {
        apiObservables.evictAll();
    }
}

И затем вы можете использовать его, используя модифицированный NetworService, чтобы создать экземпляр интерфейса, который вы хотите использовать:

Создание экземпляра NetwService:

public class LoginActiviy extends Activity {

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

            //Instantiation of LoginInterface
            LoginInterface loginInterface = 
            NetworkService.getInstance().create(LoginInterface.class);
            //Then we use the method for login
            loginInterface.login(/* Handling the callback here*/);

            }
        }

public class MoviesActivity extends Activity {

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

            //Instantiation of MovieInterface
            MovieInterface movieInterface = 
            NetworkService.getInstance().create(MovieInterface.class);
            //Then we use the method for login
            loginInterface.getMovies(/* Handling the callback here*/);

            }
        }

Я надеюсь, что это может помочь вам получить лучшее представление.

person Juanjo Berenguer    schedule 30.08.2018
comment
А как насчет класса NetworkService? Как я могу улучшить @Juanjo Berenguer? - person StuartDTO; 30.08.2018
comment
Хороший! Пожалуйста, не могли бы вы объяснить разницу между вашим ответом и ответом Mạnh Quyết Nguyễn, пожалуйста? - person StuartDTO; 31.08.2018
comment
Привет @StuartDTO. Основное отличие состоит в том, что я реализую шаблон Singleton для создания класса NetworkService, это означает, что у меня всегда будет уникальный экземпляр этого класса, и я буду создавать объекты в методе createInstance() один раз (в первый раз, когда INSTANCE имеет значение null). Затем мы вызываем метод create с различными необходимыми интерфейсами. - person Juanjo Berenguer; 31.08.2018