Android RXJava句柄链api调用:显示不同的错误消息并继续流
我正试图将一个回调地狱转换为RX,但我一直无法获得正确的顺序,下面是我想要实现的功能 a) 用户登录->获取身份验证Cookies,如果登录凭据无效,则显示错误消息 b) 使用Auth Cookie获取客户类型 c) 如果客户类型为零/显示配置文件受限错误消息并注销用户 d) 如果customerType为非零,则继续获取其他客户详细信息 e) 如果任何客户API返回错误响应,请注销该用户并显示登录失败消息 f) 如果所有客户API成功,则显示主屏幕Android RXJava句柄链api调用:显示不同的错误消息并继续流,android,rx-java,retrofit,rx-java2,rx-android,Android,Rx Java,Retrofit,Rx Java2,Rx Android,我正试图将一个回调地狱转换为RX,但我一直无法获得正确的顺序,下面是我想要实现的功能 a) 用户登录->获取身份验证Cookies,如果登录凭据无效,则显示错误消息 b) 使用Auth Cookie获取客户类型 c) 如果客户类型为零/显示配置文件受限错误消息并注销用户 d) 如果customerType为非零,则继续获取其他客户详细信息 e) 如果任何客户API返回错误响应,请注销该用户并显示登录失败消息 f) 如果所有客户API成功,则显示主屏幕 API Login @For
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();
}
}
}));
}
API
登录
@FormUrlEncoded
@POST(“distauth/UI/Login”)
单个doLogin1(@Field(“用户名”)字符串用户名,@Field(“密码”)字符串密码,
@字段(“rememberme”)字符串rememberme,@Field(“answer”)字符串answer,
@查询映射选项);
公共单个doLogin(@NonNull字符串用户名,@null字符串密码){
返回authapi.doLogin1(用户名、密码、“y”、“logiOptions”)
.flatMap(新函数(){
@凌驾
公共单源应用(响应)引发异常{
if(response.code()==HttpStatus.MOVED_temporary.value())
&&!StringUtils.isEmpty(Session.getCookie())
) {
返回单曲。just(true);
}
抛出新异常(“无效登录详细信息”);
}
});
}
//==========
注销
@FormUrlEncoded
@发布(“distauth/UI/Logout”)
@标题(“缓存控制:无缓存”)
可完成的doLogout(@Field(“logout”)布尔logout)//返回302 HTTP状态代码,其中iPlanetCookie为空
//==========
注意:Loing/logout不是REST API,此遗留应用程序实现为表单Post;)所以当登录成功时返回302带有cookie,并且注销时也返回302作为状态码
获取客户详细信息
单一getCustomerAccountDetails(布尔值强制刷新);
//==========
单个getCustomerUserProfile(布尔forceRefresh);
@凌驾
公共单getCustomerUserProfile(布尔applyResponseCache){
返回此.mcCustomerRemoteDataStore.getCustomerUserProfile(applyResponseCache)
.doOnSuccess(新消费者(){
@凌驾
public void accept(CustomerType CustomerType)引发异常{
if(customerType!=null&&customerType.getBody()!=null&&
!StringUtils.isEmpty(customerType.getBody()){
if(customerType.getBody().equalsIgnoreCase(AppConfig.ERROR)){
抛出新CustomerProfileNotFound(500,“用户帐户限制”);
}否则{
mcCustomerLocalRepository.saveCustomerType(customerType);
}
}
}
}).doOnError(新使用者(){
@凌驾
public void accept(Throwable Throwable)引发异常{
Log.e(标签“获取客户用户配置文件时出错”,可丢弃);
}
});
}
//==========
单个getAccountId(布尔forceRefresh);
//==========
单getCustomer(布尔forceRefresh);
//==========
获取客户的全部详细信息
单getCustomerFullDetails(布尔applyResponseCache);
实施:
@凌驾
公共单getCustomerFullDetails(布尔forceRefresh){
单个customerDetails=Single.zip(
getCustomerUserProfile(forceRefresh).subscribeOn(Schedulers.io()),
getAccountId(forceRefresh).subscribeOn(Schedulers.io()),
getCustomerAccountDetails(false).subscribeOn(Schedulers.io()),
getCustomer(forceRefresh).subscribeOn(Schedulers.io()),新函数4(){
@凌驾
公共CustomerDetails应用(@NonNull CustomerType CustomerType,
@非空CustomerAccountId CustomerAccountId,
@非空CustomerAccountVO CustomerAccountVO,
@非Null客户)引发异常{
返回CustomerDetails.builder().customerType(customerType).customerAccountVO
(customerAccountVO).客户(customer).customerAccountId(customerAccountId)。
build();
}
});
返回客户详细信息;
}
//==========
每个客户请求都是独立的,所以我认为应该以sperate线程的形式执行,并压缩最终结果/
单个updateCustomerDetails(@Nonnull boolean secure,int secureRequestCode,@Nonnull JSONObject customerContact);
//演示者实现:这个实现没有像我上面期望的那样工作,有人能帮我纠正一下吗,
public void doLoginHandler(@NonNull字符串用户名,@NonNull字符串密码){
选中ViewAttached();
getMvpView().showLoadingIndicator();
添加订阅(
apiService.doLogin2(用户名、密码)
.subscribeOn(Schedulers.io())
.flatMap(新函数(){
@凌驾
公共单源应用(布尔aBoolean)引发异常{
if(阿布奥兰语){
.flatMapCompletable(new Function<CustomerDetails, CompletableSource>() {
@Override
public CompletableSource apply(CustomerDetails customerDetails) throws Exception {
if(customerDetails.isErrorOccurred()){
return apiService.doLogout();
}
return Completable.complete();
}
})
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 */
}
.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()
}
}}