I have a login API, which accepts that a field, type is either "email" or "phone".
I'd like to make them Enums
enum class LoginBodyType (val value: String) {
EMAIL("email"), PHONE ("phone")
}
I have a data class similar to follows
class LoginBody (val type: LoginBodyType) {
var phone: String = ""
var email: String = ""
var password: String = ""
}
so that when I call the login function, it will be something like this
val body = LoginBody(LoginBodyType.EMAIL)
body.email = username
body.password = password
where the type will be fixed to one of the enums, but it would submit to the server as a type String.
One option is to do body = LoginBody(LoginBodyType.EMAIL.value) and class LoginBody (val type: String) but it feels like there's a more elegant solution. I'm using Retrofit 2 if that matters.
You could try to play around with sealed class. So something like:
enum class LoginBodyType {
EMAIL,
PHONE
}
sealed class LoginBody {
data class Email(val email: String, val password: String)
data class Phone(val phone: String)
}
val body = when(type) {
LoginBodyType.EMAIL -> LoginBody.Email(email, pass)
LoginBodyType.PHONE -> LoginBody.Phone(phone)
}
Alternatively check How to obtain all subclasses of a given sealed class?
You could do something like this.
enum class LoginBodyType {
EMAIL,
PHONE;
override fun toString(): String = name.toLowerCase()
}
val body = LoginBody(LoginBodyType.EMAIL)
Related
I have a data class like this -
data class User(val id: String, val name: String)
I want to use apply scope function to modify the values and return the modified object.
User("1", "Name").apply {
this.id = "2" // Gives me error since val cannot be assigned
}
I do not want to make my data class immutable and also don't want to use copy function. How do I fix this?
You can use copy method with data classes:
val user1 = User("1", "Name")
val user2 = user1.copy(id = "2")
change like this data class User(var id: String, var name: String)
in my Android app, after sending some registration credentials I get the following JSON output from the server:
{
"response":"successfully registered new user",
"email":"testing#gmail.com",
"username":"testing",
"id":9,
"token":"98d26160e624a0b762ccec0cb561df3aeb131ff5"
}
I have modeled this using the Moshi library with the following data class:
#JsonClass(generateAdapter = true)
data class Account (
#Json(name = "id")
val account_id : Long,
#Json(name="email")
val account_email: String,
#Json(name="username")
val account_username: String,
#Json(name="token")
val account_authtoken : String,
#Json(name="response")
val account_response : String
)
Everything works fine. Now I wanted to handle error cases. When I get an error (let's say, the email I want to register with already exists) then I should get a JSON output like this:
// what the app gets when there is some error with the credentials
// e.g. email exists, username exists etc.
{
"error_message" : "The email already exists",
"response": "Error"
}
The method that executes the request looks like the following:
override suspend fun register(email: String, userName: String, password: String, passwordToConfirm: String): NetworkResult<Account> {
// make the request
val response = authApi.register(email, userName, password, passwordToConfirm)
// check if response is successful
return if(response.isSuccessful){
try {
// wrap the response into NetworkResult.Success
// response.body() contains the Account information
NetworkResult.Success(response.body()!!)
}
catch (e: Exception){
NetworkResult.Error(IOException("Error occurred during registration!"))
}
} else {
NetworkResult.Error(IOException("Error occurred during registration!"))
}
}
If the response is successful, then it wraps the response.body() into NetworkResult.Success data class.
My NetworkResult class is a sealed class with two sub data classes Success & Error.
It looks like this:
// I get the idea for this from https://phauer.com/2019/sealed-classes-exceptions-kotlin/
sealed class NetworkResult<out R> {
data class Success<out T>(val data: T) : NetworkResult<T>()
data class Error(val exception: Exception) : NetworkResult<Nothing>()
}
But that does not handle the JSON output for errors I mentioned above. When the app gets the error JSON output, Moshi complains that the Account data class does not have a error_message property which is clear to me because I do not have such a field in my Account data class.
What do I need to change so that I can also handle any error cases I wish ? I know, I could model a second data class and call it Error with the fields response and error_message but my sealed class NetworkResult only accepts one class as generic type.
So, what can I do ?
If you don't initialise a value to a field in data class, Moshi will consider it as a required field.
#JsonClass(generateAdapter = true)
data class Account (
#Json(name = "id")
val account_id : Long = 0,
#Json(name="email")
val account_email: String = "",
#Json(name="username")
val account_username: String = "",
#Json(name="token")
val account_authtoken : String = "",
#Json(name="response")
val account_response : String = "",
#Json(name="error_message")
val error_message : String = ""
)
Like this you can create the same data class for Success and Error both.
I have a function filter here
fun filter(category: String) {
...
}
and a Class with many constant string
object Constants {
val CAT_SPORT = "CAT_SPORT"
val CAT_CAR = "CAT_CAR"
...
}
How to ensure the parameter category is a constant string from Constants (or throw warning)?
I am looking for something like #StringRes.
I know Enum may do the trick but prefer not to code refactor at this moment.
Using androidx.annotation you can do something like this:
object Constants {
#Retention(AnnotationRetention.SOURCE)
#StringDef(CAT_SPORT, CAT_CAR)
annotation class Category
const val CAT_SPORT = "CAT_SPORT"
const val CAT_CAR = "CAT_CAR"
}
fun filter(#Constants.Category category: String) {
...
}
I have a request that returns JSON:
{
"success": 0,
"errors": {
"phone": [
"Incorrect phone number"
]
}
}
I plugged Fuel instead of Retrofit for Kotlin. So, my classes are:
data class RegistrationResponse(
val success: Int,
val errors: RegistrationErrorsResponse?) {
class Deserializer : ResponseDeserializable<RegistrationResponse> {
override fun deserialize(content: String): RegistrationResponse? =
Gson().fromJson(content, RegistrationResponse::class.java)
}
}
data class RegistrationErrorsResponse(val phone: List<String>?) {
class Deserializer : ResponseDeserializable<RegistrationErrorsResponse> {
override fun deserialize(content: String): RegistrationErrorsResponse? =
Gson().fromJson(content, RegistrationErrorsResponse::class.java)
}
}
A request looks like:
class Api {
init {
FuelManager.instance.basePath = SERVER_URL
}
fun registration(name: String, phone: String): Request =
"/registration/"
.httpPost(listOf("name" to name, "phone" to phone))
}
private fun register(name: String, phone: String) {
Api().registration(name, phone)
.responseObject(RegistrationResponse.Deserializer()) { _, response, result ->
val registrationResponse = result.component1()
if (registrationResponse?.success == 1) {
showScreen()
} else {
showErrorDialog(registrationResponse?.errors?.phone?.firstOrNull())
}
}
}
A problem is that when error occurs, phone variable in data class (registrationResponse?.errors?.phone) is filled with null, but not "Incorrect phone number".
After searching through Fuel issues I understood that in most cases we don't need to write own deserializators as they are already written by Gson.
In https://github.com/kittinunf/Fuel/issues/265 there is an example. So, just put your data class inside <>:
URL.httpPost(listOf("name" to name, "phone" to phone)).responseObject<RegistrationResponse> ...
and get data through
result.component1()?.errors?.phone?.firstOrNull()
Old version of answer
Probably an obstacle is a list deserialization, see
1. https://github.com/kittinunf/Fuel/issues/233 and
2. https://github.com/kittinunf/Fuel/pull/236.
I think, by default Fuel doesn't use Gson deserialization.
I still don't know how to deserialize a list, But got values with this expression:
((result.component1().obj()["errors"] as JSONObject).get("phone") as JSONArray)[0]
Right code:
class MainActHandler(val weakActivity: WeakReference<Activity>): Handler() {
override fun handleMessage(msg: Message?) {
val trueAct = weakActivity.get() ?: return
if (msg?.what == ConversationMgr.MSG_WHAT_NEW_SENTENCE){
val sentence = msg.obj as String?
trueAct.conversation.text = sentence
}
super.handleMessage(msg)
}
}
cannot be resolved code:
class MainActHandler(weakActivity: WeakReference<Activity>): Handler() {
override fun handleMessage(msg: Message?) {
val trueAct = weakActivity.get() ?: return
if (msg?.what == ConversationMgr.MSG_WHAT_NEW_SENTENCE){
val sentence = msg.obj as String?
trueAct.conversation.text = sentence
}
super.handleMessage(msg)
}
}
cannot be resolved code screenshot
The only difference is the "val" has been deleted and cannot be resolve.
Which might be important is that it's a inner class.
BUT
This one class without "val/var" in constructor parameter is working:
class BookInfo(convrMgr: ConversationMgr, id: String, queue: RequestQueue, queueTag:String) {
val TAG = "BookInfo"
var title: String? = ""
init {
val url = "https://api.douban.com/v2/book/$id"
// Request a string response from the provided URL.
val stringRequest = StringRequest(Request.Method.GET, url,
Response.Listener<String> { response ->
Log.d(TAG + " Response", response.substring(0))
// Parse JSON from String value
val parser = Parser()
val jsonObj: JsonObject =
parser.parse(StringBuilder(response.substring(0))) as JsonObject
// Initial book title of book properties.
title = jsonObj.string("title")
Log.d(TAG + " Book title", title)
convrMgr.addNewMsg(title)
},
Response.ErrorListener { error -> Log.e(TAG + " Error", error.toString()) })
// Set the tag on the request.
stringRequest.tag = queueTag
// Add the request to the RequestQueue.
queue.add(stringRequest)
}
}
And if I add var/val before "queue: RequestQueue", I'll get suggestion:
"Constructor parameter is never used as a property less. This inspection reports primary constructor parameters that can have 'val' or 'var' removed. Unnecessary usage of 'val' and 'var' in primary constructor consumes unnecessary memory."
I am just confused about it.
When you write val/var within the constructor, it declares a property inside the class. When you do not write it, it is simply a parameter passed to the primary constructor, where you can access the parameters within the init block or use it to initialize other properties. For example,
class User(val id: Long, email: String) {
val hasEmail = email.isNotBlank() //email can be accessed here
init {
//email can be accessed here
}
fun getEmail(){
//email can't be accessed here
}
}
Constructor parameter is never used as a property
This suggestion is saying that you do not use this property in place apart from the initialization. So, it suggests you to remove this property from the class.
Constructor parameters must use var or val when they are used as a property elsewhere in the class. They do not need to be properties if they are only used for class initialization.
In the example below, the parameter must be a property (var or val) because it is used in a method:
class A(val number: Int) {
fun foo() = number
}
In this other example, the parameter is only used to initialize the class, so it does not need to be a property:
class B(number: Int): A(number) {
init {
System.out.println("number: $number")
}
}
This might be a late answer but the magic lies under the hood:
Based on #BakaWaii's answer:
Putting var/val will make the variable a property of the class and not putting it will make it a parameter of only the constructor function.
So what does it mean, to understand lets look into some code:
class Test(a: Int){}
Now Lets see the decompiled java code:
public final class Test {
public Test(int a) {
}
}
So now if I try to access a using the object of Test() like the below code:
Test t = new Test(10);
t.a //Error
It will give me error. Unresolved reference: a. Why because a is a parameter of the constructor only.
Now if we put var/val in the paramater like below:
class Test(var a: Int){}
The decompliked Java code will become:
public final class Test {
private int a;
public final int getA() {
return this.a;
}
public final void setA(int var1) {
this.a = var1;
}
public Test(int a) {
this.a = a;
}
}
Thus it will not only give you a class property but also give you getter/setters for setting the values.
Now the next question arises if the field a is private how can it be accessed. Simple answer in Java you cannot, i.e. if you are calling the KT class from a Java you will not be able to assign value of a like Test(1).a = 10 but will have to use Test(1).setA(5).
But as kotlin internally handles getters/setters Test(1).a = 5 will be ok.
For #Parcelize to work you need to open up the super's properties and override them in the child:
abstract class Goal(open var number: Int, open var name: String) : Parcelable
#Parcelize
class OperationalGoal(override var number: Int, override var name: String, var description: String) : Goal(number, name)```
In very simple terms, use var or val in class constructor parameters when you want to use that variable, say, inside a method within that class. Thus you're effectively turning them into properties and not just mere constructor or method parameters.
class User(var name: String, age: Int) {
var str = "John"
var num = 18
fun setName(){
name = str // due to using var on our class constructor parameter, we can access the constructor variable *name* inside this setter method. *name* is a property parameter thanks to the var keyword.
}
fun setAge(){
age = num // this will result in a compiler error, because *age* is just a parameter, notice that var wasn't used in the *age* parameter within the class constructor, which means we can't access it like we did with *name*
}
}
Run this Kotlin Playground code to get a clearer idea of what's going on.