RxJava2 merge cached data and subject - kotlin

I have an disk storage that returns an Object? from disk (Could be already saved or not) and an BehaviourSubject (This data comes from other class call, check code below):
Code is:
private val subject: Subject<Optional<Element>> = BehaviorSubject.create()
fun getElements(): Observable<List<Element>> =
Observable.concat(Observable.just(storage.getElement()), subject)
.filter({ it.isPresent })
.take(1)
.flatMapSingle {
Observable.just(it.get())
.flatMapIterable { it.categories }
.toList()
}
fun updateSubject(response: Response) {
storage.save(response.element) //Save element in storage
subject.onNext(response.element.toOptional())
}
My problem is, in other class I do
getElements().subscribe(onElements(), onError());
First time, when storage has null it does nothing, even I've got a breakpoint in subject.onNext(response.element.toOptional()), hoping that onNext will trigger a stream for getElements, but nothing happens.
Second time, when I've already saved in storage the received element (So, storage.getElement() returns something) it works fine.
My functional description is:
Get element from both cache and subject, take first that arrives, and return it (First time it will be who the comes subject one), next time, i'm hoping that first one will be the storage one.

I am assuming that storage is some sort of persistence object so it so storage.getElement() might return a valid object at the time you create the subject?
If that is the case, then I think you should check to see if you have a stored object before you create the subject, if so use BehaviorSubject.createDefalut(storedObject) if it does exist or BehaviorSubject.create() if not.
Then inside your getElements() function I think you can just use subject.filter() ....

Your null optional elements are being filtered out by your .filter { it.isPresent } call so nothing gets emitted downstream.

Related

How to return an int value stuck in a for loop but a callback in Kotlin?

