本文介绍了使用livedata,retrofit,mvvm和存储库模式制作通用网络适配器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

限时删除!!

我是android架构组件的新手,我正在尝试将LiveData和ViewModels与mvvm,存储库模式和改造一起使用.谷歌在其架构指南中提到了GitHubSample,但想根据我的需要对其进行简化.下面是我到目前为止拥有的代码,但是在完成它时存在以下问题.

I am new to android architecture components and I am trying to use LiveData and ViewModels with mvvm, repository pattern and retrofit. Referred to GitHubSample google gave in its architecture guide but want to simplify it little bit for my needs. Below is the code which I had so far but having below problems in completing it.

    LiveDataCallAdapter中的
  1. onActive()方法根本没有调用
  2. 无法弄清楚如何在SettingsData类中将响应作为LiveData获得(总是将其获得为null)?理想情况下,我只想拥有成功和失败的侦听器,并且应该在这些块中获取数据.在上这堂课之前,所有常规的网络错误都应该已经得到处理.我不知道该怎么做.3.我不想在这个SettingsData类中调用.入队,很多示例都显示了
  1. onActive() method in LiveDataCallAdapter is not invoking at all
  2. Not able to figure out how I can get the response as a LiveData(I get this as null always) in SettingsData class? Ideally here I just want to have success and failure listener and I should get the data inside these blocks. All the generic network errors should already be handled before coming to this class. I am not able to figure out how to do this.3.I do not want to call.enqueue in this SettingsData class which many examples shows

任何帮助将不胜感激.预先感谢

Any help is greatly appreciated. Thanks in advance

//Activity
private fun loadApplicationSettings() {

        val settingsViewModel = ViewModelProviders.of(this).get(SettingsViewModel::class.java)
        settingsViewModel.userApplicationSettings.observe(this, Observer<UserApplicationSettings> { userApplicationSettingsResult ->

            Log.d("UserApplicationSettings", userApplicationSettingsResult.toString())
            userSettingsTextView.text = userApplicationSettingsResult.isPushNotificationEnabled
        })
    }

//ViewModel
class SettingsViewModel : ViewModel() {

    private var settingsRepository: SettingsRepository
    lateinit var userApplicationSettings: LiveData<UserApplicationSettings>

    init {
        settingsRepository = SettingsRepository()
        loadUserApplicationSettings()
    }

    private fun loadUserApplicationSettings() {
        userApplicationSettings = settingsRepository.loadUserApplicationSettings()
    }
}

//Repository
class SettingsRepository {

    val settingsService = SettingsData()

    fun loadUserApplicationSettings(): LiveData<UserApplicationSettings> {
        return settingsService.getUserApplicationSettings()
    }
}

//I do not want to do the network calls in repository, so created a seperate class gets the data from network call
class SettingsData {

    val apiBaseProvider = ApiBaseProvider()

    fun getUserApplicationSettings(): MutableLiveData<UserApplicationSettings> {

        val userApplicationSettingsNetworkCall = apiBaseProvider.create().getApplicationSettings()

        //Not sure how to get the data from userApplicationSettingsNetworkCall and convert it to livedata to give to repository
        // deally here I just want to have success and failure listener and I should get the data inside these blocks. All the generic network errors should already be handled before coming to this class. I am not able to figure out how to do this.

        val userApplicationSettingsData: LiveData<ApiResponse<UserApplicationSettings>> = userApplicationSettingsNetworkCall

     //Thinking of having a success and fail block here and create a LiveData object to give to repository. Not sure how to do this

        return userApplicationSettingsData
    }
}

//Settings Service for retrofit
interface SettingsService {

    @GET("url")
    fun getApplicationSettings(): LiveData<ApiResponse<UserApplicationSettings>>
}

//Base provider of retrofit
class ApiBaseProvider {

    fun create(): SettingsService {

        val gson = GsonBuilder().setLenient().create()
        val okHttpClient = createOkHttpClient()

        val retrofit = Retrofit.Builder()
            .addCallAdapterFactory(LiveDataCallAdapterFactory())
            .addConverterFactory(GsonConverterFactory.create(gson))
            .baseUrl("url")
            .build()

        return retrofit.create(SettingsService::class.java)
    }
}

//
class LiveDataCallAdapterFactory : Factory() {

    override fun get(
        returnType: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): CallAdapter<*, *>? {
        if (getRawType(returnType) != LiveData::class.java) {
            return null
        }
        val observableType = getParameterUpperBound(0, returnType as ParameterizedType)
        val rawObservableType = getRawType(observableType)
        if (rawObservableType != ApiResponse::class.java) {
            throw IllegalArgumentException("type must be a resource")
        }
        if (observableType !is ParameterizedType) {
            throw IllegalArgumentException("resource must be parameterized")
        }
        val bodyType = getParameterUpperBound(0, observableType)
        return LiveDataCallAdapter<Any>(bodyType)
    }
}

//Custom adapter that does the network call
class LiveDataCallAdapter<T>(private val responseType: Type) : CallAdapter<T, LiveData<ApiResponse<T>>> {

    override fun responseType(): Type {
        return responseType
    }

        override fun adapt(call: Call<T>): LiveData<ApiResponse<T>> {

        return object : LiveData<ApiResponse<T>>() {
            override fun onActive() {
                super.onActive()

                call.enqueue(object : Callback<T> {
                    override fun onResponse(call: Call<T>, response: Response<T>) {
                        println("testing response: " + response.body())
                        postValue(ApiResponse.create(response))

                    }

                    override fun onFailure(call: Call<T>, throwable: Throwable) {
                        postValue(ApiResponse.create(throwable))
                    }
                })
            }
        }
    }
}


//I want to make this class as a generic class to do all the network success and error handling and then pass the final response back
/**
 * Common class used by API responses.
 * @param <T> the type of the response object
</T> */
sealed class ApiResponse<T> {

    companion object {

        fun <T> create(error: Throwable): ApiErrorResponse<T> {
            return ApiErrorResponse(error.message ?: "unknown error")
        }

        fun <T> create(response: Response<T>): ApiResponse<T> {

            println("testing api response in create")

            return if (response.isSuccessful) {
                val body = response.body()
                if (body == null || response.code() == 204) {
                    ApiEmptyResponse()
                } else {
                    ApiSuccessResponse(
                        body = body
                    )
                }
            } else {
                val msg = response.errorBody()?.string()
                val errorMsg = if (msg.isNullOrEmpty()) {
                    response.message()
                } else {
                    msg
                }
                ApiErrorResponse(errorMsg ?: "unknown error")
            }
        }
    }
}

/**
 * separate class for HTTP 204 responses so that we can make ApiSuccessResponse's body non-null.
 */
class ApiEmptyResponse<T> : ApiResponse<T>()

data class ApiErrorResponse<T>(val errorMessage: String) : ApiResponse<T>()

data class ApiSuccessResponse<T>(
    val body: T
) : ApiResponse<T>() {
}

推荐答案

我们可以如下连接Activity/Fragment和ViewModel:

We can connect Activity/Fragment and ViewModel as below:

首先,我们必须创建用于处理改造响应的 ApiResource .

Firstly, we have to create our ApiResource which will handle the retrofit response.

public class ApiResource<T> {

    @NonNull
    private final Status status;

    @Nullable
    private final T data;

    @Nullable
    private final ErrorResponse errorResponse;

    @Nullable
    private final String errorMessage;

    private ApiResource(Status status, @Nullable T data, @Nullable ErrorResponse errorResponse, @Nullable String errorMessage) {
        this.status = status;
        this.data = data;
        this.errorResponse = errorResponse;
        this.errorMessage = errorMessage;
    }

    public static <T> ApiResource<T> create(Response<T> response) {
        if (!response.isSuccessful()) {
            try {
                JSONObject jsonObject = new JSONObject(response.errorBody().string());
                ErrorResponse errorResponse = new Gson()
                    .fromJson(jsonObject.toString(), ErrorResponse.class);
                return new ApiResource<>(Status.ERROR, null, errorResponse, "Something went wrong.");
            } catch (IOException | JSONException e) {
                return new ApiResource<>(Status.ERROR, null, null, "Response Unreachable");
            }
        }
        return new ApiResource<>(Status.SUCCESS, response.body(), null, null);
    }

    public static <T> ApiResource<T> failure(String error) {
        return new ApiResource<>(Status.ERROR, null, null, error);
    }

    public static <T> ApiResource<T> loading() {
        return new ApiResource<>(Status.LOADING, null, null, null);
    }

    @NonNull
    public Status getStatus() {
        return status;
    }

    @Nullable
    public T getData() {
        return data;
    }

