Problem description
I writing an Android application in Java to handle RFID reading based on some proprietary library and hardware. The RFID reader has a JNI library, which acts as an API to the hardware. This library is thread safe (as they say) but I cannot make the scanning process run on the Android's UI thread. I also don't want to use AsyncTask because it has started being deprecated I want the user to be able to start/resume the scanning process based on the click of a button without the process starting each time from the beginning.
I have the following code inside a class Scanner that is a singleton object.
class Scanner {
/*...*/
public Observable<Product> scan() {
return Observable.create(emitter -> {
while (true) {
UHFTAGInfo TAG = RFIDReader.inventorySingleTag();
if (TAG != null) {
emitter.onNext(new Product(TAG.getEPC()));
}
}
});
}
/*...*/
}
On the UI side, I have a ScanFragment and some buttons. This is the OnClickListener() of the scanButton that triggers the scanning process.
scanButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Scanner.getInstance().scan()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Product>() {
#Override
public void onSubscribe(#NonNull Disposable d) {
}
#Override
public void onNext(#NonNull Product product) {
listProducts.add(product);
adapter.notifyDataSetChanged();
}
#Override
public void onError(#NonNull Throwable e) {
}
#Override
public void onComplete() {
}
});
}
Questions
What is the correct way to stop/resume/end the above infinite emitting Observable when another stopButton/resumeButton is pressed or when user moves to another fragment?
I have thought many different things like an Atomic flag without and with an Observable for it and using takeUntil for that Observable. But will takeUntil stop my Observable and I will have to start it from the beginning?
First of all, you have an unconditional infinite loop which is unable to respond to when the downstream disposes the sequence. Use while (!emitter.isDisposed()).
Stopping a sequence is done via Disposable.dispose. You get one Disposable in onSubscribe so you'll have to make that available to the button that you want to stop a sequence.
Resuming is essentially subscribing to the sequence again, i.e., what you already do in that onClick callback.
Related
I'm learning android development I'm using RXJava with retrofit to interact with an API. So i've successfully managed to GET Data from my API. The problem now is that the program continues before the data has been fetched. How can I wait for the data to be downloaded from the API and then run some function?
I have a retrofit client as shown
public class RetrofitClient {
private static Retrofit retrofit = null;
private RetrofitClient() {
}
public static Retrofit getClient(String baseUrl) {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(SimpleXmlConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
return retrofit;
}
}
I do an API call as such.
public void getData(MyFragment Fragment) {
mAPIService.getData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Data>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Log.i("ERROR IN GET DATA", e.toString());
}
#Override
public void onNext(Data response) {
Log.i("MY DATA", response.toString());
fragment.downloadData(response);
}
});
}
the problem is my android application does not wait for fragment.downloadData(response) to finish but instead continues executing code and then crashes because response is null.
I have a listener on a button that when clicked gets data from the API
button.setOnClickListener(v ->{
APICaller.getData(this);
Log.i("TEST", data.ToString()); //THIS IS NULL
});
This is the downloadData function that I run from the APICaller
public void downloadData(Data data) {
this.data = data;
}
You need to be waiting for your RxJava stream to emit a value (either error or response).
Firstly, for this, if you're expecting a "single" emission, success or failure, I would use a Single. At the moment it looks your mAPIService.getData() method is returning an Observable. These are meant for streams that are going to emit multiple values which in your cause I am assuming is not what is going to happen. You only have one item that is going to be emitted so I would look at returning a Single. Not part of your question but FYI.
What I like to do is to tell my UI that whatever I'm doing is "loading", normally in the doOnSubscribe. Then the UI knows to show a loading icon or to not allow user interactions or something. Something like this (nb. notice how its after the observeOn(AndroidSchedulers.mainThread). Any time you interact with UI elements, do it on the main thread. I think this will do it):
mAPIService.getData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(new Consumer<Disposable>() {
#Override
public void accept(Disposable disposable) throws Exception {
fragment.loading();
}
})
Then when either the onError or onSuccess returns in your subscription, that is where you tell the UI it's ready to proceed. You then either have a valid response or can show the error to the user.
EDIT: Reactive Programming
After your comments it looks like you do not understand reactive programming. It has a bit of a steep learning curve and I still struggle with it today.
Your APICaller class, whatever it is, should return the Observable itself. You shouldn't be passing in a Fragment to this and handling it within there as you're opening yourself up to memory leaks and its a bit of a code smell. A better option is to just return the Observable returned by mAPIService.getData(). That's it. At the moment you are pushing the Observable to another thread using Schedulers.io() which says to your main thread, carry on and don't wait for me. You then come back to this thread when a response is emitted using the code .observeOn(AndroidSchedulers.mainThread())
In your fragment is then where you handle the emission of a value or an error. Your fragment code then becomes:
button.setOnClickListener(v ->{
APICaller.getData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Data>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Log.i("ERROR IN GET DATA", e.toString());
}
#Override
public void onNext(Data response) {
Log.i("MY DATA", response.toString());
this.data = response
Log.i("TEST: "+data.toString());
}
});
});
I would like to recommend you watch some tutorials, do some reading and I'd also like to refer you back to my point about Singles at the start
I have an HTTP request that triggers a long-running task (multiple HTTP requests to another service) that is supposed to be completed in the background while the original requests complete.
So what I do is
public void triggerWork(#RequestBody SomeObject somObject) {
return new ResponseEntity<>(startWorkAndReturn(somObject), HttpStatus.OK);
}
public void startWorkAndReturn(SomeObject someObject) {
Observable.create(observableEmitter -> {
// do the work with someObject here and at some time call
observableEmitter.onNext("result");
}).subscribe(new Observer<Object>() {
#Override
public void onSubscribe(Disposable disposable) {
}
#Override
public void onNext(Object o) {
// called at some unknown time
}
#Override
public void onError(Throwable throwable) {
}
#Override
public void onComplete() {
// currently not used as all the work is done in onNext but maybe that's a mistake
}
});
return;
}
But this seems to block the request until all the work has been done. Which already seems odd to me, since I never call onComplete, which in itself might be a mistake. But still, I am wondering how to create a request that immediately returns after triggering a background worker.
Is Flowables the solution here? I am going to refactor to those anyways to handle backpressure. Or do I need to create a background worker Thread? What is the best practice here?
Thanks
I would use Observable.fromCallable{} since you need emit only single event. That will handle onCompleate call. From information you share I don`t know how can you properly handle disposable. You should add subscribeOn() and observeOn() operators that will define on which thread 'work' should be processed and result should be observed.
Docs ref:
http://reactivex.io/RxJava/javadoc/io/reactivex/Observable.html#fromCallable-java.util.concurrent.Callable-
http://reactivex.io/documentation/operators/subscribeon.html
http://reactivex.io/documentation/operators/observeon.html
I have the following code that I want to turn Reactive:
class ReadThread extends Thread {
#Override
public void run() {
super.run();
while(!isInterrupted()) {
try {
String result = doBlockingIO();
listener.onReceivedData(result);
} catch (IOException e) {
e.printStackTrace();
return;
}
}
}
}
And if I want to stop I/O operation I call
readThread.interrupt();
My initial rxjava2 implementation is as per bellow:
mCompositeDisposable.add(Maybe.fromCallable(() -> {doBlockingIO();}))
.repeat()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new ResourceSubscriber<String>() {
#Override
public void onNext(String data) {
onReceivedData(data);
}
#Override
public void onError(Throwable e) {
onErrorOccurred(e);
}
#Override
public void onComplete() {
// do nothing
}
}));
As this is an Android application, on my onStop() Activity callback I call dispose() on the composite disposable above.
Running the above rxjava2 code on certain devices has some weird side effects, as when I stop IO Activity and move to another Activity that is also using similar composite disposable and IO operations, some operations seem to freeze. Also by looking at some logs I can tell that rxjava2 Thread running doBlockingIO completes after I leave and come back to the IO Activity:
07-21 19:18:17.420 5774-434/com.mobile.installation E/IOActivity: doBlockingIO() -> PRE RxCachedThreadScheduler-5 --->> LEFT ACTIVITY
07-21 19:18:30.134 5774-565/com.mobile.installation E/IOActivity: doBlockingIO() -> PRE RxCachedThreadScheduler-6 --->> RE-ENTERED ACTIVITY
07-21 19:18:30.847 5774-434/com.mobile.installation E/IOActivity: doBlockingIO() -> POST RxCachedThreadScheduler-5
Any idea on how to implement the typical java IO interrupt scenario properly with rxjava2?
Many thanks in advance!
I got a weird issue in one of my activities.
When coming back from taking a picture / video, in my onActivityResult I am showing a dialog that lets the user name the camera.
Once the user presses OK, I send onNext() to a subject with the requested file name that copies the file (and shows progress dialog).
For some reason the map() function that does the copy is always called on the main thread, even though I call subscribeOn(Schedulers.io()).
#Override
protected void onActivityResult(final int requestCode, int resultCode, Intent intent) {
...
final PublishSubject<String> subject = PublishSubject.create();`
mSubscription = subject
.subscribeOn(Schedulers.io())
.map(new Func1<String, String>() {
#Override
public String call(String fileName) {
Log.I.d(TAG,"map");
return doSomeIOHeavyFuncition();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() {
#Override
public void call(final String fullPath) {
Log.d(TAG,"onNext");
doSomethingOnUI(fullPath);
subject.onCompleted();
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
...
}
}, new Action0() {
#Override
public void call() {
...
}
});
final AlertDialog dialog = new AlertDialog.Builder
....
.create()
.show();
dialog.getButton(DialogInterface.BUTTON_POSITIVE)
.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String someString = getStringFromDialog(dialog);
dialog.dismiss();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(input.getWindowToken(), 0);
showProgressDialog();
subject.onNext(someString);
}
});
}
Changing the subscribeOn(Schedulers.io()) call to observeOn(Schedulers.io()) solved the issue.
Still I would like to know why it didn't work...
subscribeOn and observeOn is the mostly confused operators there are. The former makes sure that subscription side effects happen on the specified scheduler (thread), but that doesn't mean that values will pop up on that thread as well.
For example, if your Observer opens a network connection when one subscribes to it, you don't want that to run on the main thread, therefore, you need subscribeOn to specify where that subscription and thus the network connection will be created.
When data finally arrives, the emitting thread can be anything, one of the schedulers or a background plain old thread. Since we don't know or don't like that thread, we want to move the observation of the data to another thread. This is what observeOn does: makes sure operators after it will execute their onNext logic on the specified scheduler. Android devs use it already to move the observation of values back to the main thread.
What's rarely explained though is what happens when you want some extra computation off the main thread before the final result lands on the main thread again: use multiple observeOn operators:
source
.observeOn(Schedulers.computation())
.map(v -> heavyCalculation(v))
.observeOn(Schedulers.io())
.doOnNext(v -> { saveToDB(v); })
.observeOn(AndroidSchedulers.mainThread())
...
I want to delay my application for a while while a melody is playing, and when it's finished change the image on an imageview.
public void addListenerOnButtons() {
harmonicaTecknad= (ImageView)this.findViewById(R.id.harmonicatecknadspelautblas);
harmonicaTecknad.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
harmonicaTecknad.setImageResource(R.drawable.harmonicatecknadtryckrood);
RunAnimations();
utblas=MediaPlayer.create(SpelaTonerActivity.this, R.raw.utblas4);
utblas.start();
Thread timer = new Thread(){
public void run() {
try { // The delay should occur here
sleep(utblas.getDuration());
} catch(InterruptedException e) {
e.printStackTrace();
} finally {
harmonicaTecknad.setImageResource(R.drawable.harmonicatecknad1);
}
}
};
timer.start();
}
}
I get an exception error, obviously I cannot set the image inside the thread, so where should I set it?
This is all explained in details, with examples, in the android documentation:
Android offers several ways to access the UI thread from other
threads. You may already be familiar with some of them but here is a
comprehensive list:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
Handler
[...]
Unfortunately, these classes and methods could also tend to make your
code more complicated and more difficult to read. It becomes even
worse when your implement complex operations that require frequent UI
updates.
To remedy this problem, Android 1.5 and later platforms offer a
utility class called AsyncTask, that simplifies the creation of
long-running tasks that need to communicate with the user interface.
Write harmonicaTecknad.setImageResource(R.drawable.harmonicatecknad1); code on UI thread because you can not write UI code to non UI thread.
So simply replace above line of code with
runOnUiThread(new Runnable()
{
#Override
public void run()
{
harmonicaTecknad.setImageResource(R.drawable.harmonicatecknad1);
}
});
If you are still getting error then for testing purpose just change sleep(utblas.getDuration()); with sleep(1000);
As the other answers say, you need to set the image from the UI thread. However, while you can use Thread.sleep(), you should in general avoid using sleeps for logic. Most classes have some way to get callbacks when things happen - for example in your case you can use MediaPlayer.OnCompletionListener. You register for a callback from the media player, and that callback will always be on the UI thread. This is what it looks like:
public void addListenerOnButtons() {
harmonicaTecknad= (ImageView)this.findViewById(R.id.harmonicatecknadspelautblas);
harmonicaTecknad.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
harmonicaTecknad.setImageResource(R.drawable.harmonicatecknadtryckrood);
RunAnimations();
utblas=MediaPlayer.create(SpelaTonerActivity.this, R.raw.utblas4);
utblas.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
harmonicaTecknad.setImageResource(R.drawable.harmonicatecknad1);
}
};
utblas.start();
}
}