The following Code A is from the project architecture-samples at https://github.com/android/architecture-samples
The function getTasks() will return Result<List<Task>>.
The class data class Error(val exception: Exception) will return Result<Nothing>().
I think the code Error(e) will cause error because it can't return Result<List<Task>>.
Is the Nothing child class of any other class in Kotlin?
Code A
class TasksLocalDataSource internal constructor(
private val tasksDao: TasksDao,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksDataSource {
...
override suspend fun getTasks(): Result<List<Task>> {
return withContext(ioDispatcher) {
try {
Success(tasksDao.getTasks())
} catch (e: Exception) {
Error(e) //I think that it will be cause error
}
}
}
...
}
interface TasksDao {
...
#Query("SELECT * FROM Tasks")
suspend fun getTasks(): List<Task>
...
}
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object Loading : Result<Nothing>()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$exception]"
Loading -> "Loading"
}
}
}
By "child" class, I'm assuming you mean subtype? If so, yes Nothing is indeed a subtype of every other type in Kotlin. Its the opposite analog of Any? which is the supertype of every type in Kotlin.
Nothing is what allows functions like TODO() to work. The implementation of this function is:
public inline fun TODO(): Nothing = throw NotImplementedError()
indicating that TODO() never returns anything i.e. it always throws an exception at runtime. This is what allows TODO() to be placed into any context. For example:
fun foo(): String = TODO()
compiles without any error even though TODO() does not return a String , because the compiler knows TODO() will never return anything (more specifically, Nothing is a subtype of String and therefore "returns" a valid type in that expression).
Now, getting to your question:
I think the code Error(e) will cause error because it can't return Result<List<Task>>.
We noted that Nothing is indeed a subtype of every other type.
We must also note that the definition of the sealed Result class i.e. Result<out R> uses the variance annotation out. This means that the type parameter T of Result is always only returned from Result, and never consumed by Result i.e. Result is covariant in T. Quoting the Kotlin documentation:
The general rule is: when a type parameter T of a class C is declared out, it may occur only in out-position in the members of C, but in return C<Base> can safely be a supertype of C<Derived>.
Combining this rule with the knowledge that Nothing is a subtype of every other type, this allows Error(e) which implements Result<Nothing> to be a valid return value of Result<List<Task>>, and therefore there is no error in the line of code you indicated.
Related
I am writing sealed class to handle results of network calls:
sealed class Loadable<out T> {
object Loading: Loadable<Nothing>()
class Success<T>(val data: T): Loadable<T>()
class Error(val exception: DomainException): Loadable<DomainException>()
}
class DomainException(override val message: String?) : Exception()
When I started to works with Loadable, I faced unexpected error:
fun <T> makeCall(liveData: MutableLiveData<Loadable<T>>, block: suspend () -> T) {
val newContext = coroutineContext + CoroutineExceptionHandler { _, throwable ->
liveData.postValue(Loadable.Error(throwable as DomainException))
}
launch(newContext) {
liveData.postValue(Loadable.Loading)
val result = withContext(Dispatchers.IO) { block.invoke() }
liveData.postValue(Loadable.Success(result))
}
}
This code has no errors with Loadable.Loading and Loadable.Success, but for Loadable.Error I get
Type mismatch. Required: Loadable! Found: Loadable.Error
This seems strange for me, as Loadable.Error is Loadable. Things get even more unclear, if I use Nothing for Error:
class Error(val exception: DomainException): Loadable<Nothing>()
When there is no type mismatch error. I think I miss something with generic types in kotlin. I read docs about generics, but cannot get where I am wrong.
The Code A is from the official sample project.
Result<out R> is a sealed class, I'm very strange why Loading is defined as object class.
I think the Code B is more reasonable, is it right?
Code A
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object Loading : Result<Nothing>()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$exception]"
Loading -> "Loading"
}
}
}
Code B
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
data class Loading : Result<Nothing>()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$exception]"
is Loading -> "Loading"
}
}
}
Added Content:
To Tenfour04 and a_local_nobody: Thanks!
The Android Studio can compile and run after I add is before Loading -> "Loading" in Code C.
1: What are differents between Code A and Code C?
2: And more, In my mind, all types whithin sealed class should be the same, maybe they are all data class, or they are all object.
But Code A mix data class and object, and it's Ok, does it mean that I add even Interface whithin sealed class ?
Code C
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object Loading : Result<Nothing>()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$exception]"
is Loading -> "Loading" // I add "is"
}
}
}
There's no reason not to use an object for a class that doesn't have any properties (holds no state). object means you don't have to create multiple instances of it and you never have to worry about which instance you're looking at, so it's simpler. You don't have to call a constructor on it to get an instance, and there will never be more than one instance of it taking up memory.
Note that your is Loading check in Code B will still work if Loading is an object. objects are more versatile because == and is checks are both valid and effectively mean the same thing.
By the way (as mentioned before by #a_local_nobody), you cannot create a data class with no properties, although you could create a regular class.
that won't work, because:
data class Loading : Result<Nothing>() <-- this isn't valid for a data class
Data classes must have at least one primary constructor parameter, presumably the author used an object there to avoid having to make use of a constructor value, compared to the others:
data class Success<out T>(val data: T) : Result<T>() <-- (val data: T)
data class Error(val exception: Exception) : Result<Nothing>() <-- (val exception: Exception)
which clearly require values
I have been doing android development for a while but I always looking forward to learning new things.
I came across the code below on codelab for viewmodel unit testing. I really like the code base is arranged but do not understand some codes like the one below.
I will like some guidance on creating a class that has a type of map as below.
Basically I will like to know how Result<*> still relate to Result and why the class is just called/implemented as Success(it).
I will appreciate a kind guidance.
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object Loading : Result<Nothing>()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$exception]"
Loading -> "Loading"
}
}
}
/**
* `true` if [Result] is of type [Success] & holds non-null [Success.data].
*/
val Result<*>.succeeded
get() = this is Success && data != null
//implementation
override fun observeTask(taskId: String): LiveData<Result<Task>> {
return tasksDao.observeTaskById(taskId).map {
Success(it)
}
}
Result<*>.succeeded is an extension property of the sealed class Result.
Check the guide on extensions in Kotlin:
https://kotlinlang.org/docs/reference/extensions.html
I have made a call to emit() in my viewModel, but I dont know why my LiveDataScope returns a Resource<Any> when I have defined that the Resource is of type Artist
ViewModel
class EventsViewModel(private val useCase: Events):ViewModel() {
val fetchArtistList = liveData(Dispatchers.IO){
try {
val artistList = useCase.getEvents()
emit(artistList)
}catch (e:Exception){
Crashlytics.logException(e.cause)
emit(Resource.error("Error: ",e.message))
}
}
}
UseCase
class EventsImpl(private val eventsRepo:EventsRepo): Events {
override suspend fun getEvents(): Resource<MutableList<Artist>> = eventsRepo.getEventsDB()
}
Repo
class EventsRepoImpl : EventsRepo {
override suspend fun getEventsDB(): Resource<MutableList<Artist>> {
val artistList = mutableListOf<Artist>()
val resultList = FirebaseFirestore.getInstance()
.collection("events")
.get().await()
for (document in resultList) {
val photoUrl = document.getString("photoUrl")
val artistName = document.getString("artistName")
val place = document.getString("place")
val time = document.getString("time")
val day = document.getLong("day")
artistList.add(Artist(photoUrl!!, artistName!!, time!!, place!!, day!!))
}
return Resource.success(artistList)
}
}
But for some reason, instead of inferring the type with Resource<MutableList<Artist>> in my viewmodel, it provides a Resource<Any> to the LiveData:
I have implemented the same way in another class but livedata is returning fine, I tried clear cache and restart, clean and rebuild but it keeps returning the same
Why is not inferring the type correctly ?
It is inferring correctly. Your code is suggesting to Kotlin that the LiveData can yield two different types of objects. You have this:
emit(artistList)
and this:
emit(Resource.error("Error: ",e.message))
The most specific common type the Kotlin can infer from that is Resource<Any>, since they are both Resource objects, but with different generic types.
Consider instead emitting a sealed class with two subclasses, one for the data type, and another for the error type.
You can change your Resource implementation to this as Doug is pointing out
sealed class Resource<out T> {
class Loading<out T> : Resource<T>()
data class Success<out T>(val data: T) : Resource<T>()
data class Failure<out T>(val throwable: Throwable) : Resource<T>()
}
I am trying to build a set of providers for realm objects.
Here is an example structure I've tried to build:
Interface:
interface IDataProvider<out T : RealmObject> {
fun getRealmObject(): T
}
Base provider class with companion function for typed provider instantiation:
open abstract class BaseProvider<out T : RealmObject> constructor(protected val context: Context?) : IDataProvider<T> {
companion object {
fun <T : RealmObject, E : BaseProvider<T>> create(context: Context?): E {
if (something) {
return SomeChildProviderProvider(context)
} else {
throw TypeNotSupportedException()
}
}
}
}
And here is a child class:
class SomeChildProvider(context: Context?) : BaseProvider<ChildRealmModel>(context){
override fun getRealmObject(): ChildRealmModel {
throw UnsupportedOperationException("not implemented")
}
}
Problem I have is on the line
return SomeChildProviderProvider(context)
Type mismatch.
Required: E.
Found: SomeChildProvider.
I can't figure out why it does not see that E is actually SomeChildProvider.
Thank you.
P.S. I know that I can cast it to E, but in my opinion, it should not be needed in this situation. Maybe I am missing something obvious here or probably lack of Kotlin knowledge.
UPDATE1:
After the first answer, we have realized that code above does not make much sense since we have to define a type of returning provider and to pass it into create method. Initial idea was that create method returns some type which is BaseProvider subtype. Here are the changes I have made in order to support the initial idea:
IDataProvider
interface IDataProvider {
fun execute(realm: Realm)
fun createModel(realm: Realm): RealmObject
}
BaseProvider
open abstract class BaseProvider constructor(protected val context: Context?) : IDataProvider {
override fun execute(realm: Realm) {
realm.executeTransaction { r ->
createModel(r)
}
}
companion object {
fun create(context: Context?): IDataProvider {
if (something) {
return ChildProvider(context)
} else {
throw TypeNotSupportedException()
}
}
}
}
ChildProvider
class ChildProvider(context: Context?) : BaseProvider(context) {
override fun createModel(realm: Realm): ChildRealmModel {
var realmObject = realm.createObject(ChildRealmModel ::class.java)
//object property initialization
return realmObject
}
}
UI call
BaseProvider.create(context).execute(realm)
Although, createModel method returns RealmObject, it's instance will be of ChildRealmModel. What I don't like about it is that we have to inspect instance type and cast into if we need exact model somewhere else.
Your code is not consistent.
In the function declaration you pledge to return E, which is a subtype of BaseProvider<T> and can be chosen by the user on the call site.
But in the implementation you return SomeChildProviderProvider, which is of course a subtype of BaseProvider<T>, but still can be totally unrelated to E which was chosen by the user.
An example:
class AnotherChildProvider : BaseProvider<ChildRealmModel>(context) {...}
val x = BaseProvider.create<ChildRealmModel, AnotherChildProvider>(context)
What is the type of x? According to the function signature, it must be AnotherChildProvider. But inside the function you return SomeChildProviderProvider, which CAN NOT be casted to AnotherChildProviderProvider.