Using generic as parameter in constructor Kotlin - android

I m using two constructor with different input types one as string and other as generic. The problem is while using Kotlin its only using the string constructor and ignores the generic
class DataResponse<T> {
var isSuccess: Boolean = false
private set
var errorMessage: String? = null
var data: T? = null
constructor(success: Boolean, data: T) {
this.isSuccess = success
this.data = data
}
constructor(success: Boolean, errorMessage: String) {
this.isSuccess = success
this.errorMessage = errorMessage
}
}
usage
if (apiResponse.code() == 200) {
Observable.just(DataResponse<List<ResultDTO>>(true,
apiResponse.body()?.resultList)) ---> **(error on this line forcing to convert it to string)**
} else {
Observable.just(DataResponse(false, "Something went wrong"))
}

You can give named parameters in kotlin. That is if more than two constructors or functions with same name exists, we can explicitly specify the parameter as named one. Here I suggest to explicity mention the parameter data.
if (apiResponse.code() == 200) {
Observable.just(DataResponse<List<ResultDTO>>(true,data=
apiResponse.body()?.resultList))
} else {
Observable.just(DataResponse(false, "Something went wrong"))
}

Currently your DataResponse class represents two different things. One is an error message and the other one is actual data in case of success. isSuccess is redundant too because it is always true when data is non-null and always false if errorMessage is non-null.
I would change the design in the following way:
sealed class DataResponse
class SuccessResponse<T>(val data: T?)
class ErrorResponse(val errorMessage: String)
Now you have two separate classes, both having the same supertype DataResponse. This way you will always know what you are dealing with.
Usage:
when(dataResponse) {
is SuccessResponse -> TODO("deal with data")
is ErrorResponse -> TODO("deal with error")
}

Related

Getting a null pointer exception when mocking and spying in a test class

