问题
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