Создание универсального сетевого адаптера с использованием шаблонов liveata, retrofit, mvvm и репозитория - PullRequest
1 голос
/ 27 июня 2019

Я новичок в компонентах архитектуры Android и пытаюсь использовать LiveData и ViewModels с mvvm, шаблоном хранилища и модификацией. Ссылаясь на GitHubSample, Google дал в своем руководстве по архитектуре, но хочу немного упростить его для моих нужд. Ниже приведен код, который у меня был до сих пор, но у меня были проблемы с его заполнением.

  1. Метод onActive () в LiveDataCallAdapter вообще не вызывается
  2. Не можете понять, как я могу получить ответ в виде LiveData (я всегда получаю это значение как нулевое) в классе SettingsData? В идеале, здесь я просто хочу, чтобы слушатели имели успех и неудачу, и я должен получить данные внутри этих блоков. Все общие сетевые ошибки уже должны быть обработаны до прихода в этот класс. Я не могу понять, как это сделать. 3. Я не хочу вызывать .enqueue в этом классе SettingsData, который во многих примерах показывает

Любая помощь очень ценится. Заранее спасибо

//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>() {
}

1 Ответ

0 голосов
/ 27 июня 2019

Сначала убедитесь, что ваша активность отслеживает settingsViewModel.userApplicationSettings и вызывает loadApplicationSettings().

Если предположить, что оставшаяся часть реализации работает правильно, все, что вам нужно сделать в SettingsData, это вернуть вызов от ApiBaseProvider().create().getApplicationSettings() поскольку он возвращает LiveData на основе реализации LiveDataCallAdapter:

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

Вы можете видеть, что адаптер отправляет сообщение об успехе и неудаче тому, кто его слушает.

После внимательного изучения LiveDataCallAdapter на return object : LiveData<ApiResponse<T>>() { override fun onActive() { onActive вызывается только тогда, когда число активных наблюдателей изменяется с 1 на 0.

Я выбрал LiveDataCallAdapter.kt из googlesamples и заметил использование private var started = AtomicBoolean(false) и if (started.compareAndSet(false, true)), которые здесь не реализуются, поэтому я полагаю, что это активирует Livedata.onActive для запуска.

Вы также можете попробовать добавить это к своей логике, и если вы хотите узнать больше об AtomicBooleвзглянуть на AtomicBoolean.compareAndSet (! флаг, флаг)?

Вы также можете проверить LiveData # onactive .

...