[英]Making a generic network adapter using livedata, retrofit, mvvm and repository pattern
我是 android 架構組件的新手,我正在嘗試將 LiveData 和 ViewModels 與 mvvm、存儲庫模式和改造一起使用。 參考 GitHubSample google 在其架構指南中給出,但想根據我的需要稍微簡化它。 下面是我到目前為止的代碼,但在完成它時遇到了以下問題。
任何幫助是極大的贊賞。 提前致謝
//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:
首先,我們必須創建我們的ApiResource來處理改造響應。
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;
}
}
Status只是一個Enum class
,如下所示:
public enum Status {
SUCCESS, ERROR, LOADING
}
必須以 getter 和 setter 可以處理錯誤的方式創建ErrorResponse類。
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));
}
}
JunoBalanceResponse
包含我正在等待的對象及其 getter 和 setter,作為對我的改造請求的響應。
下面是 api 接口的示例。
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);
}
}
}
現在我們可以連接我們的Repository和ApiViewModel 。
public class ApiViewModel extends ViewModel {
private Repository repository = new Repository();
public LiveData<ApiResource<JunoBalanceResponse>> getJunoBalanceResponse(Map<String, String> headers) {
return repository.getJunoBalanceResponse(headers);
}
}
最后,我們可以在 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;
}
}
});
首先,請確保您的活動正在觀察settingsViewModel.userApplicationSettings
並正在調用loadApplicationSettings()
。
假設其余實現正確運行,那么您在SettingsData
要做的就是從ApiBaseProvider().create().getApplicationSettings()
返回ApiBaseProvider().create().getApplicationSettings()
因為它基於LiveDataCallAdapter
實現返回LiveData:
class SettingsData {
fun getUserApplicationSettings(): LiveData<UserApplicationSettings> =
ApiBaseProvider().create().getApplicationSettings()
}
因此,活動中的Observer<UserApplicationSettings>
將被通知,因為您已經訂閱了它。
另外,如果您看看LiveDataCallAdapter
:
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))
}
})
您可以看到適配器將成功和失敗發布到正在傾聽的任何人。
在仔細查看return object : LiveData<ApiResponse<T>>() { override fun onActive() {
處的LiveDataCallAdapter
之后return object : LiveData<ApiResponse<T>>() { override fun onActive() {
僅在活動觀察者的數量從0變為1時才調用onActive。
我從googlesamples的LiveDataCallAdapter.kt中挑選了一個,並注意到使用了未在此處實現的private var started = AtomicBoolean(false)
和if (started.compareAndSet(false, true))
,因此我想這是在激活Livedata.onActive
被觸發。
您也可以嘗試將其添加到邏輯中,如果您想進一步了解AtomicBoolean,請查看AtomicBoolean.compareAndSet(!flag,flag)?
您還可以檢查LiveData#onactive 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.