Why is this complaining about a miss-type? map supposed to transfor the value of an observable to another, but it's expecting me to return an observable from the map
override fun fetchExercises(): Observable<List<Exercise>> {
return FirebaseDatabase.getInstance()
.getReference("exercises")
.observeSingleEvent()
.map { snapshot: DataSnapshot? -> object: List<Exercise>
// Error here
return listOf<Exercise>()
}
}
fun DatabaseReference.observeSingleEvent(): Observable<DataSnapshot> {
return Observable.create(object : Observable.OnSubscribe<DataSnapshot> {
override fun call(subscriber: Subscriber<in DataSnapshot>) {
val valueEventListener = object: ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot?) {
subscriber.onNext(snapshot)
subscriber.onCompleted()
}
override fun onCancelled(error: DatabaseError?) {
subscriber.onError(FirebaseDatabaseThrowable(error))
}
}
addListenerForSingleValueEvent(valueEventListener)
}
})
}
#zsmb13 has pointed out the correct answer right there. Here, I would like to comment on some mistake made in your lambda expression.
.map { snapshot: DataSnapshot? -> object: List<Exercise>
// Error here
return listOf<Exercise>()
}
object: List<Exercise> here is not a valid syntax since the body goes after an -> sign inside the lambda expression. We do not specify the return type inside the body.
Also, the return here means to return from fetchExercises() as described in Kotlin docs:
A return statement without a label always returns from the function declared with the fun keyword. This means that a return inside a lambda expression will return from the enclosing function, whereas a return inside an anonymous function will return from the anonymous function itself.
As what #zsmb13 said, return is not needed in most of the case. If you really want it(for flow control), you can use qualified returns:
//This is answer from #zsmb13
.map { snapshot: DataSnapshot? ->
return#map listOf<Exercise>()
}
This part of your code
.map { snapshot: DataSnapshot? -> object: List<Exercise>
says that the type of the snapshot parameter that this lambda receives is DataSnapshot? -> object: List<Exercise> (which actually isn't a valid function type, that would be DataSnapshot? -> List<Exercise>).
I believe what you actually wanted to do is the following, a snapshot parameter that just has the type DataSnapshot:
.map { snapshot: DataSnapshot? ->
listOf<Exercise>()
}
The return type of the lambda doesn't have to be specified here, it will just be inferred. You also don't need to use the return keyword in a lambda, because the last expression in it is what's returned.
Related
I am new to Kotlin and I am making a method that makes a call to an interface of Endpoints and uses one of the methods present there. I am using Observable<> instead of Call<> into the response. I wanted to know how to obtain the response body() in the "result" above. This is my method
private fun refreshUser(userLogin: String) {
executor.execute {
// Check if user was fetched recently
val userExists = userDao.hasUser(userLogin, getMaxRefreshTime(Date())) != null
// If user have to be updated
if (!userExists) {
disposable = endpoints.getUser(userLogin)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> /*Get the response body() HERE*/},
{ error -> Log.e("ERROR", error.message) }
)
}
}
}
It all depends on how you have defined the Retrofit interface. In order to get the Response you need to return something from the interface that looks like:
fun getUsers() : Observable<Response<User>>
Then inside { result -> /*Get the response body() HERE*/}, you will get something of the form Response<User>, which has the response's body.
Also to note, you do not need to enclosing executor if you leverage Room for the dao interactions; it has RxJava support. You can use RxJava operators to combine the dao lookup with the server call.
See this tutorial
https://medium.freecodecamp.org/rxandroid-and-kotlin-part-1-f0382dc26ed8
//Kotlin
Observable.just("Hello World")
.subscribeOn(Schedulers.newThread())
//each subscription is going to be on a new thread.
.observeOn(AndroidSchedulers.mainThread()))
//observation on the main thread
//Now our subscriber!
.subscribe(object:Subscriber<String>(){
override fun onCompleted() {
//Completed
}
override fun onError(e: Throwable?) {
//TODO : Handle error here
}
override fun onNext(t: String?) {
Log.e("Output",t);
}
})
if you wanna use retrofit 2 and rxjava 2
https://medium.com/#elye.project/kotlin-and-retrofit-2-tutorial-with-working-codes-333a4422a890
interface WikiApiService {
#GET("api.php")
fun hitCountCheck(#Query("action") action: String,
#Query("format") format: String,
#Query("list") list: String,
#Query("srsearch") srsearch: String):
Observable<Model.Result>
}
Observable is the class response.
private fun beginSearch(srsearch: String) {
disposable =
wikiApiServe.hitCountCheck("query", "json", "search", srsearch)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> showResult(result.query.searchinfo.totalhits) },
{ error -> showError(error.message) }
)
}
If, as you mentioned to #Emmanuel, the return type of your getUser() method is Observable<Response<User>> then calling result.body() will yield the resulting User.
{ result ->
val user: User = result.body()
}
If however, you are looking for the the raw response, you can instead call result.raw().body(); which will return an okhttp3.ResponseBody type.
{ result ->
val body: ResponseBody = result.raw().body()
val text: String = body.string()
}
Re-edit : I will use the result of this method to initialize the visibility of some buttons in my view
The accepted answer seems good in theory but the return value of first method is Flowable> instead of Flowable. Hence I cannot pass it as a parameter to subscription since it requires Flowable but it is Flowable>
Question Before Edit
I am using RxJava to observe a method I am required to call from SDK. Using this method I am trying to make an assertion about the existence of something but I do not know how long the call will take so it is hard for me to say terminate the subscription after x seconds.
override fun doesExist(): Boolean {
var doesExist = false
var subscription : Subscription
val flowable = Flowable.just(SDK.searchContact("contact"))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : FlowableSubscriber<Flowable<List<Contact>>> {
override fun onSubscribe(s: Subscription) {
subscription = s
}
override fun onNext(t: Flowable<List<Contact>>?) {
doesExist = true
}
override fun onComplete() {
Log.d("tagtagcomplete", "tagtagcomplete")
}
override fun onError(e: Throwable?) {
Log.d("tagtagerror", "tagtagerror")
}
return doesExist
}
So what I want to do is return true if I won't get any result from searchContract method and return false if I get a result. While using observables, I can create a subscription object and call it's methods but I could not figure out how to do it right way with flowables.
My confusion is the following: Method to sdk returns a Flowable<List<Contact>>> but in my opinion I need to check if a contact exists only once and stop
Right now my method does not go inside onError, onNext or onComplete. It just returns doesExist
I reedit my answer,following return type is you need.
fun doesExist(): Flowable<Single<Boolean>> {
return Flowable.just(Single.just(SDK.searchContact("contact")).map{ it.isEmpty()})
}
I am using Room in a project and I have the following DAO interface:
#Dao
interface BalanceDao {
#Query("SELECT * FROM balance")
fun getAllBalances(): Flowable<List<BalanceDataModel>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertBalanceList(balanceDataModelList: List<BalanceDataModel>): List<Long>
}
Insert works fantastically, but the getAllBalances() method does not work, since it does not retrieve any row. I extracted the DB after the insertion and I can see all the rows there; SELECT * from balance works perfectly when locally executed to the extracted DB with a desktop app.
I also tried to change the return type of getAllBalances() from Flowable<List<BalanceDataModel>> to Single<List<BalanceDataModel>> but the same keeps happening: no results.
I have a PortfolioManager, from which I call the following method and I pass the observer and the owner from my Fragment.
fun getAllCoins(owner: LifecycleOwner, observer: Observer<List<Balance>>) {
portfolioViewModel
.balanceListLiveData
.observe(owner, observer)
return portfolioViewModel.getPortfolioCoins()
}
Then in the PortfolioManager, I have access to a ViewModel, from which I call the following method:
fun getPortfolioCoins() {
coinRepository
.getBalanceListPortfolio()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(onSuccess = {
balanceListLiveData.postValue(it)
})
}
And in the repository class, I have this:
fun getBalanceListPortfolio(): Single<List<Balance>> {
val converter = BalanceDataModelConverter()
val balanceList = mutableListOf<Balance>()
coinDatabase.balanceDao()
.getAllBalances()
.toObservable()
.flatMapIterable { t: List<BalanceDataModel> -> t }
.map { t: BalanceDataModel ->
{
val a: Balance = converter.fromFirstToSecond(t)
balanceList.add(a)
}
}
return Single.just(balanceList.toList())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
Anybody knows what could be wrong? Thanks a lot in advance!
I just saw your recent edit. I think your problem is you are returning an empty list in getBalanceListPortfolio. This observable was not subscribed upon.
coinDatabase.balanceDao()
.getAllBalances()
.toObservable()
.flatMapIterable { t: List<BalanceDataModel> -> t }
.map { t: BalanceDataModel ->
{
val a: Balance = converter.fromFirstToSecond(t)
balanceList.add(a)
}
}
I suggest you convert this to list and return this observable (something like this, cant try to compile. I have no working env right now).
return coinDatabase.balanceDao()
.getAllBalances()
.toObservable()
.flatMapIterable { t: List<BalanceDataModel> -> t }
.map { t: BalanceDataModel -> converter.fromFirstToSecond(t) }
.toList()
.toObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
This is wrapped as an observable to you might want to change your type to Observable (instead of single) first, just to test. Let me know the results.
I'm converting my function having lambda as parameter into inline function for performance improvement.
I have list of lambda of type MutableList<(Authenticate) -> Unit> variable as data member in class. When I try to adding lambda parameter into the list.
Kotlin compiler says:
Illegal usage of inline parameter callback
Here is the code
// Some code skipped
object Odoo {
val pendingAuthenticateCallbacks = mutableListOf<(Authenticate) -> Unit>()
inline fun authenticate(
login: String, password: String, database: String,
quick: Boolean = false, crossinline callback: Authenticate.() -> Unit
) {
// Following statement has error saying
// Illegal usage of inline parameter callback. add 'noinline' modifier to parameter declaration.
pendingAuthenticateCallbacks += callback
// Error in above statement
if (pendingAuthenticateCallbacks.size == 1) {
// Retrofit2 Object boxing code skipped
val call = request.authenticate(requestBody)
call.enqueue(object : Callback<Authenticate> {
override fun onFailure(call: Call<Authenticate>, t: Throwable) {
(pendingAuthenticateCallbacks.size - 1 downTo 0)
.map { pendingAuthenticateCallbacks.removeAt(it) }
.forEach {
it(Authenticate(httpError = HttpError(
Int.MAX_VALUE,
t.message!!
)))
}
}
override fun onResponse(call: Call<Authenticate>, response: Response<Authenticate>) {
(pendingAuthenticateCallbacks.size - 1 downTo 0)
.map { pendingAuthenticateCallbacks.removeAt(it) }
.forEach {
it(Authenticate(httpError = HttpError(
response.code(),
response.errorBody()!!.string()
)))
}
}
})
}
}
}
Inlining inserts the code in the lambda directly into the call site, which removes the overhead of having a function object.
For example, this roughly results in main here:
fun withLambda(lambda: () -> Unit) {
lambda()
}
inline fun inlinedLambda(lambda: () -> Unit) {
lambda()
}
fun main(args: Array<String>) {
withLambda { println("Hello, world") }
inlinedLambda { println("Hello, world") }
}
being converted to this:
fun main(args: Array<String>) {
withLambda { println("Hello, world") }
println("Hello, world") // <- Directly inserted!
}
If you have
pendingAuthenticateCallbacks += callback
This is impossible because callback must be an object in order for it to be added to the list.
You need to add the noinline modifier.
A rough approximation would be to say that an inlined lambda cannot be treated as an object, as it doesn't really exist as an object. It is used directly instead of being created as an object.
Of course, you could create a containing lambda:
pendingAuthenticateCallbacks += { callback() } // Not a good idea
but this would entirely defeat the point of inlining (don't do this!).
However, making the parameter noinline would mean your method now has zero lambda parameters that can be inlined, so you might as well just remove the inline modifier as performance benefit would be minimal.
The compiler should recognize this:
Note that if an inline function has no inlinable function parameters and no reified type parameters, the compiler will issue a warning, since inlining such functions is very unlikely to be beneficial.
The main reason for inlining methods is for performance when using lambdas and for reified generic type parameters. As of Kotlin 1.1, it is also possible to have an inline property accessor for properties without a backing field.
In short, if you have no lambda parameters (or no reified type parameters, in which case you must), it is usually pointless to mark a function as inline.
I searched for long time, I only can find a solution using addListenerForSingle Event which is triggered onDataChanged. It only triggered when there was a change on database....
How can I get a single value with page loaded (I mean onCreate)?
For example, get key is really easy,
mReference.child("kotran").child("isactive").getKey()
//isactive
But there no way to get value.
addListenerForSingleValueEvent will be called once, the first time you register such listener.
Take a look at this generic Kotlin example for the whole function:
override fun <T : Any> getValue(typeClass: KClass<T>, reference: String, vararg children: String): Single<T> =
Single.create({
val databaseReference = firebaseDatabase.getReference(reference)
databaseReference.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(dbError: DatabaseError?) {
it.onError(FirebaseDatabaseException(
dbError?.message ?: context stringOf R.string.database_error,
dbError?.details ?: exceptionDetails(reference, children))
)
}
override fun onDataChange(dataSnapshot: DataSnapshot?) {
val value = childDataSnapshot(dataSnapshot, children)?.getValue(typeClass.java)
if (value != null) {
it.onSuccess(value)
} else {
it.onError(RetreivedValueNullException(exceptionDetails(reference, children)))
}
}
})
})
It is returning Single<T>, however, if you're not using RxJava, just remove related Single.create wrap and return T
Implementation of exceptionDetails():
private fun exceptionDetails(reference: String, children: Array<out String>): String =
"Exception occurred on reference: $reference and children: ${children.forEach { String.format("-> %s", it) }}"
And usage example (if this function is in some FirebaseRepository:
firebaseRepository.getValue(Boolean::class, "kotran", "isActive")
.subscribeOn(Schedulers.io)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ /* do something with the value */},
{ /* do something with the error */})
More detailes available on this gist.
There is no getValue() method in Firebase that works directly on a reference, like getKey(). To get a value you need to use a listener. The onDataChange() method immediately returns the current value. Here is the official docs from Firebase. Please check the listen for value events section.
Hope it helps.