Retrofit Observable response to LiveData object using subscribe

微笑、不失礼 提交于 2019-12-11 15:13:00

问题


I have inherited this project that was using retrofit incorrectly with LiveData by trying to convert responses to LiveData immediately. I am in the middle of trying to refactor, but am getting stuck with converting an RxJava Observable to a LiveData object in the ViewModel. Here is what I have so far:

Retrofit Service

interface Service {
  @POST("user/login")
  fun postLogin(@Body body: LoginBody) Observable<ApiResponseWrapper<LoginRemote>>
}

As you can see, Observable wraps an ApiResponseWrapper which wraps a LoginRemote. Here are those two classes

LoginRemote

class LoginRemote(@SerializedName("user") val user: User) {
    companion object {
        fun parseResponse(response: Response<LoginRemote>): LoginResponse {
            return if (response.isSuccessful) {
                response.body()!!.format()
            } else if (response.code() == 302) {
                // accept terms again
                LoginResponse(listOf(LoginResponse.ErrorType.TermsRequired()))
            } else {
                val errorBody = response.errorBody()?.string()?.trim() ?: ""

                if (errorBody == "[\"Wrong username or password.\"]" || errorBody.contains("has not been activated or is blocked."))
                    return LoginResponse(listOf(LoginResponse.ErrorType.CredentialsInvalid()))
                if (errorBody.contains("[\"Already logged in as "))
                    return LoginResponse(LoginResponse.SuccessData(null, null, null))

                return LoginResponse(listOf(LoginResponse.ErrorType.Generic()))
            }
        }
    }

    fun format(): LoginResponse {
        val roles = user.roles.keys
        val role = when {
            roles.contains(ROLE_ID_STANDARD) -> Role(ROLE_ID_STANDARD)
            roles.contains(ROLE_ID_LIMITED) -> Role(ROLE_ID_LIMITED)
            else -> Role(ROLE_ID_PREMIUM)
        }
        return LoginResponse(LoginResponse.SuccessData(role = role, gender = user.fieldGender))
    }

    data class User(
            @SerializedName("field_gender") val fieldGender: Gender?,
            @SerializedName("roles") val roles: Map<Int, String>,
    )


}

ApiResponseWrapper

sealed class ApiResponseWrapper<R> {
    companion object {
        fun <R> success(response: Response<R>): Success<R> =
                Success(response)

        fun <T> failure(error: Throwable): Failure<T> =
                Failure(error)
    }

    class Failure<T>(val error: Throwable) : ApiResponseWrapper<T>()

    class Success<T>(val response: Response<T>) : ApiResponseWrapper<T>()
}

With those out of the way, now here is what my Repository looks like

Repository

@Singleton
class Repository @Inject constructor(
  val mService: Service
) {
  fun login(username: String, password: String): Observable<ApiResponseWrapper<LoginRemote>> {
    return mService.login(LoginBody(username, password))
  }
}

From my understanding, this is what the Repository is suppose to be like (Or would it be better to convert the ApiReponseWrapper<LoginRemote> to a custom LoginResponse here??

Here is the view model:

LoginViewModel

class LoginViewModel
@Inject constructor(val mRepository: AuthRepository) : ViewModel() {

    fun login(username: String, password: String): LiveData<LoginResponse> {
        val res = mRepository.login(username, password)

        // need to SUBSCRIBE to the Observer here and convert the
        // ApiResponseWrapper<LoginRemote> into a LoginResponse 
        // (using the LoginRemote) and then wrap that LoginResponse 
        // in a LiveData object to be returned to the Activity/Fragment
    }
}

This is where I am stuck. I have been unable to figure out how to convert the ApiResponseWrapper into a LiveData object.

Here is my LoginResponse just in case it's needed

LoginResponse

class LoginResponse(val result: Result<SuccessData, ErrorType>) {
    class SuccessData(
            val role: Role?,
            val gender: Gender?,
    )

    sealed class ErrorType {
        class CredentialsInvalid : ErrorType()
        class UsernameRequired : ErrorType()
        class PasswordRequired : ErrorType()
        class Generic : ErrorType()
    }

    constructor(successData: SuccessData) : this(Result.success(successData))
    constructor(errors: List<ErrorType>) : this(Result.error(errors))
}

Finally, here is my Fragment that uses the ViewModel:

LoginFragment

class LoginFragment: Fragment(), Injectable [
  @Inject 
  lateinit var viewModelFactory: ViewModelProvidor.Factory
  lateinit var mLoginViewModel: LoginViewModel

  override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    mLoginViewModel = ViewModelProviders.of(this, viewModelFactory).get(LoginViewModel::class.java)
  }

  fun login() {
    mLoginViewModel
      .login("someusername", "somepassword")
      .observe(this, Observer { // it: LoginResponse
        activity?.onResult(it?.result, 
          onSuccess = { // LoginResponse.SuccessData
            // YAY, handle the successful login response
          }, onError = { // LoginResponse.ErrorType
            // BOO, handle the failed login response
          })
      })

  }

}

So I believe I am on the right track, but I'm inexperienced with reactive coding and am having trouble finding tutorials that match this architecture (usually find RxJava MVVM tutorials without LiveData, or LiveData without Retrofit, etc.).

What should my ViewModel look like here? How do I take the Observable<ApiResponseWrapper<LoginRemote>> returned by the repository and convert it into a LiveData<LoginResponse> to be sent back and handled by the fragment?

来源:https://stackoverflow.com/questions/57813679/retrofit-observable-response-to-livedata-object-using-subscribe

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!