How to pass null to an Observable with nullable type in RxJava 2 and Kotlin
Asked Answered
S

7

22

I initialize my variable like this:-

 val user: BehaviorSubject<User?> user = BehaviorSubject.create()

But I can't do this. IDE throws an error:-

user.onNext(null)

And doing this, IDE says u will never be null:-

user.filter( u -> u!=null)
Squarely answered 25/7, 2017 at 5:23 Comment(7)
You can't - even in Java, .onNext(null) will evaluate to .onError(new NullPointerException(...)).Jervis
In RxJava 1 it was possible. But in 2 it is not.Squarely
BTW: Your code is not even compiling. Please post only valid code in here. You can edit the post to fix this.Fetiparous
@Fetiparous The code doesn't have to compile if the question is how to make the code compile.Stolen
RxJava implements the reactive streams specification which does not support null values. You can use github.com/gojuno/koptional to wrap the values. Includes filters for RxJavaMasaryk
@Stolen the question is not how to make it compile. It is how to pass nullFetiparous
One possible workaround would be to use AtomicReference(null) vs AtomicReference(User)Mooch
S
4

Thank you very much for all your answers but I ultimately went with this solution:-

class UserEnvelope(val user:User?) {}

And using this in the observables.

This best suited my requirements.

I am new to Kotlin so I don't know how to use Optionals. But from what I understand, I would have to typecast it to User type everytime I need to observe the values right?

Squarely answered 28/7, 2017 at 10:33 Comment(1)
I like this approach, it is straight forward and clean.Ivanovo
S
28

As Guenhter explained, this is not possible. However, instead of proposing the null-object pattern, I'd recommend an implementation of the Optional type:

data class Optional<T>(val value: T?)
fun <T> T?.asOptional() = Optional(this)

This makes your intent much clearer, and you can use a destructuring declaration in your functions:

Observable.just(Optional("Test"))
  .map { (text: String?) -> text?.substring(1)?.asOptional() }
  .subscribe()

Using the null-object pattern here can cause more bugs than it solves.

Stolen answered 25/7, 2017 at 11:47 Comment(4)
Yes, I guess this is the cleanest way to do it (even if I don't really like Optionals :) ).Fetiparous
A "complete" implementation of Optional (with methods like java.util.Optional) is pretty short: gist.github.com/ephemient/2dec1165b7993e6a6cd7cdfa005fe277Jervis
@Jervis interesting approach!Stolen
Could someone explain this code? I don't understand `data class Optional<T>, and also fun <T> T?.asOptional() = Optional(this). It looks like he is creating a data class called "Optional", but what does the <T> after it do? Does the class "Optional" take on the same type of it's parameter, the "value" of type "T?"?Tisatisane
F
11

If you use rxkotlin/rxjava 2.0 (I assume so) than the answer is: you can't. The reason is explained here.

This is a break of the interface. Have a look at the Observable Interface

public interface Observer<T> {

    /** ... */
    void onSubscribe(@NonNull Disposable d);

    /** ... */
    void onNext(@NonNull T t);

    /** ... */
    void onError(@NonNull Throwable e);

    /** ... */
    void onSubscribe(@NonNull Disposable d);

    /** ... */
    void onNext(@NonNull T t);

    /** ... */
    void onError(@NonNull Throwable e);
...

The @NonNull will be considered by the Kotlin compiler and therefore you CAN'T pass null.

Even if you could, the onNext would immediately throw an error:

@Override
public void onNext(T t) {
    if (t == null) {
        onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
        return;
    }
    ...
}

If you really need such a thing as null you have to fake it. e.g. by creating a static object of User which represents your null-element.

e.g.

data class User(val username, val password) {

    companion object {
        val NULL_USER = User("", "")
    }
}
...
val user = BehaviorSubject.create<User>()
...
user.onNext(User.NULL_USER)
...
user.filter { it !== User.NULL_USER }

But if is somehow possible, try to avoid the null concept and maybe think of another solution where this isn't needed.

Fetiparous answered 25/7, 2017 at 5:36 Comment(0)
S
4

Thank you very much for all your answers but I ultimately went with this solution:-

class UserEnvelope(val user:User?) {}

And using this in the observables.

This best suited my requirements.

I am new to Kotlin so I don't know how to use Optionals. But from what I understand, I would have to typecast it to User type everytime I need to observe the values right?

Squarely answered 28/7, 2017 at 10:33 Comment(1)
I like this approach, it is straight forward and clean.Ivanovo
C
1

To implement the solution mentioned in the nhaarman's answer, you can use the util class Optional (doc) from the Android SDK which was added in API level 24.

If your app's minSdkVersion less than 24 then you still need to implement it by yourself.

Christyna answered 6/2, 2020 at 12:13 Comment(0)
H
0

Since RxJava 2 does not support null values, there are some other acceptable solutions you can use:

  • Work with a custom or third party wrapper library of Optionals like some of the posted answers suggest. When I got rid of Java in favour of Kotlin, Optionals went away in the same package since Kotlin per se supports nullability as part of its type System. Just by this change the code was much more clearer, and I personally don't want to get Optionals back in my code as long as I can avoid them.
  • Emit Any class instances with your subject type. For example you could create an Empty.INSTANCE enum class which would emulate the null value and then filter by the enum class.
  • The last one is the one I use and prefer being a variant of the previous solution and is based on specialisations. Our friends of JetBrains always emphasise that classes are very cheap in Kotlin, so this would be a quick example to distinguish logged users and not logged ones:

    abstract class SessionUser
    sealed class LoggedUser(val username: String, val password: String) : SessionUser()
    sealed class LogoutUser : SessionUser()
    
    private val user = BehaviorSubject.create<SessionUser>()
    private val loggedUser = 
       user.filter { it is LoggedUser }.cast(LoggedUser::class.java)
    
    fun login(username: String, password: String) {
       user.onNext(LoggedUser(username, password))
    }
    
    fun logout() {
       user.onNext(LogoutUser())
    }
    
Hypnotize answered 25/5, 2018 at 19:23 Comment(0)
C
0

I've taken an approach similar to Optional<User> and UserEnvelope. I make a simple User class and a ReifiedUser class that inherits from it. The User class has a companion object that has a NONE instance. The BehaviorSubject is instantiated with the User.NONE instance. It looks something like this:

open class User {
    companion object {
        val NONE = User()
    }
}

class ReifiedUser(
        @field:JsonProperty(J.FirstName) val firstName: String,
        @field:JsonProperty(J.LastName) val lastName: String
) : User()

My BehaviorSubject is instantiated like this:

val activeUser: BehaviorSubject<User> = BehaviorSubject.createDefault(User.NONE)

And wherever I need to use activeUser I either flatMap it to Observable.empty() if it's NONE or just figure out what it is and what to do in the subscriber.

I don't like mixing java Optional with kotlin nullable because mixing map and let gets really confusing and ugly. This way it's very obvious what's going on.

Conglomeration answered 27/10, 2020 at 22:20 Comment(0)
B
0

I think it makes more sense to write a container class such as Result. An example of that would be

data class Result<T>(value: T?, error: Throwable?)

Usage

Observable.create { observer ->
   upstreamService.listen(object: UpstreamListener {
     onSuccess(data: User) {
       observer.onSuccess(Result(data))
     }
     onError(exception: Throwable) {
       observer.onSuccess(Result(null, exception))
     }
   }
}
Besom answered 6/4, 2021 at 14:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.