In java methods everything is passed-by-value so i can change the object attributes passed to the method and expect that the original object attributes are changed. but in this method i get different result:
I have this method:
public Observable<Menu> makeMenu(Menu menu, NumberSettingChanges.MenuChanges changes) {
// Start flow with added and edited extensions
return Observable.from(changes.added.entrySet())
.mergeWith(Observable.from(changes.edited.entrySet()))
//Upload announcement voices or do nothing if extension is not an announcement
.flatMap(e -> {
if (AppTypeContract.APP_TYPE_ANNOUNCEMENT.equals(e.getValue().type)) {
return mMediaManager.uploadAsync(e.getValue().config.localPrompt)
.doOnNext(response -> {
//Update extension prompt with the storage path.
menu.config.extensions.get(e.getKey()).config.prompt = response.mPath;
menu.config.extensions.get(e.getKey()).config.localPrompt = "";
})
.flatMap(response -> Observable.just(e));
} else {
return Observable.just(e);
}
}
)
}
and i manipulate menu attributes in the flatmap:
menu.config.extensions.get(e.getKey()).config.localPrompt = "";
I call the method in the same class:
public Observable<NumberSetting> saveSettings(NumberSetting o, NumberSetting n) {
NumberSettingChanges changes = compareNumberSetting(o, n);
return makeMenu(n.day, changes.day)
.mergeWith(makeMenu(n.night, changes.night));
}
and finally:
saveSettings(ns, mNumberSettingNew).subscribe();
What i expect is that the mNumberSettingNew.menu.config.extensions.get(e.getKey()).config.prompt is changed but no change is happening after this call and the mNumberSettingNew has no change at all.
Note that i am sure that changing prompt line is done in the debug.
I don't think I could explain Java's parameter semantics any better than (or even half as good as) the link you referenced in your first paragraph so I won't try. The main point is: Everything in Java is passed by value (i. e. copied) but with objects what is copied is not the object itself but the reference to the object. So in other words the reference is passed by value.
So with respect to your particular problem: Yes, if you pass a reference to a mutable object to some rx-java code that reference will point to the same instance of the object. If you mutate the instance then the caller code will also be able to see the changes because they were made on the same instance. That's because rx-java is still only Java and cannot change the language semantics on that level.
Without seeing the whole code I am unsure what could be the problem here... When are you checking whether mNumberSettingsNew actually has the changes you were making in your doOnNext? If you check that immediately after saveSettings(ns, mNumberSettingNew).subscribe(); your uploadAsync may not have returned yet. You could try adding an actual Subscriber in your subscribe and check the result there.
On a more general note, I think you should try to avoid side-effects like this as much as you can when using rx-java. Your case - taking an input object, applying a set of (possibly asynchronous) changes to that object, and waiting for the changed output object - is a bit tricky, but I think it could be done with scan. Maybe something vaguely like this:
Observable.from(changes.added.entrySet())
.mergeWith(Observable.from(changes.edited.entrySet()))
.scan(menuBeforeAnyChanges, new Func2<Menu, Change, Menu>() {
public Menu call(final Menu previousVersionOfTheMenu, final Change nextChange) {
// since I don't know of a version of scan that can return
// an Observable you would I think you would have to adapt
// your code in here to be fully synchronous - but of
// course the scan itself could run asynchronously
final newVersionOfTheMenu = previousVersionOfTheMenu.applyChange(nextChange);
return newVersionOfTheMenu;
}
)
This would take the original Version of the menu, consecutively apply all the changes from added and edited and /emit/ every updated version of menu. So you would not have any side effects but simply subscribe to that observable with a Subscriber<Menu> and then take the last() Menu and that would be the one with all changes applied.
EDIT: Oh, I just saw that there is another method called reduce that does just that: first scan and then last or takeLast.
Related
I wrote the code as below.
suspend fun getDataByRegion(): Flow<Result?> {
// getRegion() return Flow<Region>
return getRegion().map { region: Region ->
repository.requestDataFromServer(region)
}
}
The problem is that repository.requestDataFromServer(region) called twice.
So I think I should use operators like zip or combine.
When using these operators, how can the second flow use the data of the first flow?
With combine and zip operators you can not depend on the other's result. So in general your chaining approach with map is OK.
There is several options you have:
Assuming your repository method is not called from anywhere else, the reason for it being called twice is that the region Flow is emitting twice. So try to find out why this is the case.
Anyhow if your region Flow method returns the same region twice you can fix it by simply adding
.distinctUntilChanged() after getRegion() like:
getRegion().distinctUntilChanged().map { region: Region ->
repository.requestDataFromServer(region)
}
It will make sure your region Flow doesn't emit redundantly with the same data. Alternatively add distinctUntilChanged() directly to the repository method, if this is always the expected behavior.
Ask yourself if this method really needs to return a stream (Flow). I guess you need a stream since the region can change at runtime and you want something in your app to update automatically? But if not you could simply convert the stream to a single result:
val region = getRegion().first()
repository.requestDataFromServer(region)
I am learning how to use the coroutines in kotlin. looking at some examples in the internet i found that within the context f the also operator the reference
it
is used. i could not find any explanation about the meaning of
it
please provide some brief explanantion about what does "it" mean
when you use the also method, it has 1 parameter.
Think of it in Java kinda like this:
foo.also(int it) {
// do stuff
}
In Kotlin, the it parameter is implicit (sometimes you might want to use it sometimes you don't).
If you want to rename it to something more readable you can
foo.also { newName ->
// do stuff with newName
}
Or just use it like it is
foo.also {
// do stuff with $it
}
So therefore when you are using a method (or a closure/lambda) if it has 1 parameter, then the implicit name of that parameter is always it.
Basically it represents the lambda parameter
let's say you want to perform anything on the variable but do to check the nullity first, you can do it like
var str:String?=null // str is of string type
now you can use it fail safe
str?.let{it:String// youll see like this
// now you can access str as **it**
}
it is the implicit name of a single parameter
For more information about it and this in scoping functions like also
I am new to Kotlin (coming from Delphi, which is object-oriented Pascal). I just want to ensure I am having functions return List<>s correctly:
Making up an absurdly simple example here:
fun firstTenInts(): List<Int> {
val iList: MutableList<Int> = mutableListOf()
for (i in 1..10)
iList.add(i)
return iList
}
So, my thoughts/questions here are:
Am I correct to use a MutableList within the function and simply return it (even though the function's type is List)?
Do I need to create a local MutableList variable? Do I need any local "list" variable? I am used to (again, Delphi) doing something like:
function firstTenInts: TStringList;
var
i: integer;
begin
result.Clear;
for i := 1 to 10 do
result.Add(IntToStr(i));
end;
which requires no "new" local variable. I simply "work" result which is very similar to Kotlin's return value except that it serves as a local variable of the function's type which can be "worked" throughout the function.
Is there no way to manipulate a Kotlin function's return value other than with another (locally created) variable?
Finally, I can rest assured that any local variables I create are destroyed when the function ends even though I'm "passing them back" - correct?
P.S. I know this is an absurd way to create a List of 10 integers. I am using this only as a framework for the questions/issues I have detailed above. Assume that the returned List will be of unknown size.
(Please do not suggest better ways of creating this list of integers; that is not what I am asking about).
Am I correct to use a MutableList within the function and simply return it (even though the function's type is List)?
Generally that's ok. You do such things if you require a list that can be mutated within the function but from outside you do not want it to be easily mutable (which doesn't mean that it isn't mutable; you could still cast it to a mutable list, i.e. firstTenInts() as MutableList would work and so you could also mutate it again).
Whether you need that mutable list in the function or not actually depends on you. For example just calling listOf(1, 2, 3) will return you a List<Int> immediately, i.e. fun first3Ints() = listOf(1,2,3) will immediately return a List<Int>.
Do I need to create a local MutableList variable? Do I need any local "list" variable? I am used to (again, Delphi) doing something like:
You do not need to, but you can. It's up to you what better suites your needs. This also applies to your local list variable. You do not necessarily need one, even though you will have one nonetheless under the hood. Example:
fun first3Ints() = listOf(1, 2, 3)
translates to something like the following:
fun first3Ints() : List<Int> {
val result = listOf(1, 2, 3)
return result
}
On smaller functions you at least can spare some variable declarations using direct assignments. You can also use something like apply, also, let, run or with to overcome val/var, e.g. (just simulating... all variants can be implemented easier):
fun first3Ints() = arrayListOf().apply {
add(1) // apply allows calling all methods of the receiver directly.. (receiver = arrayListOf...)
add(2)
add(3)
}
fun first2Ints() = arrayListOf().also { // similar to apply, but now there is an 'it' available... you could also name that variable differently, e.g. using .also { result -> result.add(1) }
it.add(1)
it.add(2)
}
Is there no way to manipulate a Kotlin function's return value other than with another (locally created) variable?
So this becomes yes (even though .. technically speaking there will be one)... it is possible, but you need to specify on what you basically want to operate, except for the case where you implement an extension function. Then this within the extension function actually becomes the object you called the function on.
Finally, I can rest assured that any local variables I create are destroyed when the function ends even though I'm "passing them back" - correct?
... yes and no. Yes, you can rest assured that any local variable will be garbage collected when need arises (except you are doing something very special). And there is also the no: you don't know when they will be destroyed. Regarding your returned value it is even more special: you are actually getting only a reference to an object, not the object itself... so somehow you basically get that local variable back. You may want to have a look at JVM / stack and heap and how the objects are referenced and stored...
Your return is right and you can use ArrayList instead of mutableListOf and there will be no problem...
but still I didn't understand your main problem but the main topic you were talking about shows that you want to be sure of using the list as a return and that is right man
There are various ways for creating ArrayList of 10 items (just as an example you given). You can find below code snippet such as example :
// directly returning array list as Kotlin has functionality to define such kind of function just like macros
fun firstTenInts(): List<Int> = arrayListOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// Or by using standard function apply
fun firstTenInts(): List<Int> = ArrayList<Int>().apply {
for (i in 1..10)
this.add(i)
}
// Or by declaring variable of ArrayList
fun firstTenInts(): List<Int> {
val iList = ArrayList<Int>()
for (i in 1..10)
iList.add(i)
return iList
}
// Or by using standard function with
fun firstTenInts(): List<Int> = with(ArrayList<Int>()) {
for (i in 1..10)
this.add(i)
return#with this
}
Above are the various examples defines how you can do differently (Although sample you provided is also a valid example).
Trying to understand RxJava here. We have this code:
public void notifyNewOwnersSynced() {
OrgTreeType orgTreeType = getOrgTreeType();
new OrgTreeQuerier().queryOrgUserIds().byOrgTreeType(orgTreeType).executeAsync()
.map(opt -> opt.isPresent() ? opt.get() : new HashSet<String>(0))
.subscribe(
this::onNewOrgUserIds,
e -> Log.exception(new Exception("Unable to update selected id filter for type " + orgTreeType, e))
);
}
private void onNewOrgUserIds(#NonNull Set<String> allIds) {
synchronized (mLock) {
for (String id : allIds) {
if (!mPreviousAllIds.contains(id)) {
mSelectedIDs.add(id);
}
}
Set<String> idsNoLongerInHierarchy = new HashSet<>(); //Because we can't remove while we are iterating.
for (String selectedId : mSelectedIDs) {
//If there is a selected ID not in the new hierarchy...
if (!allIds.contains(selectedId)) {
//Plan to remove it.
idsNoLongerInHierarchy.add(selectedId);
}
}
mSelectedIDs.removeAll(idsNoLongerInHierarchy);
mPreviousAllIds = allIds;
}
mSaveListener.saveChangesAndPostFilterChangedEvent();
postSelectedIdsChangedEvent();
}
We have two lint warnings showing up on it: The result of subscribe is not used. and Result of single.subscribe() is ignored
This is in a class that is used by our UI(Fragments) to keep track of what users have been selected.
But on a larger scale we have a lot of spots in our code that use Rx like this to do something in the background (map something, network call, save data to the DB) and we don't every use the result.
Can I safely suppress these errors? or do I need to add handling for the Disposables?
How can I know when I need to dispose of a disposable?
Although not a comprehensive list of cases, I think I can point out some that I've faced during my career and might help you out.
The most common scenario I faced was when we make network calls that take too long and the app is put in the background. If not disposed, the result of the network will be forwarded to the subscriber. This is not really the issue. The problem is that usually the subscriber wants to change something UI related, which crashes the app. In this case, you dispose because you are no longer interested in receiving these events.
There are cases where the way the subscriber handles the result wouldn't be problematic, but the IDE has no way to know this and hence it warns you all the time.
I'm sure there are tons of other reasons why disposing should be handled - i.e., when observables acquire resources when subscribed and release them once unsubscribed from. So in general I guess it's good to handle the disposables. I only ever kept a disposable undisposed when I wanted to keep downloading files in the background and to be honest with you, I'm not even sure if this is a good practise.
Adding to this, if an observable or any of the other flavored observables (single, maybe, etc.) terminates, then it's disposed automatically.
I am using Room with RxJava2 to implement my data layer via Repository Pattern principles.
I have the following simple code which decides where to pick data from.
#Override
public Single<Team> getTeamById(int teamId) {
return Single.
concat(local.getTeamById(teamId),
remote.getTeamById(teamId)).
filter(team -> team != null).
firstOrError();
}
The problem here is that instead of going to the remote source , it returns an error from the first source (local) if the data was not available.
android.arch.persistence.room.EmptyResultSetException: Query returned empty result set: select * from teams where id = ?
How should I instruct the concat to forgo any error that is received and continue its concatenation?
Aslong you're not sure if you can receive at least one Team from you data provider, you should probably think of using Maybe instead of Single.
You can lookup the definition here:
Single as it states:
it always either emits one value or an error notification
Use Maybe instead:
Maybe
there could be 0 or 1 item or an error signalled by some reactive
source
As your error already states there seems to be a problem while extracting results from your query.
Handle your result extraction correctly, so that you check if there are results before trying extracting any. Therefor the Maybe would either return 0 or 1 item, and not throw any error at all when no Team was found.
You cannot pass null in RxJava2. So whenever your local repo is empty you just can't return null in your single. There was a question o stack about handling null objects: Handle null in RxJava2
Also here you can find an article showing you preferred implementation of repository pattern using RxJava2:
https://android.jlelse.eu/rxjava-2-single-concat-sample-for-repository-pattern-1873c456227a
So simplifying - instead of returning null from both local and remote repo pass some sort of "empty" object. That will be useful also in your business logic allowing you to recognize empty set of data.
If you want to continue when the first source errors (instead of completing as empty), you can use onErrorResumeNext instead of concat (I assume both get calls return Observable, adjust as necessary):
return local.getTeamById(teamId)
.onErrorResumeNext(error -> {
if (error instanceof EmptyResultSetException) {
return remote.getTeamById(teamId));
}
return Observable.error(error);
})
.firstOrError();
I used Maybe to solve my Rxjava2 repository pattern problem.
In your case, I would use the following code to sort it out:
//you may need to rewrite your local.getTeamById method
protected Maybe<Team> getTeamById(int teamId) {
Team team = localDataHelper.getTeamById(teamId);
return team != null ? Maybe.just(team) : Maybe.empty();
}
#Override
public Single<Team> getTeamById(int teamId) {
Maybe<Team> cacheObservable = local.getTeamById(teamId);
Maybe<Team> apiCallObservable = remote.getTeamById(teamId).toMaybe();
return Maybe.concat(cacheObservable, apiCallObservable)
.toSingle();
}