The fact is that I need to simultaneously pull in data from the local database, from the server, while checking the connection to the Internet.
Without checking the internet is easy. But when I turn off mobile data, crashes.
I do not understand how to combine and decided to do this:
private void getCategories() {
composite.add(getDataFromLocal(context)
.observeOn(AndroidSchedulers.mainThread()).flatMap(new Function<PromoFilterResponse, ObservableSource<List<FilterCategory>>>() {
#Override
public ObservableSource<List<FilterCategory>> apply(PromoFilterResponse promoFilterResponse) throws Exception {
if (promoFilterResponse != null) {
PreferencesHelper.putObject(context, PreferencesKey.FILTER_CATEGORIES_KEY, promoFilterResponse);
return combineDuplicatedCategories(promoFilterResponse);
} else {
return Observable.empty();
}
}
})
.subscribe(new Consumer<List<FilterCategory>>() {
#Override
public void accept(List<FilterCategory> categories) throws Exception {
if (mView != null) {
mView.hideConnectingProgress();
if (categories != null && categories.size() > 0) {
mView.onCategoriesReceived(categories);
}
}
}
}));
composite.add(InternetUtil.isConnectionAvailable().subscribe(isOnline -> {
if (isOnline) {
composite.add(
getDataFromServer(context)
.flatMap(new Function<PromoFilterResponse, ObservableSource<List<FilterCategory>>>() {
#Override
public ObservableSource<List<FilterCategory>> apply(PromoFilterResponse promoFilterResponse) throws Exception {
if (promoFilterResponse != null) {
PreferencesHelper.putObject(context, PreferencesKey.FILTER_CATEGORIES_KEY, promoFilterResponse);
return combineDuplicatedCategories(promoFilterResponse);
} else {
return Observable.empty();
}
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(categories -> {
if (mView != null) {
mView.hideConnectingProgress();
if (categories != null && categories.size() > 0) {
mView.onCategoriesReceived(categories);
} else {
mView.onCategoriesReceivingFailure(errorMessage[0]);
}
}
}, throwable -> {
if (mView != null) {
if (throwable instanceof HttpException) {
ResponseBody body = ((HttpException) throwable).response().errorBody();
if (body != null) {
errorMessage[0] = body.string();
}
}
mView.hideConnectingProgress();
mView.onCategoriesReceivingFailure(errorMessage[0]);
}
}));
} else {
mView.hideConnectingProgress();
mView.showOfflineMessage();
}
}));
}
private Single<Boolean> checkNetwork(Context context) {
return InternetUtil.isConnectionAvailable()
.subscribeOn(Schedulers.io())
.doOnSuccess(new Consumer<Boolean>() {
#Override
public void accept(Boolean aBoolean) throws Exception {
getDataFromServer(context);
}
});
}
private Observable<PromoFilterResponse> getDataFromServer(Context context) {
return RetrofitHelper.getApiService()
.getFilterCategories(Constants.PROMO_FILTER_CATEGORIES_URL)
.subscribeOn(Schedulers.io())
.retryWhen(BaseDataManager.isAuthException())
.publish(networkResponse -> Observable.merge(networkResponse, getDataFromLocal(context).takeUntil(networkResponse)))
.doOnNext(new Consumer<PromoFilterResponse>() {
#Override
public void accept(PromoFilterResponse promoFilterResponse) throws Exception {
PreferencesHelper.putObject(context, PreferencesKey.FILTER_CATEGORIES_KEY, promoFilterResponse);
}
})
.doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
LogUtil.e("ERROR", throwable.getMessage());
}
});
}
private Observable<PromoFilterResponse> getDataFromLocal(Context context) {
PromoFilterResponse response = PreferencesHelper.getObject(context, PreferencesKey.FILTER_CATEGORIES_KEY, PromoFilterResponse.class);
if (response != null) {
return Observable.just(response)
.subscribeOn(Schedulers.io());
} else {
return Observable.empty();
}
}
As you can see, connect the local database separately, simultaneously check the Internet and upload data from the server.
But it seems to me not quite right. Moreover, the subscriber is duplicated and so on.
I saw a lot of tutorials, where the combination of the local database with the API is described, but I did not see it at the same time processing the connection error with the Internet.
I think many people faced such a problem and how did you solve it?
Suppose You have two Obsevable: one from server and another from database
You can merge them into one stream like below:
public Observable<Joke> getAllJokes() {
Observable<Joke> remote = mRepository.getAllJokes()
.subscribeOn(Schedulers.io());
Observable<Joke> local = mRepository.getAllJokes().subscribeOn(Schedulers.io());
return Observable.mergeDelayError(local, remote).filter(joke -> joke != null);
}
Im' not android developer, but in my mind methods return types should be something like this:
//just for demonstration
static boolean isOnline = false;
static class NoInternet extends RuntimeException {
}
private static Completable ensureOnline() {
if (isOnline)
return Completable.complete();
else
return Completable.error(new NoInternet());
}
private static Single<String> getDataFromServer() {
return Single.just("From server");
}
private static Maybe<String> getDataFromLocal() {
return Maybe.just("From local");//or Maybe.never()
}
We can run all in parallel with Observable.merge. But what if error NoIternet happens? Merged observable will fail. We can use materialisation - transform all emission and errors to onNext value.
private static void loadData() {
Observable<Notification<String>> fromServer = ensureOnline().andThen(getDataFromServer()).toObservable().materialize();
Observable<Notification<String>> fromLocaldb = getDataFromLocal().toObservable().materialize();
Observable.merge(fromLocaldb, fromServer)
.subscribe(notification -> {
if (notification.isOnNext()) {
//calls one or two times(db+server || db || server)
//show data in ui
} else if (notification.isOnError()) {
if (notification.getError() instanceof NoInternet) {
//show no internet
} else {
//show another error
}
} else if (notification.isOnComplete()){
//hide progress bar
}
});
}
Related
I am calling the request it self in a base class
#Override
public Single<BaseResponse<D>> call() {
Single<BaseResponse<D>> singleResponse = Single.create(emitter -> request().getAsOkHttpResponseAndParsed(typeToken(), new OkHttpResponseAndParsedRequestListener<BaseData<D>>() {
#Override
public void onResponse(Response okHttpResponse, BaseData<D> response) {
try {
BaseResponse<D> r = new BaseResponse<D>(okHttpResponse) {
};
r.setData(response.getData());
r.setStatus(response.getStatus());
emitter.onSuccess(r);
} catch (Throwable throwable) {
Log.e("error", "throwable");
emitter.onError(throwable);
}
}
#Override
public void onError(ANError anError) {
Log.e("error", "error");
emitter.onError(anError);
}
}));
singleResponse.doOnSubscribe(__ ->
EspressoIdlingResource.countingIdlingResource.increment()).
doFinally(EspressoIdlingResource.countingIdlingResource::decrement).subscribe();
return singleResponse;
}
then I have my single observer builder class
public class SingleObserverBuilder<T> extends BaseObserverBuilder<T, SingleObserver<T>> {
private final Consumer<Disposable> disposableConsumer;
public SingleObserverBuilder(#NonNull Consumer<Disposable> disposableConsumer) {
this.disposableConsumer = disposableConsumer;
}
#Override
public SingleObserver<T> build() {
return new SingleObserver<T>() {
#Override
public void onSubscribe(Disposable d) {
disposableConsumer.accept(d);
}
#Override
public void onSuccess(T t) {
if (onSuccessListeners() != null) {
for (Consumer<T> onSuccess : onSuccessListeners()) {
onSuccess.accept(t);
}
}
}
#Override
public void onError(Throwable e) {
Log.e("error", "base error");
if (onFailureListeners() != null)
for (Consumer<Throwable> onFailure : onFailureListeners()) {
onFailure.accept(e);
}
}
};
}
}
and in my base observer fragment I access this single observer
public abstract class BaseObserverFragment extends Fragment {
protected CompositeDisposable disposable = new CompositeDisposable();
protected <T> SingleObserverBuilder<T> getDefaultSingleObserver(Object tag) {
SingleObserverBuilder<T> builder = new SingleObserverBuilder<>(disposable -> this.disposable.add(disposable));
builder.tag(tag).onSuccess(object -> {
Logger.e(builder.tag().toString(), "onSuccess");
}).onFailure(throwable -> {
Log.e("error", "base fragment");
Logger.e(builder.tag().toString(), "onFailure");
ErrorLogger.log(getContext(), builder.tag().toString(), throwable);
});
return builder;
}
#Override
public void onDetach() {
super.onDetach();
if(!disposable.isDisposed()) {
Log.e("error", "detach");
disposable.clear();
disposable.dispose();
}
}
}
then in the fragment itself that's how I call api request
viewModel.setAvailabilityStatus(isAvailable)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this.<BaseResponse<ChangeSpAvailabilityResponse>>getDefaultSingleObserver(OperationTag.CHANGE_AVAILABILITY)
.onSuccess(response -> {
})
.onFailure( throwable -> {
Log.e("error", "last");
if (throwable instanceof ANError) {
Log.e("error", "throwable");
if (((ANError) throwable).getErrorCode() == 400) {
((ANError) throwable).getErrorBody();
handleChangeAvailabilityResponse(((ANError) throwable).getErrorAsObject(ChangeSpAvailabilityResponse.class));
}
}
}).build());
}
in that api request I wanna handle failure request body and it all work fine, but sometimes I get this error :
The exception was not handled due to missing onError handler in the
subscribe() method call. Further reading:
https://github.com/ReactiveX/RxJava/wiki/Error-Handling |
com.androidnetworking.error.ANError
and I don't know why or what should I do to handle error
also I have this line
RxJavaPlugins.setErrorHandler(throwable -> {
Log.e("error", throwable.getMessage());
});
in my application class
I have playing with Rxjava recently trying to implement a chain of events(Api callas/Database operations) and seem to have hit a roadblock when it comes to handling errors.
This is what I am trying to do. I am calling an Api that will check if user exists in the database. Based on the response I get, I am trying to chain a few sequences using rxjava. Following diagram might explain a little better.
checkUser()
/ \
No Yes
/ \
createUserRemote() FetchUserNotesRemote()
| |
End SaveUserNotesLocal()
|
End
I am able to chain together checkUser() -> FetchUserNotesRemote() -> SaveUserNotesLocal() sequence with the following code.
checkUser()
.flatMap(id -> {return fetchData(id);})
.flatMap(notesResponseObject -> {return saveFetchedData(notesResponseObject);})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
}
#Override
public void onError(Throwable e) {
//handle errors here
}
});
The issue I am mainly trying to solve.
I can't figure out how to handle a case where checkUser() returns
a 404 http status. Because when that happens, subscriber's onError
method gets called which seems to me is what should happen. How can I
handle it so that when I get an error (404) response from API,
instead of executing FetchUserNotesRemote() and SaveUserNotesLocal(),
I execute a different chain of events?
Another thing I am not sure about is, if there is an error called on
any of the observables in a chain, how does the subscriber's onError method know
which observable called it?
1) To execute different chain of observables on error you can use method onErorrResumeNext(). More info here: github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators
Example:
checkUser().flatMap(id -> {return fetchData(id);})
.flatMap(notesResponseObject -> {return saveFetchedData(notesResponseObject);})
.onErrorResumeNext(throwable -> { return doSomethingDifferent(); }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
}
#Override
public void onError(Throwable e) {
//handle errors here
}
});
2) If the exception is thrown somewhere in your stream, it is passed down to subscriber onError(). If you want to know at which part of stream error was thrown, you can add multiple onErorrResumeNext() calls, that throw concrete exception after each api call.
checkUser()
.onErrorResumeNext(throwable -> { return Observable.error(new CheckUserException()); }
.flatMap(id -> {return fetchData(id);})
.onErrorResumeNext(throwable -> { return Observable.error(new FetchDataException()); }
.flatMap(notesResponseObject -> {return saveFetchedData(notesResponseObject);})
.onErrorResumeNext(throwable -> { return Observable.error(new SaveDataException()); }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
}
#Override
public void onError(Throwable e) {
//handle errors here
}
});
I completely forgot about this. But #mol pushed me in the right direction. My solution was a bit different. This may not be the best solution but it worked for me at the time.
I first created my own custom exception classes like following.
public class CreateUserLocalException extends Exception {
public CreateUserLocalException(String message) {
super(message);
}
}
Then in my checkUser() function I throw exception of type I created above like following.
public Single<String> checkUser(String id) {
return Single.create(new SingleOnSubscribe<String>() {
#Override
public void subscribe(SingleEmitter<String> emitter) throws Exception {
try {
GetUserResponseObject getUserResponseObject = apiClient.usersIdGet(id);
Log.d("Test", "checkUserCall: Status: " + getUserResponseObject.getStatus());
emitter.onSuccess(getUserResponseObject.getBody().getUserId());
} catch (AmazonServiceException e) {
Log.d("Test", "AmazonServiceException : " + e.getErrorMessage());
e.printStackTrace();
if (e.getErrorMessage().equals("timeout")) {
throw new SocketTimeoutException();
} else {
throw new CheckUserException(Integer.toString(e.getStatusCode()));
}
} catch (Exception e) {
e.printStackTrace();
throw new CheckUserException(Integer.toString(AppConstants.ERROR));
}
}
});
}
Then in my chain of calls, in the event of an error, onError(throwable) gets invoked where I am checking the instanceof Exception to identify what kind of exception occurred. Below is the code for chain of functions.
cloudSyncHelper.checkUser(user.getUser_id())
.retry(3, new Predicate<Throwable>() {
#Override
public boolean test(Throwable throwable) throws Exception {
Log.d("Test", throwable.toString());
if (throwable instanceof SocketTimeoutException) {
Log.d("Test", "Time out.. Retrying..");
return true;
}
return false;
}
})
.flatMap(s -> {
return cloudSyncHelper.createUserLocal(user)
.onErrorResumeNext(throwable -> {
Log.d("Test", "onErrorResumeNext, throwable message: " + throwable.getMessage());
if (throwable instanceof CreateUserLocalException) {
if (Integer.parseInt(throwable.getMessage()) == AppConstants.LOCAL_DB_DUPLICATE) {
return Single.just(user.getUser_id());
}
}
return Single.error(new CreateUserLocalException(Integer.toString(AppConstants.LOCAL_DB_ERROR)));
});
})
.flatMap(id -> {
return cloudSyncHelper.fetchData(id)
.retry(3, new Predicate<Throwable>() {
#Override
public boolean test(Throwable throwable) throws Exception {
Log.d("Test", throwable.toString());
if (throwable instanceof SocketTimeoutException) {
Log.d("Test", "Time out.. Retrying..");
return true;
}
return false;
}
});
})
.flatMap(notesResponseObject -> {
return cloudSyncHelper.saveFetchedData(notesResponseObject);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "onSuccess Called");
getSharedPreferences(AppConstants.AppName, MODE_PRIVATE).edit().putBoolean("isFirstRun", false).apply();
startActivity(new Intent(LoginScreen.this, HomeScreen.class));
}
#Override
public void onError(Throwable e) {
if (e instanceof SocketTimeoutException) {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "Socket Time Out");
Utils.createToast(LoginScreen.this, "Socket timed out");
return;
}
int code = Integer.parseInt(e.getMessage());
Log.d("Test", "onError Called");
if (e instanceof CheckUserException) {
Log.d("Test", "onError CheckUserException");
if (code == AppConstants.NOTFOUND) {
newUserSequence(user);
} else {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Utils.createToast(LoginScreen.this, "Unable to user information from cloud. Try again.");
}
}
if (e instanceof CreateUserLocalException) {
Log.d("Test", "onError CreateUserLocalException");
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
}
if (e instanceof FetchDataException) {
Log.d("Test", "onError FetchDataException");
if (code == AppConstants.NOTFOUND) {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
getSharedPreferences(AppConstants.AppName, MODE_PRIVATE).edit().putBoolean("isFirstRun", false).apply();
startActivity(new Intent(LoginScreen.this, HomeScreen.class));
} else {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "Unable to fetch data from cloud");
Utils.createToast(LoginScreen.this, "Unable to fetch data from cloud. Try again.");
}
}
if (e instanceof SaveDataLocalException) {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "onError SaveDataLocalException");
if (code == AppConstants.LOCAL_DB_ERROR) {
Log.d("Test", "Unable to save data fetched from cloud");
Utils.createToast(LoginScreen.this, "Unable to save data fetched from cloud");
} else {
Utils.createToast(LoginScreen.this, "Unable to save data fetched from cloud");
}
}
}
});
Hope this helps.
I'm trying to use AsyncTransaction for inserting some object, but for the moment it is a failed...
I tried to debug, but without success either ...
See my code :
realm.executeTransactionAsync(
new Realm.Transaction() {
#Override
public void execute(#NonNull Realm realm) {
Log.i("Insert", "Insert start");
realm.insert(character);
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
Log.i("Insert", "Insert complete");
finish();
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
Log.i("Insert","Error " + error.getMessage());
}
});
When i debug, i see that I didn't go on any callback of the async-transaction, there is no log, there is nothing that can help me.
Thank in advance,
EDIT :
public RealmAsyncTask executeTransactionAsync(final Transaction transaction,
#Nullable final Realm.Transaction.OnSuccess onSuccess,
#Nullable final Realm.Transaction.OnError onError) {
checkIfValid();
//noinspection ConstantConditions
if (transaction == null) {
throw new IllegalArgumentException("Transaction should not be null");
}
// Avoid to call canDeliverNotification() in bg thread.
final boolean canDeliverNotification = sharedRealm.capabilities.canDeliverNotification();
// If the user provided a Callback then we have to make sure the current Realm has an events looper to deliver
// the results.
if ((onSuccess != null || onError != null)) {
sharedRealm.capabilities.checkCanDeliverNotification("Callback cannot be delivered on current thread.");
}
// We need to use the same configuration to open a background SharedRealm (i.e Realm)
// to perform the transaction
final RealmConfiguration realmConfiguration = getConfiguration();
// We need to deliver the callback even if the Realm is closed. So acquire a reference to the notifier here.
final RealmNotifier realmNotifier = sharedRealm.realmNotifier;
final Future<?> pendingTransaction = asyncTaskExecutor.submitTransaction(new Runnable() {
#Override
public void run() {
if (Thread.currentThread().isInterrupted()) {
return;
}
SharedRealm.VersionID versionID = null;
Throwable exception = null;
final Realm bgRealm = Realm.getInstance(realmConfiguration);
bgRealm.beginTransaction();
// NOTHING IS DONE AFTER IS POINT .....
try {
transaction.execute(bgRealm);
if (Thread.currentThread().isInterrupted()) {
return;
}
bgRealm.commitTransaction();
// The bgRealm needs to be closed before post event to caller's handler to avoid concurrency
// problem. This is currently guaranteed by posting callbacks later below.
versionID = bgRealm.sharedRealm.getVersionID();
} catch (final Throwable e) {
exception = e;
} finally {
try {
if (bgRealm.isInTransaction()) {
bgRealm.cancelTransaction();
}
} finally {
bgRealm.close();
}
}
final Throwable backgroundException = exception;
final SharedRealm.VersionID backgroundVersionID = versionID;
// Cannot be interrupted anymore.
if (canDeliverNotification) {
if (backgroundVersionID != null && onSuccess != null) {
realmNotifier.post(new Runnable() {
#Override
public void run() {
if (isClosed()) {
// The caller Realm is closed. Just call the onSuccess. Since the new created Realm
// cannot be behind the background one.
onSuccess.onSuccess();
return;
}
if (sharedRealm.getVersionID().compareTo(backgroundVersionID) < 0) {
sharedRealm.realmNotifier.addTransactionCallback(new Runnable() {
#Override
public void run() {
onSuccess.onSuccess();
}
});
} else {
onSuccess.onSuccess();
}
}
});
} else if (backgroundException != null) {
realmNotifier.post(new Runnable() {
#Override
public void run() {
if (onError != null) {
onError.onError(backgroundException);
} else {
throw new RealmException("Async transaction failed", backgroundException);
}
}
});
}
} else {
if (backgroundException != null) {
// FIXME: ThreadPoolExecutor will never throw the exception in the background.
// We need a redesign of the async transaction API.
// Throw in the worker thread since the caller thread cannot get notifications.
throw new RealmException("Async transaction failed", backgroundException);
}
}
}
});
return new RealmAsyncTaskImpl(pendingTransaction, asyncTaskExecutor);
}
I found the trick.
In my constructor, i add some RealmObject to a another, that create a error
(Can't not write on a non write transaction)
The second point, was i used beginTransaction() on the parent, but it block on the other part for asynctransaction
I change my code for using the RealmRecyclerView on the firstPart and i didn't have the problem anymore
Thanks
I need to see the result as a boolean result: true. But there's a catch I need to do it in a non-ordinary way.
import java.io.IOException;
public class FlashLight {
private Bulb bulb;
private Battery[] batteries;
public void on() {
try {
if (this.IsThereEnoughPower()) {
this.bulb.setOn(true);
for (Battery b : batteries) {
b.setPower(b.getPower() - this.bulb.getBrightness());
}
}
} catch (IOException e) {
System.out.println(e.getMessage());
this.setBatteries(new Battery[4]);
}
}
I need to catch the exception in method on() but i can only modify method: DetermineIfFlashlightCanBeTurnedOn
public boolean DetermineIfFlashlightCanBeTurnedOn() throws IOException {
return bulb != null && DetermineIfBatteriesAreInstalled() && IsThereEnoughPower();
}
private boolean DetermineIfBatteriesAreInstalled() throws IOException {
if (batteries.length < 4) {
throw new IOException(Math.abs(-4 + batteries.length));
}
for (Battery b : batteries) {
if (b == null) {
return false;
}
}
return true;
}
private boolean IsThereEnoughPower() {
for (Battery b : batteries) {
if (b.getPower() < MIN_BATTERY_POWER) {
return false;
}
}
return true;
}
private static void testLatarki(String... args) {
FlashLight flashlight = new Flashlight();
System.out.println(flashlight.DetermineIfFlashlightCanBeTurnedOn());
}
}
Exception can be caught only in on() method.
DetermineIfBatteriesAreInstalled() DetermineIfFlashlightCanBeTurnedOn
must be signed as: throws IOException.
You can use try{}catch(){} instead :
public boolean DetermineIfFlashlightCanBeTurnedOn() {
try {
return bulb != null && DetermineIfBatteriesAreInstalled() && IsThereEnoughPower();
} catch (Exception e) {
//log your exception
}
return false;
}
I forgot to tell you guys i can use try/catch blocks only in on()
method
In this case you can use RuntimeException you don't need to use throws IOException in your method:
if (batteries.length < 4) {
throw new RuntimeException(Math.abs(-4 + batteries.length)+"");
}
So :
public boolean DetermineIfFlashlightCanBeTurnedOn() {
//--not need to use throw throws IOException-------^
return bulb != null && DetermineIfBatteriesAreInstalled() && IsThereEnoughPower();
}
private boolean DetermineIfBatteriesAreInstalled() {
//--not need to use throw throws IOException------^
if (batteries.length < 4) {
throw new RuntimeException(Math.abs(-4 + batteries.length) + "");
//----------^^
}
for (Battery b : batteries) {
if (b == null) {
return false;
}
}
return true;
}
You can read more here Is there a way to throw an exception without adding the throws declaration?
I am currently developing an Android app for existing IOS app, so using Session object to store some data on Parse is crucial for me.
As for creating and uploading sessions to Parse I have no problems.
public static void syncUser() {
ParseUser.getCurrentUser().fetchInBackground(new GetCallback<ParseObject>() {
#Override
public void done(ParseObject object, ParseException e) {
if (e != null) {
Log.d("", "SYNC ERROR");
e.printStackTrace();
}
if (object != null) {
syncSessions();
}
}
});
}
private static void syncSessions() {
ParseQuery<ParseSession> query = ParseQuery.getQuery(ParseSession.class);
query.fromLocalDatastore();
query.findInBackground(new FindCallback<ParseSession>() {
#Override
public void done(List<ParseSession> objects, ParseException e) {
for (ParseSession session : objects) {
fetchSession(session, null);
}
}
});
}
public static void fetchSession(final ParseSession session, final OnResultCallback cb) {
session.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e != null) {
if (cb != null)
cb.onResult(false);
e.printStackTrace();
} else {
if (cb != null)
cb.onResult(true);
ParseRelation<ParseSession> relation = ParseUser.getCurrentUser().getRelation("sessions");
relation.add(session);
syncUser();
}
}
});
}
public static void addNewSession(Date date, String link, int successValue) {
final ParseSession session = new ParseSession();
session.put("date", date);
session.put("link", link);
session.put("successValue", successValue);
session.pinInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e != null)
e.printStackTrace();
else {
fetchSession(session, new ParseManager.OnResultCallback() {
#Override
public void onResult(boolean success) {
if (success) {
try {
session.unpin();
} catch (ParseException e) {
e.printStackTrace();
}
}
}
});
}
}
});
}
public interface OnResultCallback {
void onResult(boolean success);
}
For creating new Session with my parameters and uploading it I use addNewSession() method, and it displays in Parse dashboard correctly, they have columns for my fields (date, link, successValue) and are stored as the default Session object.
But when I try to load them from Parse to my client, it doesn't work. I load them with this method:
public static void getSessions(final OnResultCallback cb) {
ParseRelation<ParseSession> relation = ParseUser.getCurrentUser().getRelation("sessions");
ParseQuery<ParseSession> query = relation.getQuery();
query.setLimit(1000);
query.findInBackground(new FindCallback<ParseSession>() {
#Override
public void done(List<ParseSession> objects, ParseException e) {
if (e != null) {
e.printStackTrace();
if (cb != null)
cb.onResult(false);
} else {
cb.onResult(true);
}
//if (objects != null)
//USE SESSIONS
}
});
}
I catch an exception:
W/System.err: com.parse.ParseRequest$ParseRequestException: wrong type of relation. Expecting: , but received: _Session
W/System.err: at com.parse.ParseRequest.newPermanentException(ParseRequest.java:270)
Quite similar code on IOS works fine with "sessions" relation. What mistakes have I done?
UPD. I have noticed that I get this exception only when I have allready send some custom created Session to Parse with addNewSession() and then trying to get them using getSessions(). Maybe creating and sending the Session is the problem?
I found the sollution. After I add my newly created Session to User relations with
ParseRelation<ParseSession> relation = ParseUser.getCurrentUser().getRelation("sessions");
relation.add(session);
I tried to save them with fetchInBackground, which was a mistake. I changed syncUser() to ParseUser.getCurrentUser().saveInBackground() and everything worked.