Multiple chained API calls to fetch data, but doOnNext of PublishSubject is never reached - retrofit

I have a problem to understand a chained "RXJava-Retrofit" API call. I got inspired by this and implement this class named ObservationLoader to load the data from the API bucket per bucket. When the end of data is reached the API sends a endOfRecords=true:
public Observable<PageObject<Observation>> getAllObservationDataByRegion(long taxonKey,
String regionId) {
final PublishSubject<PageObject<Observation>> subject = PublishSubject.create();
return subject.doOnSubscribe(disposable -> {
this.getData(taxonKey, regionId, 0).subscribe(subject);
})
.doOnNext(observationPageObject -> {
if (observationPageObject.isEndOfRecords()) {
// -> list is completely loaded
subject.onComplete();
} else {
int nextOffset = observationPageObject.getOffset() + 1;
this.getData(taxonKey, regionId, null, nextOffset).subscribe(subject);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private Observable<PageObject<Observation>> getData(long id,
String regionId,
int offset) {
// Get your API response value
return this.api.getObservations(id, regionId, ObservationLoader.PAGE_LIMIT, offset);
}
In my Android fragment HomeFragment I subscribe to the ObservationLoader:
ObservationLoader loader = new ObservationLoader(this.getApi());
Observable<PageObject<Observation>> observable = loader
.getAllObservationDataByRegion(this.getSelectedSpecies(), this.getSelectedRegion());
observable.subscribe(new Observer<PageObject<Observation>>() {
#Override
public void onSubscribe(Disposable d) {
Log.i(TAG, "ON_SUBSCRIBE");
}
#Override
public void onNext(PageObject<Observation> observationPageObject) {
Log.i(TAG, "ON_NEXT");
}
#Override
public void onError(Throwable e) {
Log.i(TAG, "ERROR = " + e.getMessage());
}
#Override
public void onComplete() {
Log.i(TAG, "COMPLETED");
}
});
I can see that the onSubscribe() and doOnSubscribe() are called and even the getData() is reached. I assume the API is responding correctly (a previous attempt attempt with recursion worked fine). But I never reached the doOnNext function. The observer goes straight to onComplete() and no data is received. What could be the reason?

When doOnSubscribe runs, the doesn't see any consumers yet so if getData is synchronous, there won't be any first results to trigger further results. Also if getData ends, it will complete the setup so the next getData call in doOnNext will push to an already terminated subject, ingoring all data.
You'll need a differently organized feedback loop:
// we loop back the nextOffset, in a thread-safe manner
Subject<Integer> subject = PublishSubject.<Integer>create()
.toSerialized();
// bootstrap with 0 and keep open for more offsets
subject.mergeWith(Observable.just(0))
// get the data for the current offset
.concatMap(nextOffset -> getData(taxonKey, regionId, nextOffset)
.subscribeOn(Schedulers.io())
)
// if the response is end of records, stop
.takeWhile(observationPageObject -> !observationPageObject.isEndOfRecords())
// otherwise not end of records, feedback the new offset
.doOnNext(observationPageObject ->
subject.onNext(observationPageObject.getOffset() + 1)
)
// get the data on the main thread
.observeOn(AndroidSchedulers.mainThread());

Related

WebFlux returning http.okay vice http.notFound

New to WebFlux, reactive, and handlers. I've got things "working", but am not understanding why following code is returning "okay" with empty body, vice "not found".
Clarification: The issue-of-concern is in the final return statement of DemoPOJOHandler.getById(). The "short-circuit" code works as expected (i.e., returns "Bad Request" status), but the "switchIfEmpty" path of the final return statement does not appear to get exercised if a DemoPOJORepo.getById(int) returns Mono.empty().
(Note: I've hacked up a list-based "repo" to avoid dealing with database while figuring out handlers and http return types.)
Router implementation ("/v1" is a set of annotation based RESTful endpoints)...
#Configuration
public class DemoPOJORouter {
#Bean
public RouterFunction<ServerResponse> route(DemoPOJOHandler requestHandler) {
return nest(path("/v2"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.GET("/DemoPOJO"), requestHandler::getAll)
.andRoute(RequestPredicates.GET("/DemoPOJO/{id}"), requestHandler::getById)
.andRoute(RequestPredicates.POST("/DemoPOJO"), requestHandler::add)));
}
}
Handler implementation has been "stripped down" to only the code in question. I have a feeling that much of the style is "still imperative", but I've attempted to put the reactive stuff where it "makes the most sense".
If I supply a bad value on the URI (i.e., "foo"), then I get the http "bad request" returned. But, never seem to get the "not found" that should be generated by "switchIfEmpty" if a validly formatted int value is supplied, but it does not map to an entry in the repo.
#Component
public class DemoPOJOHandler {
public static final String PATH_VAR_ID = "id";
private DemoPOJORepo repo = null;
public Mono<ServerResponse> getById(ServerRequest request) {
Mono<DemoPOJO> monoDemoPOJO = null;
Map<String, String> pathVariables = request.pathVariables();
int id = -1;
checkRepoRef(); // part of the list hack
// short-circuit if request doesn't contain id (should never happen)
if ((pathVariables == null)
|| (!pathVariables.containsKey(PATH_VAR_ID))) {
return ServerResponse.badRequest().build();
}
// short-circuit if bad id value
try {
id = Integer.parseInt(pathVariables.get(PATH_VAR_ID));
} catch(NumberFormatException e) {
return ServerResponse.badRequest().build();
}
// get entity by keyValue
monoDemoPOJO = repo.getById(id);
return monoDemoPOJO
.flatMap(demoPOJO -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.syncBody(demoPOJO)
.switchIfEmpty(ServerResponse.notFound().build()));
}
}
Hack of a list-based repo to avoid dealing with data/APIs while working on handlers and http return types.
// local hack to avoid a database for testing
public class DemoPOJORepo {
private static DemoPOJORepo fpRepo = null;
private static int NUM_ROWS = 100;
private Map<Integer, DemoPOJO> fooPOJOMap;
private DemoPOJORepo() {
initMap();
}
public static DemoPOJORepo getInstance() {
if (fpRepo == null) {
fpRepo = new DemoPOJORepo();
}
return fpRepo;
}
public Mono<DemoPOJO> getById(int id) {
Mono<DemoPOJO> monoDP;
if (fooPOJOMap.containsKey(id)) {
monoDP = Mono.just(fooPOJOMap.get(id));
} else {
monoDP = Mono.empty();
}
return monoDP;
}
private Mono<Void> initMap() {
fooPOJOMap = new TreeMap<Integer, DemoPOJO>();
int offset = -1;
for(int ndx=0; ndx<NUM_ROWS; ndx++) {
offset = ndx + 1;
fooPOJOMap.put(offset, new DemoPOJO(offset, "foo_" + offset, offset+100));
}
return Mono.empty();
}
}
Your brackets are in the wrong place causing the swithIfEmpy to apply to the ServerResponse.ok() publisher not the monoDemoPOJO, replace the return with this and it should work:
return monoDemoPOJO
.flatMap(demoPOJO -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).syncBody(demoPOJO))
.switchIfEmpty(ServerResponse.notFound().build());
As I can see the code is right. The response code is Bad request because you are trying to convert "foo" to Integer, and when it throws an exception you are returning a Bad request response, so I think it works perfectly fine.
If you use an Integer id that is not present in your database then the answer must be a not found response

toCompletableFuture() stucks for asynchronous cache

Hello there I am trying to force a promise to end to get the result from it but it just stucks on loading.
public class CacheController extends Controller {
private AsyncCacheApi cache;
public Result cache()
{
String test = "nice";
cache.set("item.key", test, 15);
Customer user = new Customer("Ana", 12);
CompletionStage<Done> result = cache.set(user.getName(), user);
block(result);
return ok("Cached");
}
public Result checkCache() throws Exception
{
Logger.info("start");
//CompletionStage<String> news = cache.get("item.key");
//news.thenRun(() -> System.out.println("works"));
CompletionStage<Customer> result = cache.get("Ana");
Logger.info("step 1");
Logger.info(cache.get("Ana").toString());
Logger.info("Step 2");
Customer c = block(result);
Logger.info("Step 3 " + c.getName());
//result.thenRun(() -> setUser(result)).thenRun(() -> Logger.info(user.getName() + " " + user.getAge()));
return ok("cancan");
}
private <T> T block(CompletionStage<T> stage) {
try {
return stage.toCompletableFuture().get();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
When trying to load the page it gets stuck after step2 at line 56: Customer c = block(result); by my guesses
Any ideas to fix it?
#Codrin
I had the same problem. But, see https://www.playframework.com/documentation/2.6.x/JavaCache#Setting-the-execution-context
By default, all Ehcache operations are blocking, and async implementations will block threads in the default execution context.
Maybe CompletableFuture.get() gets stuck because it is executed in the same thread with the caller.
Referring to the linked page, I added snippet below to my application.conf and it worked.
play.cache.dispatcher = "contexts.blockingCacheDispatcher"
contexts {
blockingCacheDispatcher {
fork-join-executor {
parallelism-factor = 3.0
}
}
}

JavaFX, Is there any callback for Platform.runlater()?

My application adds multiple Runnable on FX thread, by calling Platform.runlater method, then, I want to do some calculations only when there is no additional Runnable is in the FX Platform queue.
But I don't know the right way, is there any event or callback mechanism to get the right time?
Currently I force the application thread to sleep randomly MILLISECONDS.
This is a bad idea from the start since you do not know what other code uses Platform.runLater. Also you should not rely on such implementation details; for all you know the queue could never be empty.
However you could post those Runnables using a custom class that keeps track of the number of Runnables and notifies you when all are done:
public class UpdateHandler {
private final AtomicInteger count;
private final Runnable completionHandler;
public UpdateHandler(Runnable completionHandler, Runnable... initialTasks) {
if (completionHandler == null || Stream.of(initialTasks).anyMatch(Objects::isNull)) {
throw new IllegalArgumentException();
}
count = new AtomicInteger(initialTasks.length);
this.completionHandler = completionHandler;
for (Runnable r : initialTasks) {
startTask(r);
}
}
private void startTask(Runnable runnable) {
Platform.runLater(() -> {
runnable.run();
if (count.decrementAndGet() == 0) {
completionHandler.run();
}
});
}
public void Runnable runLater(Runnable runnable) {
if (runnable == null) {
throw new IllegalArgumentException();
}
count.incrementAndGet();
startTask(runnable);
}
}

rxjava - Combine onerror and timout handling

I will start with what I want to achieve.
I want to call a method that returns an Observabe.
I do not know if the called method handles exceptions and timeouts
I want to combine observables in my call (merge/zip etc)
if one method fails, I want the answers from the methods that succeeded -
I don't want to break the flow.
In case of exception, I am capable of handling it and continuing with the flow,
but when I try to add timeoutmanagement I fail.
Here is my code
public static void main(String[] args) {
createObservables(false, true); // stalls for timeout
zip();
}
private static void createObservables(final boolean throwException,
final boolean stall) {
obs1 = Observable.just(1);
obs1 = obs1.map(new Func1<Integer, Integer>() {
#Override public Integer call(Integer integer) {
int i = 0;
if (throwException)
getObj().equals("");
if (stall)
zzz(10);
return ++integer;
}
});
obs2 = Observable.just(111);
}
private static void zip() {
System.out.println("**Zip**");
obs1 = obs1.onErrorReturn(new Func1<Throwable, Integer>() {
#Override public Integer call(Throwable throwable) {
return 999;
}
});
obs1 = obs1.timeout(5, TimeUnit.SECONDS);
Observable.zip(obs1, obs2, new Func2<Integer, Integer, ArrayList<Integer>>() {
#Override
public ArrayList<Integer> call(Integer integer1, Integer integer2) {
ArrayList<Integer> integers = new ArrayList<Integer>();
integers.add(integer1);
integers.add(integer2);
return integers;
}
}).subscribe(new Observer<Object>() {....}
);
}
Now, when I call
createObservables(false , false); // no exceptions and timeouts
I get onNext - [2, 111].
then I call
createObservables(true, false); // throw exception in one method only
I get onNext - [999, 111] - which is what I want. Exception and the result from the second method.
But when I call
createObservables(false, true); // stall on timeout
I get only onError.
But I want to get the other method answer.
Thanks.
Try creating an observable for your timeout value, in this case you want the same value as your error case:
Observable obs1Timeout = Observable.just(999);
Then in your timeout policy give it this observable as the fallback to use in the case of a timeout:
obs1 = obs1.timeout(5, TimeUnit.SECONDS, obs1Timeout);

Android testing - start with clean database for every test

I'm testing my application with Android Instrumentation tests.
So I have a test-class extending ActivityInstrumentationTestCase2 which contains multiple tests. The code looks like this:
public class ManageProjectsActivityTestextends ActivityInstrumentationTestCase2<ManageProjectsActivity> {
public ManageProjectsActivityTest() {
super("eu.vranckaert.worktime", ManageProjectsActivity.class);
}
#Override
protected void setUp() throws Exception {
getInstrumentation().getTargetContext().deleteDatabase(DaoConstants.DATABASE);
super.setUp();
solo = new Solo(getInstrumentation(), getActivity());
}
#Override
protected void runTest() throws Throwable {
super.runTest();
getActivity().finish();
}
public void testDefaults() {
// My test stuff
}
public void testAddProject() {
// My test stuff
}
}
So the activity which is under test has a list of projects.
The list of projects is retrieved from the database. And when no database is available, so when the DB is created, I insert one default project.
So that means when the tests are run this is what I exepct:
The database, if available, is removed on the device
The first test is started (and thus the activity is launched which creates my DB with one project)
The tests uses the newly created DB, so meaning with only one project, during the test a second project is created
The first test is finished and the setUp() method is called again
The database, that should exist now, is again removed
The second test is started (and thus the activity is launched which creates my DB with one project)
The second test also finishes
But that's not quite what this test-suite does...
This is the result of my test-suite:
The database, if available, is removed on the device
The first test is started (and thus the activity is launched which creates my DB with one project)
The tests uses the newly created DB, so meaning with only one project, during the test a second project is created
The first test is finished and the setUp() method is called again
The database, that should exist now, is again removed
And here it comes: The second test is started (but my DB is not created again!!! I cannot see a file on the device either...) and the test should display only one project at the beginning but it does display already two!!!
The second test also finishes but fails because I have two projects at the beginning...
At the beginning I did not override the runTest() method but I thought that maybe I should end the activity myself to force the re-creation, but it doesn't make any difference.
So it seems that the DB is kept in memory (as even no new DB file is created on the device when I explicitly remove it). Or even the activity, because when putting a breakpoint a in the onCreate of the activity I only get in there once for both tests.
For the maintaining the DB I use ORMLite. You can see my helper class here: http://code.google.com/p/worktime/source/browse/trunk/android-app/src/eu/vranckaert/worktime/dao/utils/DatabaseHelper.java
So my question is how to force the tests to use a different DB all the time...?
A bit tangential to this problem, but I landed here when I was looking for help. Might be helpful to some folks. If you initialize your database with the RenamingDelegatingContext, it cleans the database between runs.
public class DataManagerTest extends InstrumentationTestCase {
private DataManager subject;
#Before
public void setUp() {
super.setUp();
RenamingDelegatingContext newContext = new RenamingDelegatingContext(getInstrumentation().getContext(), "test_");
subject = new DataManager(newContext);
}
// tests...
}
And the associated DataManagerClass.
public class DataManager {
private SQLiteDatabase mDatabase;
private SQLiteOpenHelper mHelper;
private final String mDatabaseName = "table";
private final int mDatabaseVersion = 1;
protected DataManager(Context context) {
this.mContext = context;
createHelper();
}
private void createHelper() {
mHelper = new SQLiteOpenHelper(mContext, mDatabaseName, null, mDatabaseVersion) {
#Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
// createTable...
}
#Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
// upgrade table
}
};
}
...
}
mDb.delete(DATABASE_TABLE_NAME, null, null);
That is indeed the solution/way-to-go...
I changed the first line in my setUp(..) method to this:
cleanUpDatabase(tableList);
And then I added the method cleanUpDatabse(..) liek this:
private void cleanUpDatabase(List<String> dbTables) {
Log.i(LOG_TAG, "Preparing to clean up database...");
DatabaseHelper dbHelper = new DatabaseHelper(getInstrumentation().getTargetContext());
ConnectionSource cs = dbHelper.getConnectionSource();
SQLiteDatabase db = dbHelper.getWritableDatabase();
Log.i(LOG_TAG, "Dropping all tables");
for (String table : dbTables) {
db.execSQL("DROP TABLE IF EXISTS " + table);
}
Log.i(LOG_TAG, "Executing the onCreate(..)");
dbHelper.onCreate(db, cs);
Log.i(LOG_TAG, "Verifying the data...");
for (String table : dbTables) {
Cursor c = db.query(table, new String[]{"id"}, null, null, null, null, null);
int count = c.getCount();
if (count != 1 && (table.equals("project") || table.equals("task"))) {
dbHelper.close();
Log.e(LOG_TAG, "We should have 1 record for table " + table + " after cleanup but we found " + count + " record(s)");
throw new RuntimeException("Error during cleanup of DB, exactly one record should be present for table " + table + " but we found " + count + " record(s)");
} else if (count != 0 && !(table.equals("project") || table.equals("task"))) {
dbHelper.close();
Log.e(LOG_TAG, "We should have 0 records for table " + table + " after cleanup but we found " + count + " record(s)");
throw new RuntimeException("Error during cleanup of DB, no records should be present for table " + table + " but we found " + count + " record(s)");
}
}
Log.i(LOG_TAG, "The database has been cleaned!");
dbHelper.close();
}
This piece of code gets executed before every test, which makes all my tests independent from each other.
Caution: In order to retrieve a reference to your DatabaseHelper (your own implementation off course ;) ) you cannot call getActivity() because that will launch your activity (and thus do all your initial DB loading (if any..)
InstrumentationRegistry → getContext → deleteDatabase
android.support.test.InstrumentationRegistry's getTargetContext and, counterintuitively perhaps, getContext should do the trick:
Synopsis
Use getContext for deleting database
(not getTargetContext).
getContext().deleteDatabase(DbHelper.DATABASE_NAME);
Example
public class DbHelperTest {
private DbHelper mDb;
#Before
public void setUp() throws Exception {
getContext().deleteDatabase(DbHelper.DATABASE_NAME);
mDb = new DbHelper(getTargetContext());
}
#After
public void tearDown() throws Exception {
mDb.close();
}
#Test
public void onCreate() throws Exception {
mDb.onCreate(mDb.getWritableDatabase());
}
#Test
public void onUpgrade() throws Exception {
mDb.onUpgrade(mDb.getWritableDatabase(), 1, 2);
}
#Test
public void dropTable() throws Exception {
String tableName = "mesa";
mDb.getReadableDatabase().execSQL("CREATE TABLE "
+ tableName + "(_id INTEGER PRIMARY KEY AUTOINCREMENT)");
mDb.dropTable(mDb.getWritableDatabase(), tableName);
}
}
you try to start time all table data delete alter native solution.
mDb.delete(DATABASE_TABLE_NAME, null, null);
Since my db is encrypted I needed to remove the actual db file after each test case. I had trouble making deleteDatabase from a RenamingDelegatingContext since the RenamingDelegatingContext could not find the previously created db for some reason.
Solution for AndroidTestCase subclass:
#Override
protected void setUp() throws Exception {
super.setUp();
mContext = new RenamingDelegatingContext(getContext(), TEST_DB_PREFIX);
// create db here
}
#Override
protected void tearDown() throws Exception {
// Making RenamingDelegatingContext find the test database
((RenamingDelegatingContext) mContext).makeExistingFilesAndDbsAccessible();
mContext.deleteDatabase(DB_NAME);
super.tearDown();
}
before each test run it
val context: Context = ApplicationProvider.getApplicationContext()
context.deleteDatabase("myDatabaseName.db")
this can also be made a rule

Resources