Reputation: 1106
I need to display an image in an ImageView, so I need to make sure that the image url is not null first. Are these 3 options valid?
Answer data class
data class Answer(
val id: Long?,
val title: String?,
val answerImage: AnswerImage?
) {
data class AnswerImage(
val x0: AnswerImageData?,
val x1: AnswerImageData?,
val x2: AnswerImageData?
) {
data class AnswerImageData(
val id: String?,
val url: String?
)
}
}
Option 1
answer.answerImage?.let { answerImage ->
answerImage.x0?.let { answerImageData ->
answerImageData.url?.let {
//display image
}
}
}
Option 2
if (answer.answerImage?.x0?.url != null)
{
//display image
}
Option 3
answer.answerImage?.x0?.url?.let {
//display image
}
Upvotes: 3
Views: 1642
Reputation: 1389
All the answers above were great but I wanted to mention something. You declared your properties as nullable so I'm guessing you are getting them from somewhere else (from your data layer if you're familiar with clean architecture).
my recommendation is to create a domain model for your class and map the data to your domain model(which has non-null properties). this way you handle nulls in the mapper. it's cleaner and follows the separation of concerns and single responsibility principles.
interface Mapper<F, S> {
fun firstToSecond(first: F): S
fun secondToFirst(second: S): F
}
data class DataModel(
val id: Long?,
val title: String?,
val answerImage: AnswerImage?
)
data class DomainModel(
val id: Long,
val title: String,
val answerImage: AnswerImage
)
class DataToDomainMapper: Mapper<DataModel, DomainModel> {
override fun firstToSecond(first: DataModel): DomainModel {
return DomainModel(
id = first.id ?: -1,
title = first.title ?: "no title",
answerImage = first.answerImage ?: AnswerImage()
)
}
override fun secondToFirst(second: DomainModel): DataModel {
return DataModel(
id = second.id,
title = second.title,
answerImage = second.answerImage
)
}
}
this way you don't have to handle nulls anywhere else in your code. and for data validation, you can check the id not to be negative. I've shortened your models, but you get the idea
Upvotes: 1
Reputation: 19622
Ken covered the answers (they're all fine and do the same thing, the last one is how the language is designed to be used really, nice and neat!) but I wanted to touch on your actual data model.
First, you say you need to check that an AnswerImageData
's url
isn't null. But the only reason it could be null, is because you've explicitly made it nullable, with a String?
type. Is an AnswerImageData
with a null url
ever valid? Or does it always need to have one? I'm guessing it does, and I'm guessing it always needs an id
too - so just make them non-null!
data class AnswerImageData(
val id: String,
val url: String
)
Now all your AnswerImageData
objects are guaranteed to have non-null values - they're all valid in that sense, it's baked into your design. So you don't need to null check them anymore!
And the same goes for your other classes - can you have an AnswerImage
with null values? This might be a trickier one, let's assume there needs to always be at least one AnswerImageData
in an AnswerImage
- in which case you can make the first non-null, and the others optional:
data class AnswerImage(
val x0: AnswerImageData,
val x1: AnswerImageData?,
val x2: AnswerImageData?
)
This isn't necessarily the best way to do this - I'd personally prefer a vararg
parameter, or some kind of collection, so you can have an arbitrary number of AnswerImageData
s and do operations like .first(predicate)
to loop over them all easily. But if you want exactly three slots, three parameters is a way to do it!
Same goes for Answer
- I'm guessing that requires an id
, title
and answerImage
- if so, don't let them be null. Enforce that valid structure through your types, it'll make your life a lot easier! And so will avoiding nullable types unless you actually need them!
I don't know if that applies to what you're doing, but it probably does, so it's worth mentioning. (This kind of thing is called *Domain-Driven Design if you want to look into it - basically enforcing the rules and structure of your business logic through the way you design your code, your types, your objects etc.)
Upvotes: 2
Reputation: 8723
As specified by @Ken Van Hoeylandt all 3 options are valid, another valid option could be to use elvis operator:
fun attemptDisplayingImage(answer: Answer) {
val answerImage = answer.answerImage ?: return
val answerImageData = answerImage.x0 ?: return
val answerImageDataUrl = answerImageData.url ?: return
// display image
}
There's an interesting article about this topic here
Upvotes: 4
Reputation: 5614
Short answer: yes.
Option 1: Would only be a good choice if you actually need to do more things with answerImage
and answerImageData
rather than just cast it safely. In this specific case, we don't have a use for declaring those variables explicitly. To conclude: option 1 in this case is not a very neat solution, but it does work.
Option 2: should work, because all attributes are immutable. The compiler can then deduce on the next line (inside if
scope), that the url
property will still be non-null
.
Option 3: this is in my opinion the best one: it's the easiest one to process as a reader of the code, as you would generally finish it with code like this: .let { safeUrl -> .. }
.
Upvotes: 4