I am trying to get the size of this firebase collection size of documents, and for some reason in Kotlin, I can't seem to get this to work. I have declared a variable to be zero in an int function and I put it inside a for loop where it increments to the size of the range. Then when I return the value, it is zero. Here is the code I have provided, please help me as to why it is returning zero.
This is just what is being passed to the function
var postSize = 0
That is the global variable, now for below
val db = FirebaseFirestore.getInstance()
val first = db.collection("Post").orderBy("timestamp")
getPostSize(first)
This is the function
private fun getPostSize(first: Query){
first.get().addOnSuccessListener { documents ->
for(document in documents) {
Log.d(TAG, "${document.id} => ${document.data}")
getActualPostSize(postSize++)
}
}
return postSize
}
private fun getActualPostSize(sizeOfPost: Int): Int {
// The number does push to what I am expecting right here if I called a print statement
return sizeOfPost // However here it just returns it to be zero again. Why #tenffour04? Why?
}
It is my understanding, according to the other question that this was linked to, that I was suppose to do something like this.
This question has answers that explain how to approach getting results from asynchronous APIs, like you're trying to do.
Here is a more detailed explanation using your specific example since you were having trouble adapting the answer from there.
Suppose this is your original code you were trying to make work:
// In your "calling code" (inside onCreate() or some click listener):
val db = FirebaseFirestore.getInstance()
val first = db.collection("Post").orderBy("timestamp")
val postSize = getPostSize(first)
// do something with postSize
// Elsewhere in your class:
private fun getPostSize(first: Query): Int {
var postSize = 0
first.get().addOnSuccessListener { documents ->
for(document in documents) {
Log.d(TAG, "${document.id} => ${document.data}")
postSize++
}
}
return postSize
}
The reason this doesn't work is that the code inside your addOnSuccessListener is called some time in the future, after getPostSize() has already returned.
The reason asynchronous code is called in the future is because it takes a long time to do its action, but it's bad to wait for it on the calling thread because it will freeze your UI and make the whole phone unresponsive. So the time-consuming action is done in the background on another thread, which allows the calling code to continue doing what it's doing and finish immediately so it doesn't freeze the UI. When the time-consuming action is finally finished, only then is its callback/lambda code executed.
A simple retrieval from Firebase like this likely takes less than half a second, but this is still too much time to freeze the UI, because it would make the phone seem janky. Half a second in the future is still in the future compared to the code that is called underneath and outside the lambda.
For the sake of simplifying the below examples, let's simplify your original function to avoid using the for loop, since it was unnecessary:
private fun getPostSize(first: Query): Int {
var postSize = 0
first.get().addOnSuccessListener { documents ->
postSize = documents.count()
}
return postSize
}
The following are multiple distinct approaches for working with asynchronous code. You only have to pick one. You don't have to do all of them.
1. Make your function take a callback instead of returning a value.
Change you function into a higher order function. Since the function doesn't directly return the post size, it is a good convention to put "Async" in the function name. What this function does now is call the callback to pass it the value you wanted to retrieve. It will be called in the future when the listener has been called.
private fun getPostSizeAsync(first: Query, callback: (Int) -> Unit) {
first.get().addOnSuccessListener { documents ->
val postSize = documents.count()
callback(postSize)
}
}
Then to use your function in your "calling code", you must use the retrieved value inside the callback, which can be defined using a lambda:
// In your "calling code" (inside onCreate() or some click listener):
val db = FirebaseFirestore.getInstance()
val first = db.collection("Post").orderBy("timestamp")
getPostSizeAsync(first) { postSize ->
// do something with postSize inside the lambda here
}
// Don't try to do something with postSize after the lambda here. Code under
// here is called before the code inside the lambda because the lambda is called
// some time in the future.
2. Handle the response directly in the calling code.
You might have noticed in the above solution 1, you are really just creating an intermediate callback step, because you already have to deal with the callback lambda passed to addOnSuccessListener. You could eliminate the getPostSize function completely and just deal with callbacks at once place in your code. I wouldn't normally recommend this because it violates the DRY principle and the principle of avoiding dealing with multiple levels of abstraction in a single function. However, it may be better to start this way until you better grasp the concept of asynchronous code.
It would look like this:
// In your "calling code" (inside onCreate() or some click listener):
val db = FirebaseFirestore.getInstance()
val first = db.collection("Post").orderBy("timestamp")
first.get().addOnSuccessListener { documents ->
val postSize = documents.count()
// do something with postSize inside the lambda here
}
// Don't try to do something with postSize after the lambda here. Code under
// here is called before the code inside the lambda because the lambda is called
// some time in the future.
3. Put the result in a LiveData. Observe the LiveData separately.
You can create a LiveData that will update its observers about results when it gets them. This may not be a good fit for certain situations, because it would get really complicated if you had to turn observers on and off for your particular logic flow. I think it is probably a bad solution for your code because you might have different queries you want to pass to this function, so it wouldn't really make sense to have it keep publishing its results to the same LiveData, because the observers wouldn't know which query the latest postSize is related to.
But here is how it could be done.
private val postSizeLiveData = MutableLiveData<Int>()
// Function name changed "get" to "fetch" to reflect it doesn't return
// anything but simply initiates a fetch operation:
private fun fetchPostSize(query: Query) {
first.get().addOnSuccessListener { documents ->
postSize.value = documents.count()
}
}
// In your "calling code" (inside onCreate() or some click listener):
val db = FirebaseFirestore.getInstance()
val first = db.collection("Post").orderBy("timestamp")
fetchPostSize(first)
postSizeLiveData.observer(this) { postSize ->
// Do something with postSize inside this observer that will
// be called some time in the future.
}
// Don't try to do something with postSize after the lambda here. Code under
// here is called before the code inside the lambda because the lambda is called
// some time in the future.
4. Use a suspend function and coroutine.
Coroutines allow you to write synchronous code without blocking the calling thread. After you learn to use coroutines, they lead to simpler code because there's less nesting of asynchronous callback lambdas. If you look at option 1, it will become very complicated if you need to call more than one asynchronous function in a row to get the results you want, for example if you needed to use postSize to decide what to retrieve from Firebase next. You would have to call another callback-based higher-order function inside the lambda of your first higher-order function call, nesting the future code inside other future code. (This is nicknamed "callback hell".) To write a synchronous coroutine, you launch a coroutine from lifecycleScope (or viewLifecycleOwner.lifecycleScope in a Fragment or viewModelScope in a ViewModel). You can convert your getter function into a suspend function to allow it to be used synchronously without a callback when called from a coroutine. Firebase provides an await() suspend function that can be used to wait for the result synchronously if you're in a coroutine. (Note that more properly, you should use try/catch when you call await() because it's possible Firebase fails to retrieve the documents. But I skipped that for simplicity since you weren't bothering to handle the possible failure with an error listener in your original code.)
private suspend fun getPostSize(first: Query): Int {
return first.get().await().count()
}
// In your "calling code" (inside onCreate() or some click listener):
lifecycleScope.launch {
val db = FirebaseFirestore.getInstance()
val first = db.collection("Post").orderBy("timestamp")
val postSize = getPostSize(first)
// do something with postSize
}
// Code under here will run before the coroutine finishes so
// typically, you launch coroutines and do all your work inside them.
Coroutines are the common way to do this in Kotlin, but they are a complex topic to learn for a newcomer. I recommend you start with one of the first two solutions until you are much more comfortable with Kotlin and higher order functions.

