簡體   English   中英

使用 livedata、改造、mvvm 和存儲庫模式制作通用網絡適配器

[英]Making a generic network adapter using livedata, retrofit, mvvm and repository pattern

我是 android 架構組件的新手,我正在嘗試將 LiveData 和 ViewModels 與 mvvm、存儲庫模式和改造一起使用。 參考 GitHubSample google 在其架構指南中給出,但想根據我的需要稍微簡化它。 下面是我到目前為止的代碼,但在完成它時遇到了以下問題。

  1. LiveDataCallAdapter 中的 onActive() 方法根本沒有調用
  2. 無法弄清楚如何在 SettingsData 類中將響應作為 LiveData(我總是將其設為 null)? 理想情況下,我只想擁有成功和失敗的監聽器,我應該在這些塊中獲取數據。 在進入這個類之前,應該已經處理了所有的通用網絡錯誤。 我無法弄清楚如何做到這一點。 3.我不想在很多例子顯示的這個SettingsData類中調用.enqueue

任何幫助是極大的贊賞。 提前致謝

//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);
        }
    }
}

現在我們可以連接我們的RepositoryApiViewModel

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM