How does subscribeOn work - android

I have some doubts regarding the working on the subscribeOn operator. I read some article regarding this.
The observeOn is quite easy to understand, it changes only the downstram, and change affects to all the downstream.
But as told in the article subscribeOn can be put in any place in the stream because it affects only the time of subscription.:
To understand this , I did a samlpe and tried logging the thread at each point of time.
Observable.just("Hello")
.map(s -> {
Log.d(TAG, s + " in " + Thread.currentThread());
return 1;
})
.subscribeOn(Schedulers.newThread())
.map(integer -> {
Log.d(TAG, integer + " in " + Thread.currentThread());
return true;
})
.map(aBoolean -> {
Log.d(TAG, aBoolean + " in " + Thread.currentThread());
return 11.0;
})
.subscribeOn(Schedulers.computation())
.subscribe(aDouble -> {
Log.d(TAG, "accept in " + Thread.currentThread());
Log.d(TAG, "accept: " + aDouble);
});
The result is
Hello in Thread[RxNewThreadScheduler-1,5,main]
1 in Thread[RxNewThreadScheduler-1,5,main]
true in Thread[RxNewThreadScheduler-1,5,main]
accept in Thread[RxNewThreadScheduler-1,5,main]
accept: 11.0
Here twice I'm applying subscribeOn, but everytime the first added one seem to be applied throughout the stream.
Can anyone please explain in simple words how does it actually work, since I'm a beginner and hard to digest this!
Thanks in advance

subscribeOn: If you have multiple subscribeOn then the first one takes effect. If you want to change the Scheduler on the stream after making a subscribeOn, then take a look at observeOn
observeOn: It changes the Scheduler going downstream.
For example:
just("Some String") // Computation
.subscribeOn(Schedulers.computation()) // it changes scheduler to computation beginning from source to observer.
.map(str -> str.length()) // Computation
.observeOn(Schedulers.io) //change the scheduler from here till the observer
.map(length -> 2 * length) // io
.subscribe(number -> Log.d("", "Number " + number));// io

Related

RxJava2 toList() never emits

So I have following Disposable which doesn't work. I am using Room to get all rows from a table as a list, map each of them to something and create a list and then it doesn't continue from there.
storedSuggestionDao
.getSuggestionsOrderByType() //Flowable
.doOnNext(storedSuggestions -> Timber.e("storedSuggestions: " + storedSuggestions)) //this work
.flatMapIterable(storedSuggestions -> storedSuggestions)
.map(Selection::create) ))
.doOnNext(selection -> Timber.e("Selection: " + selection)) // works
.toList()
.toObservable() // nothing works after this...
.doOnNext(selections -> Timber.d("selections: " + selections))
.map(SuggestionUiModel::create)
.doOnNext(suggestionUiModel -> Timber.d("suggestionUiModel: " + suggestionUiModel))
.subscribe();
These types of data sources from 3rd parties are usually infinite sources but toList() requires a finite source. I guess you wanted to process that collection of storedSuggestions and keep it together. You can achieve this via an inner transformation:
storedSuggestionDao
.getSuggestionsOrderByType() //Flowable
.doOnNext(storedSuggestions -> Timber.e("storedSuggestions: " + storedSuggestions)) //this work
// -------------------------------------
.flatMapSingle(storedSuggestions ->
Flowable.fromIterable(storedSuggestions)
.map(Selection::create)
.doOnNext(selection -> Timber.e("Selection: " + selection))
.toList()
)
// -------------------------------------
.doOnNext(selections -> Timber.d("selections: " + selections))
.map(SuggestionUiModel::create)
.doOnNext(suggestionUiModel -> Timber.d("suggestionUiModel: " + suggestionUiModel))
.subscribe();
I think in your case you don't need to call .toObserable()
It should be like this
storedSuggestionDao
.getSuggestionsOrderByType() //Flowable
.doOnNext(storedSuggestions -> Timber.e("storedSuggestions: " + storedSuggestions)) //this work
.flatMapIterable(storedSuggestions -> storedSuggestions)
.map(Selection::create) ))
.doOnNext(selection -> Timber.e("Selection: " + selection)) // works
.toList() // you don't have to call .toObserable()
.map(SuggestionUiModel::create)
.subscribe();
The problem is
storedSuggestionDao.getSuggestionsOrderByType() //Flowable
is a hot stream.
toList still waits for upstream to complete

Is there a limit on number of Observables we can run on Schedulers?

I am trying to see if I can spawn 1 million Observables on io() and computation() Schedulers.
public static void observableLimit() {
sum = 0;
long lowerBound = 0;
long higherBound = 1000;
Flowable.fromCallable(() -> {
Flowable.rangeLong(lowerBound, higherBound + 1)
.subscribe(integer -> Observable.just(integer)
.subscribeOn(Schedulers.io())
.subscribe(j -> {
printNum(j);
sum = sum + j;
}));
return true;
}).blockingSubscribe(aBoolean -> {
long actualSum = (higherBound * (higherBound + 1)) / 2;
System.out.println("");
System.out.println("SUM: " + sum);
Assert.assertEquals(actualSum, sum);
});
}
For higherBound = 100 it works most of the time, for 1000 it works sometimes and fails most of the time and for 10000 it almost fails everytime, it works if I tell it to run it on newThread() and if I don't use subscribeOn() at all.
How can I fix this behaviour?
The problem you're facing is not about of some limitations of Observables, but a problem with your code. You're blockingSubscribe to a Flowable that have no relation with the Flowable that span all other threads. for small values of higherBound you'll see that the code works while for large values doesn't and that because the outer Flowable may be as fast as the inner Flowable for small higherBound but collapse faster for high values of higherBound.
What I'm trying to say is that in order to see the right result you need to syncronize with the Flowable that span all the other threads instead of the outer one. I also would replace long sum by a thread-safe implementation LongAdder sum, you can achieve this using flatMap operator.
Flowable.rangeLong(lowerBound, higherBound + 1)
.flatMap(t -> Flowable.just(t)
.subscribeOn(Schedulers.io())
)
.doOnNext(sum::add)
.doOnComplete(() -> {
long actualSum = (higherBound * (higherBound + 1)) / 2;
log("SUM: " + sum.longValue() + ", ACTUAL: " + actualSum);
log("Equals: " + (actualSum == sum.longValue()));
})
.blockingSubscribe();
How can I fix this behaviour?
Don't use that pattern. Why do you want to do that in the first place?
io and newThread create OS threads and are fundamentally limited by your OS' capabilities and available memory.
computation has a fixed set of threads and can handle much larger number of Flowables because they get assigned to one of the existing worker threads.

Room flowable cache

I am using Room (1.0.0.rc1) with RX, my Dao is defined is this way:
#Dao
interface AccountDao {
#Query("SELECT * FROM Account ORDER BY name")
fun all(): Flowable<List<Account>>
}
I am subscribing this way:
dao
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { accounts = it }
I have more than one place in the code who subscribes to the flowable. The first to subscribe gets the data, the other ones don't.
How can I make an observable that will emit the actual content every time someones subscribes and will also notify every subscriber when the data changes?
You can use replay to emit lastest value every time someone subscribes. And use distinctUntilChanged to notify only when data changes.
Here is the sample:
import io.reactivex.Observable;
import io.reactivex.subjects.BehaviorSubject;
public class Q47000608 {
public static void main(String[] args) {
BehaviorSubject<Integer> bs = BehaviorSubject.createDefault(1);
Observable<Integer> o = bs.replay(1).autoConnect().distinctUntilChanged();
o.subscribe(i -> System.out.println("s1 accept " + i));
bs.onNext(2);
o.subscribe(i -> System.out.println("s2 accept " + i));
o.subscribe(i -> System.out.println("s3 accept " + i));
bs.onNext(3);
o.subscribe(i -> System.out.println("s4 accept " + i));
bs.onNext(4);
}
}
And output:
s1 accept 1
s1 accept 2
s2 accept 2
s3 accept 2
s1 accept 3
s2 accept 3
s3 accept 3
s4 accept 3
s1 accept 4
s2 accept 4
s3 accept 4
s4 accept 4

Queue of Main Looper

How can I track a queue of Looper of UI thread Android? I would like to track it for debug purposes. For example, I would like turn on the logging in Looper.loop():
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
What's the proper way to assign a Printer to mLogging? When in the Android Activity lifecycle should it be assigned?
Thanks in advance.
did you check the official docs? https://developer.android.com/reference/android/os/Looper.html
Seems that something as simple as
Looper.getMainLooper().setMessageLogging(new LogPrinter(Log.DEBUG, TAG));
would do the trick!
I was able to get the ">>>>> Dispatching to " messages logged by calling the following at the end of my main Activity's onCreate() method:
PrintWriterPrinter out= new PrintWriterPrinter(new PrintWriter(System.out,true));
getMainLooper().setMessageLogging(out);
Source: https://www.programcreek.com/java-api-examples/index.php?api=android.util.PrintWriterPrinter

Not cheatable Google fit step counter

i have a question to Google Fit.
I am creating a step counter (oh wonder g). This i have already done so far and it not really hard.
But now we come to my problem. I am only reading the steps with the Sensor API. The issue is, i can add new data via for example the Google Fit app and it will be counted in my app too. This introduces cheating and i do not want this.
So i need to have a way to only read "device created" data and not manually added data. Is there a nice way to to this?
From the SDK documentation it is not really clear how to proceed here.
So i need to have a way to only read "device created" data and not
manually added data. Is there a nice way to to this?
You will want to use Private Custom Data Types to achieve that. Read about the different types of Fitness data you can upload to Google Fit here.
1. Public data types
Standard data types provided by the platform, like com.google.step_count.delta. Any app can read and write data of
these types. For more information, see Public Data Types.
2. Private custom data types
Custom data types defined by an specific app. Only the app that defines the data type can read and write data
of this type. For more information, see Custom Data Types.
3. Shareable data types
Custom data types submitted to the platform by an app developer. Once approved, any app can read data of a
shareable type, but only whitelisted apps as specified by the
developer can write data of that shareable type. For more information,
see Shareable Data Types.
I was able to do this with the help of this alogrithm. But remember due to Android fragmentation this code still removes some of the user's data and count it as penalty
private String dumpDataSet(DataSet dataSet, int x) {
List<String> days = new ArrayList<>();
days.add("Monday");
days.add("Tuesday");
days.add("Wednesday");
days.add("Thursday");
days.add("Friday");
days.add("Saturday");
days.add("Sunday");
String day = days.get(Math.round(x / 24));
Log.d(TAG, "\tDay: " + day);
Log.i(TAG, "Data returned for Data type: " + dataSet.getDataType().getName());
DateFormat dateFormat = getTimeInstance();
String text = "";
try {
for (DataPoint dp : dataSet.getDataPoints()) {
Log.i(TAG, "\tStepCount getStreamName: " + dp.getOriginalDataSource().getStreamName());
Log.i(TAG, "\tStepCount getStreamIdentifier: " + dp.getOriginalDataSource().getStreamIdentifier());
Log.i(TAG, "\tStepCount App Type: " + dp.getDataType().getName());
Log.i(TAG, "\tStepCount Type: " + dp.getOriginalDataSource().getType());
for (Field field : dp.getDataType().getFields()) {
Log.i(TAG, "\tField: " + field.getName() + " Value: " + dp.getValue(field));
text += dp.getValue(field);
String si[] = dp.getOriginalDataSource().getStreamIdentifier().toLowerCase().split(":");
if ((((si[si.length - 1].contains("soft")) || (si[si.length - 1].contains("step"))) && si[si.length - 1].contains("counter"))) {
totalSteps += Integer.parseInt(dp.getValue(field).toString());
Log.d(TAG, "\tStepCount" + " Added Steps -> " + dp.getValue(field) + " steps");
text += "\n\n";
} else {
Log.e(TAG, "\tStepCount PENALTY ---------------------------------------------------------------");
Log.e(TAG, "\tDay = " + day + " | Hour Number = " + x + " | StepCount" + " PENALTY DEDUCTED -> " + dp.getValue(field) + " steps");
Log.e(TAG, "\tStepCount PENALTY getStreamIdentifier: " + dp.getOriginalDataSource().getStreamIdentifier());
Log.e(TAG, "\tStepCount PENALTY getStreamName: " + dp.getOriginalDataSource().getStreamName());
Log.e(TAG, "\tStepCount PENALTY App Type: " + dp.getDataType().getName());
Log.e(TAG, "\tStepCount PENALTY Type: " + dp.getOriginalDataSource().getType());
Log.e(TAG, "\tStepCount PENALTY ---------------------------------------------------------------");
}
}
}
} catch (Exception ex) {
ex.getStackTrace();
}
return text;
}
----- UPDATE -----
You can also call
DataPoint.getOriginalDataSource().getAppPackageName()
to filter out smartwatches and other apps.
I tried as suggested by Ali Shah lakhani but
DataPoint.getOriginalDataSource().getAppPackageName();
/*I also tried but could not achieve what I wanted*/
DataPoint.getOriginalDataSource().getStreamName();
DataPoint.getOriginalDataSource().getStreamIdentifier();
did not work at least for me while retrieving data. I ended up using readDailyTotalFromLocalDevice() as shown below in order to capture steps captured by device only.
Fitness.HistoryApi.readDailyTotalFromLocalDevice(mApiClient, DataType.TYPE_STEP_COUNT_DELTA).await(1, TimeUnit.MINUTES)
I cross checked the same with some of the apps that avoids manual entries in their app and the count provided by the function above is exactly the same.
Note: If a user is having multiple devices and is using the app on all of them, readDailyTotalFromLocalDevice() will have different value for each and every device since the function is responsible for returning device specific data only.

Categories

Resources