Kotlin - Here Maps - Get address out of callback function

I am attempting to get the address out of the callback function. I have been reading the documentation for CallBacks and some posts but still don't get why this is not working, as at the moment of returning the 'address' variable the callback has already finished.
private fun getAddressForCoordinates(geoCoordinates: GeoCoordinates):String {
address = "unchanged"
val maxItems = 1
val reverseGeocodingOptions = SearchOptions(LanguageCode.EN_GB, maxItems)
searchEngine.search(geoCoordinates, reverseGeocodingOptions, addressSearchCallback)
return address
}
private val addressSearchCallback =
SearchCallback { searchError, list ->
if (searchError != null) {
//showDialog("Reverse geocoding", "Error: $searchError")
Toast.makeText(context, "Error: $searchError", Toast.LENGTH_LONG).show()
return#SearchCallback
}
Toast.makeText(
context,
"Reverse geocoded address:" + list!![0].address.addressText,
Toast.LENGTH_LONG
).show()
address = list[0].address.addressText
}
From your code and comment I assume you are not familiar with the concept of asynchronous execution. That concept was well described here. I'll quote the main point:
When you execute something synchronously, you wait for it to finish
before moving on to another task. When you execute something
asynchronously, you can move on to another task before it finishes.
The fact that search() requires providing a callback and it doesn't simply return search results, is a good indication that it is most probably asynchronous. Invoking it is like saying: "Search for the data in the background and let me know when you have it. This is my email address - please send me my results there". Where email address is your callback. Invoking search() method does not block execution of your code, it does not wait for results - it only schedules searching and returns almost immediately.
Asynchronous processing is usually more tricky than a regular, synchronous code, but in many cases it is more efficient. In your case you can either try to "convert" original async API of the library to sync API that your code expects - but this is not recommended approach. Or you can redesign your code, so it will work asynchronously. For example, instead of doing this:
fun yourMethodThatNeedsAddress() {
val address = getAddressForCoordinates()
doSomethingWithAddress(address)
}
You need to do this:
fun yourMethodThatNeedsAddress() {
scheduleGetAddressForCoordinates() // renamed getAddressForCoordinates()
}
fun addressSearchCallback() {
...
doSomethingWithAddress(address)
}
So, whatever you planned to do with the acquired address, you can't do this straight after you started searching. You need to wait for a callback and then continue with processing of your address from there.
The SearchEngine from the 4.x HERE SDK needs an online connection as it is fetching results from a remote backend. This may take a few milliseconds, depending on your network connection. So, whenever you perform a search request, you need to wait until the callback is called:
searchEngine.search(geoCoordinates, reverseGeocodingOptions, addressSearchCallback)
When you call this, you pass addressSearchCallback as parameter. The implementation for addressSearchCallback can look like in your example. It will be called whenever the operation has finished. If the device is offline, then an error will be shown.
Note that the search() method is not returning any results immediately. These are passed to the callback, which happens asynchronously on a background thread. Thus, your application can continue to work without blocking any UI.
Once results are retrieved, the callback will be executed by the HERE SDK on the main thread.
So, if your code needs to do something with the address result, you have to do it inside the onSearchCompleted() method defined by the SearchCallback. If you write it in plain Java without lambda notation, it is more visible: You create a new SearchCallback object and pass it as parameter to the SearchEngine. The SearchEngine stores the object and executes the object's onSearchCompleted() whenever it thinks it's the right time:
private SearchCallback addressSearchCallback = new SearchCallback() {
#Override
public void onSearchCompleted(#Nullable SearchError searchError, #Nullable List<Place> list) {
if (searchError != null) {
showDialog("Reverse geocoding", "Error: " + searchError.toString());
return;
}
// If error is null, list is guaranteed to be not empty.
showDialog("Reverse geocoded address:", list.get(0).getAddress().addressText);
// Here is the place to do something more useful with the Address object ...!
}
};
I took this from this GitHub code snippet. Note that there is also an OfflineSearchEngine, that works without an internet connection, but for some reason it follows the same pattern and executes the task asynchronously.
private void getAddressForCoordinates(GeoCoordinates geoCoordinates) {
int maxItems = 1;
SearchOptions reverseGeocodingOptions = new SearchOptions(LanguageCode.EN_GB, maxItems);
searchEngine.search(geoCoordinates, reverseGeocodingOptions, new SearchCallback() {
#Override
public void onSearchCompleted(#Nullable SearchError searchError, #Nullable List<Place> list) {
if (searchError != null) {
showDialog("Reverse geocoding", "Error: " + searchError.toString());
return;
}
// If error is null, list is guaranteed to be not empty.
showDialog("Reverse geocoded address:", list.get(0).getAddress().addressText);
}
});
}
SearchEngine, a SearchOptions instance needs to be provided to set the desired LanguageCode. It determines the language of the resulting address. Then we can make a call to the engine's search()-method to search online for the address of the passed coordinates. In case of errors, such as when the device is offline, SearchError holds the error cause.
The reverse geocoding response contains either an error or a result: SearchError and the result list can never be null at the same time - or non-null at the same time.
The Address object contained inside each Place instance is a data class that contains multiple String fields describing the address of the raw location, such as country, city, street name, and many more. Consult the API Reference for more details. If you are only interested in receiving a readable address representation, you can access addressText, as shown in the above example. This is a String containing the most relevant address details, including the place's title.
Please refer to following link for detailed documentation on search() function and parameters associated with it.
https://developer.here.com/documentation/android-sdk-explore/4.4.0.2/dev_guide/topics/search.html

