我正在尝试将回调地狱转换为RX,但我坚持正确的顺序,以下是我要实现的功能
a)用户登录->如果登录凭据无效,则显示Auth Cookies
b)使用Auth Cookies获取客户类型,
c)如果“客户类型”为零/显示配置文件“受限错误消息”并注销用户
d)如果customerType不为零,则继续获取其他客户详细信息
e)如果任何客户API返回错误响应,请注销用户并显示登录失败消息
f)如果所有客户API成功都显示主屏幕
API
Login
@FormUrlEncoded
@POST("distauth/UI/Login")
Single<Response<Void>> doLogin1(@Field("username") String username, @Field("password") String password,
@Field("rememberme") String rememberMe, @Field("answer") String answer,
@QueryMap Map<String, String> options);
public Single<Boolean> doLogin(@NonNull String username, @Nullable String password) {
return authapi.doLogin1(username, password, "y", "", logiOptions)
.flatMap(new Function<Response<Void>, SingleSource<Boolean>>() {
@Override
public SingleSource<Boolean> apply(Response<Void> response) throws Exception {
if (response.code() == HttpStatus.MOVED_TEMPORARILY.value()
&& !StringUtils.isEmpty(Session.getCookie())
) {
return Single.just(true);
}
throw new Exception("Invalid Login Details");
}
});
}
//==========
Logout
@FormUrlEncoded
@POST("distauth/UI/Logout")
@Headers("Cache-Control: no-cache")
Completable doLogout(@Field("logout") boolean logout); //return 302 HTTP Status code with empty iPlanetCookie
//==========
NOTE: Loing/logout is not a REST API, this legacy app implement as Form Post ;) so when the success of login return 302 with cookies, and log out also return 302 as status code
Get Customer Details
Single<CustomerAccountVO> getCustomerAccountDetails(boolean forceRefresh);
//==========
Single<CustomerType> getCustomerUserProfile(boolean forceRefresh);
@Override
public Single<CustomerType> getCustomerUserProfile(boolean applyResponseCache) {
return this.mCustomerRemoteDataStore.getCustomerUserProfile(applyResponseCache)
.doOnSuccess(new Consumer<CustomerType>() {
@Override
public void accept(CustomerType customerType) throws Exception {
if (customerType != null && customerType.getBody() != null &&
!StringUtils.isEmpty(customerType.getBody())) {
if (customerType.getBody().equalsIgnoreCase(AppConfig.ERROR)) {
throw new CustomerProfileNotFound(500, "user account restrictions");
} else {
mCustomerLocalRepository.saveCustomerType(customerType);
}
}
}
}).doOnError(new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Log.e(TAG, "error occurred while getting customer user profile", throwable);
}
});
}
//==========
Single<CustomerAccountId> getAccountId(boolean forceRefresh);
//==========
Single<Customer> getCustomer(boolean forceRefresh);
//==========
Get Customer Full Details
Single<CustomerDetails> getCustomerFullDetails(boolean applyResponseCache);
Implementation:
@Override
public Single<CustomerDetails> getCustomerFullDetails(boolean forceRefresh) {
Single<CustomerDetails> customerDetails = Single.zip(
getCustomerUserProfile(forceRefresh).subscribeOn(Schedulers.io()),
getAccountId(forceRefresh).subscribeOn(Schedulers.io()),
getCustomerAccountDetails(false).subscribeOn(Schedulers.io()),
getCustomer(forceRefresh).subscribeOn(Schedulers.io()), new Function4<CustomerType, CustomerAccountId,
CustomerAccountVO, Customer, CustomerDetails>() {
@Override
public CustomerDetails apply(@NonNull CustomerType customerType,
@NonNull CustomerAccountId customerAccountId,
@NonNull CustomerAccountVO customerAccountVO,
@NonNull Customer customer) throws Exception {
return CustomerDetails.builder().customerType(customerType).customerAccountVO
(customerAccountVO).customer(customer).customerAccountId(customerAccountId).
build();
}
});
return customerDetails;
}
//==========
Each customer request is independent so I thought to execute as sperate thread and zip the final result/
Single<BaseServerResponse> updateCustomerDetails(@Nonnull boolean secure, int secureRequestCode, @Nonnull JSONObject customerContact);
//Presenter Implementation: this implementation not working as i expect above, can some one help me to get this correct,
public void doLoginHandler(@NonNull String username, @NonNull String password) {
checkViewAttached();
getMvpView().showLoadingIndicator();
addSubscription(
apiService.doLogin2(username, password)
.subscribeOn(Schedulers.io())
.flatMap(new Function<Boolean, SingleSource<CustomerDetails>>() {
@Override
public SingleSource<CustomerDetails> apply(Boolean aBoolean) throws Exception {
if (aBoolean) {
//get customr Full Details
Log.d(TAG, "apply: "+aBoolean);
return customerRepository.getCustomerFullDetails(true);
}
return null;
}
}).observeOn(AndroidSchedulers.mainThread())
.onErrorResumeNext(new Function<Throwable, SingleSource<? extends CustomerDetails>>() {
@Override
public SingleSource<? extends CustomerDetails> apply(Throwable throwable) throws Exception {
if (throwable instanceof CustomerProfileNotFound) {
getMvpView().showUserProfileAccessRestrictMessage();
} else {
getMvpView().onLoginAuthFailure();
}
return Single.just(CustomerDetails.builder().errorOccurred(true).build());
}
})
.flatMapCompletable(new Function<CustomerDetails, CompletableSource>() {
@Override
public CompletableSource apply(CustomerDetails customerDetails) throws Exception {
if(customerDetails.isErrorOccurred()){
return apiService.doLogout();
}
return Completable.complete();
}
})
.subscribe(new Action() {
@Override
public void run() throws Exception {
getMvpView().onLoginAuthSuccess();
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
if (throwable instanceof CustomerProfileNotFound) {
getMvpView().showUserProfileAccessRestrictMessage();
} else {
getMvpView().onLoginAuthFailure();
}
}
}));
}
答案 0 :(得分:0)
首先,我要说明您的代码有问题。
.flatMapCompletable(new Function<CustomerDetails, CompletableSource>() {
@Override
public CompletableSource apply(CustomerDetails customerDetails) throws Exception {
if(customerDetails.isErrorOccurred()){
return apiService.doLogout();
}
return Completable.complete();
}
})
除非调用注销API时发生网络错误,否则这条可观察的链(您所订阅的链)将始终处于“已完成”状态,这是因为您要么返回注销Completable,要么立即返回Completable。
其次,我认为解决方案是从逻辑上对所有内容进行分类,在这种情况下,错误处理的关键将是针对每个错误情况都使用自己的错误消息创建不同的Exception
它可以像这样(我只是在使用逻辑名,希望可以给你一个主意):
loginObservable.flatMap { authCredentials -> {
if (authCredentials.isValid())
return getCustomerTypeObservable(authCredentials)
else
return Single.error(InvalidCredentialsException("message goes here (optional)"))
}}.flatMap { type -> {
if (type == 0)
return Single.error(ProfileRestrictedException("different message maybe?"))
else
return getCustomerDetailsZippedObservable(type)
}}
/* ..etc */
然后在订阅站点上执行以下操作:
myObservable.subscribe( {
/* Handle success*/
}, { exception ->
when(exception) {
is InvalidCredentialsException -> mvpView.showError(message)
is ProfileRestrictedException -> {
mvpView.showError(message)
logout()
}
else -> /* Handle an exception that is not listed above */
}
})
通过这种方式,IMO比使用onErrorResumeNext
更方便。
编辑:您还可以通过执行以下操作来克服上述问题:
.flatMapCompletable { customerDetails -> {
if(customerDetails.isErrorOccurred()){
return apiService.doLogout()
.then(Completable.error(LoginFailedException("Message"))) /* This will guarantee the stream terminates with the required error type after logout is successful */
} else {
return Completable.complete()
}
}}