    @Nullable
    public ErrorResponse getErrorResponse() {
        return errorResponse;
    }

    @Nullable
    public String getErrorMessage() {
        return errorMessage;
    }
}

状态只是一个Enum class,如下所示:

The Status is just an Enum class as below:

public enum Status {
    SUCCESS, ERROR, LOADING
}

ErrorResponse 类的创建方式必须使getter和setter可以处理错误.

The ErrorResponse class must be created in such a way that the getter and setter can handle the error.

RetrofitLiveData类

public class RetrofitLiveData<T> extends LiveData<ApiResource<T>> {

    private Call<T> call;

    public RetrofitLiveData(Call<T> call) {
        this.call = call;
        setValue(ApiResource.loading());
    }

    Callback<T> callback = new Callback<T>() {
        @Override
        public void onResponse(Call<T> call, Response<T> response) {
            setValue(ApiResource.create(response));
        }

        @Override
        public void onFailure(Call<T> call, Throwable t) {
            setValue(ApiResource.failure(t.getMessage()));
        }
    };

    @Override
    protected void onActive() {
        super.onActive();
        call.enqueue(callback);
    }

    @Override
    protected void onInactive() {
        super.onInactive();
        if (!hasActiveObservers()) {
            if (!call.isCanceled()) {
                call.cancel();
            }
        }
    }

}

存储库类

public class Repository {

    public LiveData<ApiResource<JunoBalanceResponse>> getJunoBalanceResponse(Map<String, String> headers) {
        return new RetrofitLiveData<>(ApiClient.getJunoApi(ApiClient.BASE_URL.BASE).getJunoBalance(headers));
    }

}

下面是api接口的示例.

below is an example for the api interface.

public interface JunoApi {

    @Headers({"X-API-Version: 2"})
    @GET("balance")
    Call<JunoBalanceResponse> getJunoBalance(@HeaderMap Map<String, String> headers);

}

ApiClient类

public class ApiClient {

    public enum BASE_URL {
        AUTH, BASE
    }

    private static Retrofit retrofit;

    private static final String JUNO_SANDBOX_AUTH_URL = "https://sandbox.boletobancario.com/authorization-server/";
    private static final String JUNO_SANDBOX_BASE_URL = "https://sandbox.boletobancario.com/api-integration/";

    private static Retrofit getRetrofit(String baseUrl) {

        OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
            .connectTimeout(90, TimeUnit.SECONDS)
            .readTimeout(90, TimeUnit.SECONDS)
            .writeTimeout(90, TimeUnit.SECONDS)
            .build();

        retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        return retrofit;
    }

    public static JunoApi getJunoApi(BASE_URL targetPath) {
        switch (targetPath) {
            case AUTH: return getRetrofit(JUNO_SANDBOX_AUTH_URL).create(JunoApi.class);
            case BASE: return getRetrofit(JUNO_SANDBOX_BASE_URL).create(JunoApi.class);
            default: return getRetrofit(JUNO_SANDBOX_BASE_URL).create(JunoApi.class);
        }
    }
}

现在,我们可以连接我们的存储库 ApiViewModel .

Now we can connect our Repository and ApiViewModel.

public class ApiViewModel extends ViewModel {

    private Repository repository = new Repository();

    public LiveData<ApiResource<JunoBalanceResponse>> getJunoBalanceResponse(Map<String, String> headers) {
        return repository.getJunoBalanceResponse(headers);
    }
}

最后,我们可以在活动/片段"中观察到改装响应

And finally, we can observe the retrofit response in our Activity/Fragment

 apiViewModel = ViewModelProviders.of(requireActivity()).get(ApiViewModel.class);
 apiViewModel.getJunoBalanceResponse(headers).observe(getViewLifecycleOwner(), new Observer<ApiResource<JunoBalanceResponse>>() {
    @Override
    public void onChanged(ApiResource<JunoBalanceResponse> response) {
        switch (response.getStatus()) {
            case LOADING:
                Log.i(TAG, "onChanged: BALANCE LOADING");
                break;
            case SUCCESS:
                Log.i(TAG, "onChanged: BALANCE SUCCESS");
                break;
            case ERROR:
                Log.i(TAG, "onChanged: BALANCE ERROR");
                break;
        }
    }
});

这篇关于使用livedata,retrofit,mvvm和存储库模式制作通用网络适配器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

1403页,肝出来的..

09-06 19:41