Repeat request multiple times with different params using RxJava

I need to load some data from server page by page until all the data is loaded. The data is considered to be fully loaded if at some point I received fewer items than I've requested. This is the working solution that I have right now:
return Observable.fromCallable { 0 }
.delay(500, TimeUnit.MILLISECONDS)
.repeat()
.scan { previousPage, _ -> previousPage + 1}
.concatMap { doLongFetch(it) }
.takeUntil { it.size < 100 }
fun doLongFetch(page: Int): Observable<List<ListItem>>() {
//Here I do the loading
}
However, there's a problem with the source observable. As you can see, it emits new values every 500 milliseconds to provide some input for the scan function. The delay is required since otherwise, it would emit thousands of values in a very short period of time, which is not required at all. Ideally, I want to remove that delay completely and make sure that the source observable emits another value only after the downstream has handled the previous one (meaning that the data has been requested and processed).
Any ideas on how I can do that?

Kotlin-Firebase-cannot utilize the string read from database further down in the code

I have managed to read data from my firebase database but cant seem to re-use the String which has been read.
My successful read is as per below. When i check the logcat for the Log.d("Brand") it actually shows the String as expected.
brandchosenRef=FirebaseDatabase.getInstance().reference
val brandsRef = brandchosenRef.child("CarList2").orderByChild("Car").equalTo(searchable_spinner_brand.selectedItem.toString())
val valueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
for(ds in dataSnapshot.children){
Log.d("spinner brand",searchable_spinner_brand.selectedItem.toString())
val Brand = ds.child("Brand").getValue(String::class.java)
val brandselected= Brand.toString()
Log.d("Brand","$brandselected")
selectedbrand== brandselected
Log.d("selected brand",selectedbrand)
}
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d("Branderror","error on brand")
}
}
brandsRef.addListenerForSingleValueEvent(valueEventListener)
What i am trying to do is write "selectedbrand" into a separate node using the following:
val carselected = searchable_spinner_brand.selectedItem.toString()
val dealref = FirebaseDatabase.getInstance().getReference("Deal_Summary2")
val dealsummayId = dealref.push().key
val summaryArray = DealSummaryArray(dealsummayId.toString(),"manual input for testing","brand","Deal_ID",carselected,extrastext.text.toString(),otherinfo.text.toString(),Gauteng,WC,KZN,"Open")
dealref.child(dealsummayId.toString()).setValue(summaryArray).addOnCompleteListener{
}
Note, in the above i was inputting "manual input for testing" to check that my write to Firebase was working and it works as expected. if i replace that with selectedbrand, then i get the below error.
kotlin.UninitializedPropertyAccessException: lateinit property selectedbrand has not been initialized
the summary array indicated above is defined in a separate class as follows. and as seen "manual input for testing is declared as String.
class DealSummaryArray(val id:String,val brand:String,val Buyer_ID:String,val Deal_ID:String,val Car:String,val extras:String,val other_info:String,val Gauteng:String,val Western_Cape:String,val KZN:String,val Status:String) {
constructor():this("","","","","","","","","","",""){
}
}
My question simply put, it why can i not re-use the value i read from the database? even if i was not trying to re-write it to a new node i cannot seem to utilize the value outside of the firebase query.
I seem to get this problem everywhere in my activities and have to find strange work around's like write to a textview and then reference the textview. please assist.
Data is loaded from Firebase asynchronously, as it may take some time before you get a response from the server. To prevent blocking the application (which would be a bad experience for your users), your main code continues to run while the data is being loaded. And then when the data is available, Firebase calls your onDataChange method.
What this means in practice is that any code that needs the data from the database, needs to be inside the onDataChange method or be called from there. So any code that requires selectedbrand needs to be inside onDataChange or called from there (typically through a callback interface).
Also see:
How to check a certain data already exists in firestore or not, which contains example code including of the callback interface, in Java.
getContactsFromFirebase() method return an empty list, which contains a similar example for the Firebase Realtime Database.
Setting Singleton property value in Firebase Listener, which shows a way to make the code behave more synchronous, and explains shows that this may not work on various Android versions.

