What’s the best way to ensure at least one of two ...
# getting-started
m
What’s the best way to ensure at least one of two class constructor params is non-null at compile time? For example in
Copy code
data class Foo(val a: String? = null, val b String? = null)
I could write
Copy code
data class Foo(val a: String? = null, val b String? = null)
{
  init {
         requireNotNull(a ?: b)
       }
}
But this only causes a crash at runtime. How do I achieve the same result with compile time safety?
j
You could define secondary constructors (or factory methods) with the accepted sets or arguments (non-null
a
or non-null
b
), and make the primary constructor private
2
But I wonder what your use case is. If
a
and
b
are exclusive, maybe a
sealed class
would better express it (easier to use after construction).
Copy code
sealed class Foo {
    data class A(val a: String): Foo()
    data class B(val b: String): Foo()
}
If both
a
and
b
can be specified, it depends on semantics I guess (you could add a third child class with both args, or just use factory methods instead of a sealed class hierarchy). What are the accepted configurations?
1
m
I’d prefer if the call site didn’t have to choose between constructors depending on nullability, so secondary constructors is out
How would factory methods look? Would it be compile time safe?
j
How can the call site not choose depending on nullability if you want compile-time safety? This seems to be incompatible requirements to me. Compile time safety means the call site cannot give you 2 nullable things, so it has to choose.
m
a
and
b
are not exclusive, however at least one must not be null. So these are allowed: • a, b • a, null • null, b
👌 1
r
At the point you call one of them must be non-nullable or compile time safety is impossible. So you must be able to choose a constructor / factory method at that point.
2
m
Essentially I want to be able to do this:
Copy code
if (a != null || b != null) {
    Foo(a,b)
}
instead of needing to do something like
Copy code
when {
    a != null && b != null -> Foo(a,b)
    a != null -> Foo(a)
    b != null -> Foo(b)
}
so I can later get
a
or
b
without a null check, e.g.
Copy code
data class Foo(val a: String? = null, val b String? = null) {
    fun getValue(): String = a ?: b
}
You may both be correct that this is a problem that can’t be solved without writing extra code at the call site
j
Could you please explain your use case for this? I'm always interested in knowing why you have this problem in the first place. What is Foo, a and b in your case?
m
They are urls to a dark or light version of an image, the idea is to fall back to one if the other isn’t available. Neither will be shown if they are both unavailable - but that’s handled at the call site
r
In your guard clause:
Copy code
if (a != null || b != null) {
    Foo(a,b)
}
the compiler can’t know which of a & b ends up being not null, so within it both will still be of type
String?
. So you will have to call a function (whether factory or constructor) that accepts both as
String?
.
m
I think you might reconsider the secondary constructor/factory method. You can have a private primary constructor, and then something like:
Copy code
companion object {
  fun foo(a: A) = Foo(a, null)
  fun foo(b: B) = Foo(null, b)
  fun foo(a: A, b: B) = Foo(a, b)
}
👍 1
a
but isn't A and B of the same type here? these would clash as they have the same signature
m
@andylamax correct, I suppose you could do
Copy code
companion object {
  fun fooA(a: String) = Foo(a, null)
  fun fooB(b: String) = Foo(null, b)
  fun foo(a: String, b: String) = Foo(a, b)
}
@Rob Elliot yes exactly, which is the problem I’m trying to solve
m
My mistake, that was a brain fart. You could use value classes instead of primitives, that would avoid the problem of ambiguity with multiple primitive parameters.
a
This calls for a sealed class
t
I think there is no way to get compile time safety with this approach. I suggest changing the general approach so your code accepts that both values can be null and handles that correctly. Or you could abandon compile time safety at this point and just do runtime checks, which of course is always associated with some (maybe very small) risk.
m
Thanks @Tobias Berger, I’ve gone with allowing the output of
fun getValue()
to be nullable, and then handling the null case where it’s called.
n
considering your use case, since they are the same type and you expect to fallback from one to the other, would it be possible to make the properties
primary
and
secondary
, with
primary
being non-null and
secondary
being nullable? or another way to think of it -- make "light" non-null, and "dark" nullable. if you only have one image, does the light/dark differentiation mean anything?
m
@Nolan good way of thinking about it, however the light url can still be null - so the compiler will complain if you try to pass it in as
primary
.
n
yes, i guess i was assuming you could work without the light/dark designations. assuming you have 3 cases: 1. light and dark - show light for light, show dark for dark 2. light, no dark - show light for light and dark 3. dark, no light - show dark for light and dark instead do: 1. primary and secondary - show primary for light, secondary for dark 2. primary, no secondary - show primary for light and dark this depends heavily on how those objects are being created. for example, if a "theme" only has a "dark" image as primary, but then later a "light" image is added, would you know to make the "light" one primary? anyway -- i'm sure you've worked it out by now. 🙂