Android Studio 3.5.3
Kotlin 1.3
I am trying to test some simple code but I keep getting the following exception:
IllegalStateException: gsonWrapper.fromJson<Map…ring, String>>() {}.type) must not be null
I am using the spy and mocking the return so it will return a null. As I want to test the error path.
Not sure if I am doing something wrong with my stubbing or not. But can't seem to resolve this exception.
Using a wrapper class to wrap the gson implementation and spying on this in the test
public class GsonWrapper implements IGsonWrapper {
private Gson gson;
public GsonWrapper(Gson gson) {
this.gson = gson;
}
#Override public <T> T fromJson(String json, Type typeOf) {
return gson.fromJson(json, typeOf);
}
}
Implementation of my class that is under test
class MoviePresenterImp(
private val gsonWrapper: IGsonWrapper) : MoviePresenter {
private companion object {
const val movieKey = "movieKey"
}
override fun saveMovieState(movieJson: String) {
val movieMap = serializeStringToMap(movieJson)
when (movieMap.getOrElse(movieKey, {""})) {
/* do something here */
}
}
// Exception return from this method
private fun serializeStringToMap(ccpaStatus: String): Map<String, String> =
gsonWrapper.fromJson<Map<String, String>>(ccpaStatus, object : TypeToken<Map<String, String>>() {}.type) // Exception
}
The actual test class, just keeping everything simple
class MoviePresenterImpTest {
private lateinit var moviePresenterImp: MoviePresenterImp
private val gsonWrapper: GsonWrapper = GsonWrapper(Gson())
private val spyGsonWrapper = spy(gsonWrapper)
#Before
fun setUp() {
moviePresenterImp = MoviePresenterImp(spyGsonWrapper)
}
#Test
fun `should not save any movie when there is an error`() {
// Arrange
val mapType: Type = object : TypeToken<Map<String, String>>() {}.type
whenever(spyGsonWrapper.fromJson<Map<String, String>>("{\"movie\":\"movieId\"}", mapType)).thenReturn(null)
// Act
moviePresenterImp.saveMovieState("{\"movie\":\"movieId\"}")
// Assert here
}
}
Many thanks for any suggestions,
I found problems here:
You must use nullable Map? instead of non-null Map in MoviePresenterImp (Kotlin code), because in Unit Test class, you spy gsonWrapper, and force method 'spyGsonWrapper.fromJson' return null.
It OK now.
fun saveMovieState(movieJson: String) {
val movieMap = serializeStringToMap(movieJson)
when (movieMap?.getOrElse(movieKey, { "" })) {
/* do something here */
}
}
// Exception return from this method
private fun serializeStringToMap(ccpaStatus: String): Map<String, String>? {
val type: Type =
object : TypeToken<Map<String, String>>() {}.type
return gsonWrapper.fromJson(ccpaStatus, type) // Exception
}
It depends on what you want to achieve. Do you want to allow MoviePresenterImp.serializeStringToMap to return null? At the moment it is not possible and that's what you are testing in your unit test:
what will happen when gsonWrapper.fromJson returns null?
serializeStringToMap will throw an exception because its return type is declared to be non-nullable (Kotlin adds a null-check under the hood).
In fact,spyGsonWrapper.fromJson only returns null if gson.fromJson returns null. According to the Gson's java docs, it may happen only if the json argument is null (if json is invalid the method throws JsonSyntaxException). So you should either:
check if the json parameter is null in the spyGsonWrapper.fromJson and throw IllegalArgumentException if it is. This will ensure that the method never returns null (btw. you could add a #NotNull annotation, see nullability-annotations). You can keep serializeStringToMap as it is but you need to change the test, because it makes no sense anymore.
if you prefer returning null instead of throwing an exception, you need to change the MoviePresenterImp.serializeStringToMap as suggested by #duongdt3
Here is an example test:
class MoviePresenterImpTest {
private lateinit var moviePresenter: MoviePresenterImp
private lateinit var spyGsonWrapper: GsonWrapper
#Rule #JvmField
var thrown = ExpectedException.none();
#Before
fun setUp() {
spyGsonWrapper = Mockito.mock(GsonWrapper::class.java)
moviePresenter = MoviePresenterImp(spyGsonWrapper)
}
#Test
fun `should not save any movie when GsonWrapper throws an error`() {
// Given
Mockito.`when`(spyGsonWrapper.fromJson<Map<String, String>>(anyString(), any(Type::class.java)))
.thenThrow(JsonSyntaxException("test"))
// Expect
thrown.expect(JsonSyntaxException::class.java)
// When
moviePresenter.saveMovieState("{\"movie\":\"movieId\"}")
}
// Or without mocking at all
#Test
fun `should not save any movie when Gson throws error`() {
// Given
moviePresenter = MoviePresenterImp(GsonWrapper(Gson()))
// Expect
thrown.expect(JsonSyntaxException::class.java)
// When
moviePresenter.saveMovieState("Some invalid json")
}
// If you want to perform additional checks after an exception was thrown
// then you need a try-catch block
#Test
fun `should not save any movie when Gson throws error and `() {
// Given
moviePresenter = MoviePresenterImp(GsonWrapper(Gson()))
// When
try {
moviePresenter.saveMovieState("Some invalid json")
Assert.fail("Expected JsonSyntaxException")
} catch(ex : JsonSyntaxException) {}
// Additional checks
// ...
}
}
With your setup you're looking for emptyMap() instead of null
whenever(spyGsonWrapper.fromJson<Map<String, String>>("{\"movie\":\"movieId\"}", mapType))
.thenReturn(emptyMap())
This will comply with the signature, as it's non-null
fun serializeStringToMap(ccpaStatus: String): Map<String, String>
Also it'll enter the else-block within the movieMap.getOrElse() call.

Kotlin - Generic type safe, wrong warning? java.lang.ClassCastException

I'm fetching response from some API, after getting the response I converting it to List of my required Object e.g:
fun <T> getAsList(input: String): ArrayList<T> {
val objType = object : TypeToken<ArrayList<T>>() {}.type
val result = Gson().fromJson(input, objType) as ArrayList<T>
println(result[0]) // <-- no warning here !! It's work
println("result: " + result.toString()) // Also it's work here
return result
}
Then I pass this list to somewhere e.g:
updateFromDownload(getAsList<T>(resultValue))
And by override this method I can get the result, e.g:
override fun updateFromDownload(result: List<Response>?) {
val listTest = ArrayList<Response>()
listTest.add(result!![0]) // <-- This work
println(listTest)
println("resss:" + result[0]) // <-- This not work !!!
for (response in result){
// The loop crash too, not work
}
As demonstrated above, adding to listTest work fine, but If I tried to get the element individually like I did inside getAsList() It's crash due to:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to ......Response
Can I access the result directly without fill it to new list?
Edit- Whole cycle for code:
class ConnectToURL<T>(callback: DownloadCallback<T>) : AsyncTask<String, Int, ConnectToURL.Result>() {
private var mCallback: DownloadCallback<T>? = null
init {
setCallback(callback)
}
override fun onPostExecute(result: Result?) {
mCallback?.apply {
result?.mException?.also { exception ->
//val gson = Gson().fromJson(result.mResultValue!!, type)
//updateFromDownload(gson)
return
}
result?.mResultValue?.also { resultValue ->
updateFromDownload(getAsList<T>(resultValue))
return
}
finishDownloading()
}
}
}
And I Invoke ConnectToURL from:
class BuilderClass<T> private constructor(
callback: DownloadCallback<T>,
private val url: String
) {
init {
ConnectToURL(callback).execute(url)
}
/// some code . . .. .
fun build() = BuilderClass(callback, url)
}
}
Then I override the updateFromDownload function as it's part from DownloadCallback
The generic type T is erased at compile time, so the type information is not present at runtime.
object : TypeToken<ArrayList<T>>() {}.type
Thats the reason Gson does not convert to the Response class.
You could use inline plus reified to avoid type erasure.
inline fun <reified T> getAsList(input: String): ArrayList<T>

Kotlin - How to properly map result using Either?

I'm trying to read a list of objects from the database and mapping it to another type of list.
// Returns either a Failure or the expected result
suspend fun getCountries(): Either<Failure, List<CountryItem>> {
// Get the result from the database
val result = countryLocalDataSource.getCountries()
// Left means Failure
if (result.isLeft) {
// Retrieve the error from the database
lateinit var error: Failure
result.either({
error = it
}, {})
// Return the result
return Either.Left(error)
}
// The database returns a List of Country objects, we need to map it to another object (CountryItem)
val countryItems: MutableList<CountryItem> = mutableListOf()
// Iterate the Country List and construct a new List of CountryItems
result.map { countries -> {
countries.forEach {
// Assign some values from an Enum (localized string resources)
val countryEnumValue = Countries.fromId(it.id)
countryEnumValue?.let { countryIt ->
val countryStringNameRes = countryIt.nameStringRes;
// Create the new CountryItem object (#StringRes value: Int, isSelected: Bool)
countryItems.add(CountryItem(countryStringNameRes, false))
}
}
} }
// Because this is a success, return as Right with the newly created List of CountryItems
return Either.Right(countryItems)
}
For the sake of readability I didn't included the whole Repository or the DAO classes and I have left comments in the code snippet above.
In a nutshell: I'm using Kotlin's Coroutines for accessing the database in a separated thread and I'm handling the response on the UI Thread. Using the Either class in order to return two different results (failure or success).
The above code works, however It's too ugly. Is this the right approach to deliver the result?
What I'm trying to do is to refactor the code above.
The whole problem is caused by the two different object types. The Database Data Source API is returning an Either<Failure, List<Country>>, meanwhile the function is expected to return an Either<Failure, List<CountryItem>>.
I can't deliver a List<CountryItem> directly from the Database Data Source API, because Android Studio doesn't let me compile the project (entities implementing interfaces, compile error, etc.). What I'm trying to achieve is to map the Either result in a nicer way.
Try using Kotlin's Result
So in your case you can write something like:
return result.mapCatching { list: List<Country> -> /*return here List<CountryItem>>*/ }
And for checking result call:
result.fold(
onSuccess = {...},
onFailure = {...}
)
In order to invoke a constructor you should call Result.success(T) or Result.failure(Throwable)
Unfortunately, you'll also need to suppress use-as-return-type warnings How to
You can simplify by checking the type of Either and accessing the value directly. In your case:
access Left via result.a -> Failure
access Right via result.b -> List<Country>
ex:
when (result) {
is Either.Left -> {
val failure: Failure = result.b
...
}
is Either.Right -> {
val countries: List<Country> = result.b
...
}
}
An alternative is to use the either() function (normally this is called fold()):
result.either(
{ /** called when Left; it == Failure */ },
{ /** called when Right; it == List<Country> */ }
)
Assume your Country class is defined as follow:
data class Country(val name: String) {}
and your CountryItem class is defined as follow:
data class CountryItem(private val name: String, private val population: Int) {}
and your CountryLocalDataSource class with a method getCountries() like this:
class DataSource {
suspend fun getCountries(): Either<Exception, List<Country>> {
return Either.Right(listOf(Country("USA"), Country("France")))
//return Either.Left(Exception("Error!!!"))
}
}
then the answer to your question would be:
suspend fun getCountryItems(): Either<Exception, List<CountryItem>> {
when (val countriesOrFail = DataSource().getCountries()) {
is Either.Left -> {
return Either.Left(countriesOrFail.a)
}
is Either.Right -> {
val countryItems = countriesOrFail.b.map {
CountryItem(it.name, 1000)
}
return Either.Right(countryItems)
}
}
}
To call your getCountryItems(), here is an example:
suspend fun main() {
when (val countriesOrFail = getCountryItems()) {
is Either.Left -> {
println(countriesOrFail.a.message)
}
is Either.Right -> {
println(countriesOrFail.b)
}
}
}
Here's the sample code in the playground: https://pl.kotl.in/iiSrkv3QJ
A note about your map function:
I'm guessing you don't actually need the result to be a MutableList<CountryItem> but you had to define so because you want to add an element as you iterate through the input list List<Country>.
Perhaps the following is the case: If you have a List<Country> with 2 elements like in the example, and you want to map so that the result becomes a List<CountryItem> with also 2 corresponding elements, then you don't need to call forEach inside a fun that gets passed to the higher-order function map. But this may be an entirely new question.

Conflicting overloads: public constructor ApiSuccessResponse<T>(body: T)

I'm copying the Google's code from their repository of samples implementing Android Architecture Components, slowly adapting to the needs of the app I have in mind, using the code as a base. I have reached already a part where for me is displaying an error and I can't understand why. This is the code block:
data class ApiSuccessResponse<T>(val responseBody: T) : ApiResponse<T>() {
constructor(body: T) : this (responseBody = body)
}
The error message underlying is
Conflicting overloads: public constructor ApiSuccessResponse(body: T) defined in com.example.rxe.api.ApiSuccessResponse, public constructor ApiSuccessResponse(responseBody: T) defined in com.example.rxe.api.ApiSuccessResponse
Here's where I call ApiSuccessResponse, just like in the sample code:
sealed class ApiResponse<T> {
companion object {
fun <T> create(response: Response<T>): ApiResponse<T> {
return if (response.isSuccessful) {
val responseBody = response.body()
if (responseBody == null || response.code() == 204) {
ApiEmptyResponse()
} else {
ApiSuccessResponse(body = responseBody)
}
} else {
val error = response.errorBody()?.string()
val message = if (error.isNullOrEmpty()) {
response.message()
} else {
error
}
ApiErrorResponse(errorMessage = message ?: "Unknown error")
}
}
}
}
Something might have changed since the sample has been written. But if I rename the variable to body instead of responseBody, the same error will underline the call for the class ApiSuccessResponse.
You don't seem to understand how constructors work in Kotlin. This is the code you tried to copy:
data class ApiSuccessResponse<T>(
val body: T,
val links: Map<String, String>
) : ApiResponse<T>() {
constructor(body: T, linkHeader: String?) : this(
body = body,
links = linkHeader?.extractLinks() ?: emptyMap()
)
//.... rest of body
}
It has two constructors:
Primary constructor: ApiSuccessResponse(body: T, links: Map<String, String>)
Secondary constructor: ApiSuccessResponse(body: T, linkHeader: String?) (which extracts map of links from String and passes it as links into primary).
What you have is:
Primary constructor: ApiSuccessResponse(resposebody: T)
Secondary constructor: ApiSuccessResponse(body: T) (which tries to call primary constructor 1:1, but it just clashes due to identical signature)
If you don't need secondary constructor You should just delete it entirely.

Best way to pass multiple lambda functions in Kotlin to handle success/failure

New at Kotlin here and trying to learn the best way to use the higher order functions and passing lambdas. I've created this method to call an API and return an object created from a string OR return a failure if something went wrong.
fun getDeviceStatus(onSuccess: (Device) -> Unit, onFailure: ((String) -> Unit)? = null) {
FuelClient.get(DEVICE_URL,
success = { responseString ->
val adapter = MoshiUtil.moshi.adapter(Device::class.java)
val deivce= adapter.fromJson(responseString)!!
onSuccess(device)
},
failure = { onFailure?.invoke(it.message!!)})
}
I can use this function fine like so:
DeviceService.getDeviceStatus(
{ w ->
print("device")
},
{ e -> print(e) })
But it bothers me a bit that I can't see the name of the functions to see what each function does. I"m wondering if there is a cleaner/better way to do this, like
DeviceService.getDeviceStatus(){
onSuccess{print("device")}
onFailure{print("error")}
}
or maybe
DeviceService.getDeviceStatus()
.onSuccess{print("device")}
.onFailure{print("error")}
But those gives errors. Any thoughts on how to best handle the onSuccess/onFailure use case that is very common? Thx
You can attach a name to each variable in kotlin. Change your code like this
DeviceService.getDeviceStatus(
onSuccess = { w ->
print("device")
},
onFailure = { e -> print(e) })
For this specific case, when the second lambda is optional, infix functions work very well:
sealed class DeviceStatusResult {
abstract infix fun onFailure(handler: (String) -> Unit)
}
class DeviceStatusSuccess(val device: Device) : DeviceStatusResult() {
override fun onFailure(handler: (String) -> Unit) = Unit
}
class DeviceStatusFailure(val errorMessage: String) : DeviceStatusResult() {
override fun onFailure(handler: (String) -> Unit) = handler(errorMessage)
}
fun getDeviceStatus(onSuccess: (Device) -> Unit): DeviceStatusResult {
// get device status
// if (success)
val device = Device()
onSuccess(device)
return DeviceStatusSuccess(device)
// else
// return DeviceStatusFailure(message)
}
Then it can used like
getDeviceStatus { device ->
println(device)
} onFailure { errorMessage ->
System.err.println(errorMessage)
}
Maybe onFailure should be called orFail or something like that.
It is good when the second argument is optional, but not so much otherwise because it doesn't force the user to actually supply a failure handler. And I don't think it's a good idea because it will be too easy to accidentally omit a failure handler. It's much better to force the user to provide one, even if it happens to be an empty one. Therefore, it is better to use named arguments for this case, even though nothing forces to actually name them.
For example we have a class which needs to have more than one function such as two functions as parameter:
class TestClass internal constructor(
private val onClickShowName: (String) -> Unit,
private val onClickShowSurname: (String) -> Unit
) { //Your work. }
Then you need to create val as TestClass:
class MainActivity {
val mTestClass = TestClass(
onClickShowName = {dataText: String -> Log.i("TEST", dataText)},
onClickShowSurname = {dataText: String -> Log.i("TEST", dataText)}
)
}

Categories

Resources