Kotlin Kovenant returns the same object for all promises

I'm trying to use Kotlin Kovenant because I want a promise-based solution to track my retrofit calls.
What I did first was this:
all (
walkingRoutePromise,
drivingRoutePromise
) success { responses ->
//Do stuff with the list of responses
}
where the promises I pass are those that are resolved at the completion of my retrofit calls. However "responses" is a list of two identical objects. When debugging, I can confirm that two different objects with different values are being passed to the respective resolve methods. However kovenant returns two identical objects (same location in memory)
My next attempt was this:
task {
walkingRoutePromise
} then {
var returnval = it.get()
walkingDTO = returnval.deepCopy()
drivingRoutePromise
} success {
val returnval = it.get()
drivingDTO = returnval.deepCopy()
mapRoutes = MapRoutes(walkingDTO!!, drivingDTO!!)
currentRoute = mapRoutes!!.walking
callback()
}
Where I tried to do the calls one at a time and perform deep copies of the results. This worked for the first response, but then I found that it.get() in the success block - the success block of the second call - is the same unchanged object that I get from it.get() in the "then" block. It seems Kovenant is implemented to use one object for all of its resolutions, but after you resolve once, the single object it uses for the resolutions cannot be changed. What am I supposed to do if I want to access unique values from promise.resolve(object)? Seems like a very broken system.