I have migrated my application from Java to Kotlin. In Java, the copying was working just fine. However, when migrated to Kotline it was not working. After that, I came to know about copy method in Kotlin
I have tied this, but it seems I am doing something wrong.
Here is my function :
fun updateSwitchState(deviceName: String, switchNumber: Int): AuraSwitch? {
val singleDevice = Switch()
for (c in FourNodeDevice) {
if (deviceName == c.name) {
val copyDevice : SwitchClone = SwitchClone(c.state, c.name)
val state = copyDevice.copy(state = copyDevice.state)
state.updateState(switchNumber)
singleDevice.state = state.state
return singleDevice
}
}
return null
}
Whenever I change data in object state in updateState Method the value in object c also gets changed. Any help will be useful
You never create a copy of a state object.
This call creates another SwitchClone with values identical to copyDevice itself.
val state = copyDevice.copy(state = copyDevice.state)
copy() only creates a shallow copy, which means all of your objects, in that case c, copyDevice and state point to the same c.state.
You need to explicitly create a deep copy (depending on what properties are mutable) of state object and assign it to copyDevice.state field.
For Kotlin when using the Kotlin Data Class data class you get a function called copy() for you. But If your Class is not a Data Class and your project has Gson and you want to copy the whole object ( probably edit after getting it ), Then if all those conditions are true then this is a solution. This is also a DeepCopy. ( For a data Class you can use the function copy()).
Then if you are using Gson in your project. Add the function copy():
class YourClass () {
// Your class other stuffs here
fun copy(): YourClass { //Get another instance of YourClass with the values like this!
val json = Gson().toJson(this)
return Gson().fromJson(json, YourClass::class.java)
}
}
If you want to install Gson then get the latest version here.
The copy() did not solve my purpose. However clone() did. I added the following line in my code and it worked as I desired.
val state = c.states.clone()
Related
In the code below, i'd like to generalize it so I instead of viewBinding.editText.text and viewModel.property.price can use the same method for e.g viewBinding.secondEditText.text and viewModel.property.income.
I'm thinking exchanging viewBinding.editText.text for a variable defined in the primary constructor, but then I'd need the variable to contain a reference to viewBinding.editText.text/viewBinding.secondEditText.text etc. instead of containing a value.
Is this possible? I've looked at lengths for this but can't find anything useful.
fun updateProperty() {
//... other irrelevant code
if (viewBinding.editText.text.toString() != "") {
viewModel.property.price = viewBinding.editText.text.toString().toDouble()
}
//... other irrelevant code
}
You can pass parameters into a function, yeah!
This is the easy one:
fun updateProperty(editText: EditText) {
val contents = editText.text.toString()
}
simple enough, you just pass in whatever instance of an EditText and the function does something with it.
If you're just using objects with setters and getters, you can just define the type you're going to be using and pass them in. Depending on what viewmodel.property is, you might be able to pass that in as well, and access price and income on it. Maybe use an interface or a sealed class if there are other types you want to use - they need some commonality if you're going to be using a generalised function that works with them all.
Properties are a bit tricker - assuming viewmodel.property contains a var price: Double, and you didn't want to pass in property itself, just a Double that exists somewhere, you can do it like this:
import kotlin.reflect.KMutableProperty0
var wow: Double = 1.2
fun main() {
println(wow)
setVar(::wow, 6.9)
println(wow)
}
fun setVar(variable: KMutableProperty0<Double>, value: Double) {
variable.set(value)
}
>> 1.2
>> 6.9
(see Property references if you're not familiar with the :: syntax)
KMutableProperty0 represents a reference to a mutable property (a var) which doesn't have any receivers - just a basic var. And don't worry about the reflect import, this is basic reflection stuff like function references, it's part of the base Kotlin install
Yes, method parameters can also be references to classes or interfaces. And method parameters can also be references to other methods/functions/lambdas.
If you are dealing with cases that are hard to generalize, consider using some kind of inversion of control (function as parameter or lambda).
You add a lambda parameter to your updateProperty function
fun updateProperty(onUpdate: (viewBinding: YourViewBindingType, viewModel: YourViewModelType) -> Unit) {
//... other irrelevant code
// here you just call the lambda, with any parameters that might be useful 'on the other side'
onUpdate(viewBinding, viewModel)
//... other irrelevant code
}
Elsewhere in code - case 1:
updateProperty() { viewBinding, viewModel ->
if (viewBinding.editText.text.toString() != "") {
viewModel.property.price = viewBinding.editText.text.toString().toDouble()
}
}
Elsewhere in code - case 2:
updateProperty() { viewBinding, viewModel ->
if (viewBinding.secondEditText.text.toString() != "") {
viewModel.property.income = viewBinding.secondEditText.text.toString().toDouble()
}
}
Elsewhere in code - case 3:
updateProperty() { viewBinding, viewModel ->
// I am a totally different case, because I have to update two properties at once!
viewModel.property.somethingElse1 = viewBinding.thirdEditText.text.toString().toBoolean()
viewModel.property.somethingElse2 = viewBinding.fourthEditText.text
.toString().replaceAll("[- ]*", "").toInt()
}
You could then go even further and define a function for the first 2 cases, since those 2 can be generalized, and then call it inside the lambda (or even pass it as the lambda), which would save you some amount of code, if you call updateProperty() in many places in your code or simply define a simple function for each of them, and call that instead, like this
fun updatePrice() = updateProperty() { viewBinding, viewModel ->
if (viewBinding.editText.text.toString() != "") {
viewModel.property.price = viewBinding.editText.text.toString().toDouble()
}
}
fun updateIncome() = updateProperty() { viewBinding, viewModel ->
if (viewBinding.secondEditText.text.toString() != "") {
viewModel.property.income = viewBinding.secondEditText.text.toString().toDouble()
}
}
Then elsewhere in code you just call it in a really simple way
updatePrice()
updateIncome()
I would like to check whether the bundle has the specified key.
Is there any modern way using kotlin to check it?
For now I was using
if(bundle.containsKey(Extras.PRODUCT){
bundle.getParcelable<Product>(Extras.PRODUCT)?.let{
mpresenter.mProduct = it
}
}
if(bundle.containsKey(Extras.ANIMAL){
bundle.getParcelable<ANIMAL>(Extras.ANIMAL)?.let{
mpresenter.mAnimal = it
}
}
... an so on
its okay if I only check one value of the Extras. But What if I have 10 or more variable in presenter. Is there any simpler solution for my case?
You could make some extension functions like
fun <T : Parcelable?> Bundle.tryGetParcelable(key: String): T? =
// getParcelable would return null anyway, but this is a general example
if (containsKey(key)) getParcelable<T>(key) else null
bundle.tryGetParcelable<Product>(Extras.PRODUCT)?.let { mPresenter.mProduct = it }
If that's still too wordy, you can pass property references and call set on those, like this:
// Upper bound isn't nullable now, since we're only assigning if the value is non-null
fun <T : Parcelable> Bundle.tryAssign(key: String, property: KMutableProperty0<T>) {
tryGetParcelable<T>(key)?.let { property.set(it) } // or let(property::set)
}
bundle.tryAssign<Product>(Extras.PRODUCT, mPresenter::mProduct)
but you might want to make the property the receiver instead, so it reads more like the usual thing = whatever
fun <T : Parcelable> KMutableProperty0<T>.tryAssign(bundle: Bundle, key: String) {
bundle.tryGetParcelable<T>(key)?.let { set(it) } // or run(::set)
}
mPresenter::mProduct.tryAssign<Product>(bundle, Extras.PRODUCT)
You'll need to make tryGetX functions for each type of Bundle getter you need, getString returns nulls but getInt always returns an Int, so it helps to have handlers that do the containsKey check so you don't need to worry about coming up with suitable, reserved default values for "not present".
If you make all those basic getter functions the same way (returning nullables) you can reuse that tryAssign function if you like, passing in the appropriate getter:
// Not using this here but it's the same getter signature, (Bundle, String) -> T?
// Note that because we're going to be passing references to these functions, we can't
// define them as extension functions in the same file - so the Bundle is a parameter now
fun tryGetString(bundle: Bundle, key: String): String? {
return bundle.getString(key)
}
// Now we're passing in the getter function we want to use, which returns a T?
// T doesn't have a Parcelable upper bound anymore
fun <T> KMutableProperty0<T>.tryAssign(bundle: Bundle, key: String, tryGet: (Bundle, String) -> T?) {
tryGet(bundle, key)?.run(::set)
}
// you won't need the type in diamond brackets, it's just for illustration
mPresenter::mProduct.tryAssign<Product>(bundle, Extras.PRODUCT, ::tryGetParcelable)
I mean, this is starting to get a bit intense, but if you have a lot of stuff to assign it might be worth having it cleanly ordered like this? Some stuff to try anyway!
Jetpack Navigation with Safe Args is now the recommended for navigating and passing data
Related Codelab Android Navigation
The navigation component has a Gradle plugin, called safe args, that
generates simple object and builder classes for type-safe access to
arguments specified for destinations and actions.
Safe args allows you to get rid of code like this when passing values
between destinations:
val username = arguments?.getString("usernameKey") And, instead,
replace it with code that has generated setters and getters.
val username = args.username
I'm new to coding in kotlin and want to implement an immutable class that represents a project with various fields inside.
The easiest way to do this is by using a data class and using the copy() method so that anytime one of the app user modifies a field it results in the backend in a call to the copy method with the modified field producing the new project.
My problem is that this way does not allow for prior checking of parameters (eg : limit string size of the owner, making sure the number of people added to the project is reasonable etc).
If this was java, I'd use a builder pattern but this seems to defeat the purpose of kotlin, and i've read articles that are positive to using builders in kotlin (https://www.baeldung.com/kotlin/builder-pattern)
and others that are completely against (https://code-held.com/2021/01/23/dont-use-builder-in-kotlin/).
I haven't found any way to "modify" the copy method and to add the parameter sanitization checks that are needed for each parameter. I would appreciate any "smooth" idea to implement this, if anybody has found it. The goal would also be to throw exeptions/sealed classes variables so that the app UI can tell the user what went wrong instead of a generic error message just mentioning that the project was not modified.
I agree with the second link. If you look at the comments on the Baeldung article, you'll see even they were convinced and pledged to revise the article.
You can throw exceptions in an init block but if these are exceptions that are not caused by programmer error, it would be more Kotlin-idiomatic to expose a single constructor-like function that returns a wrapper or just null for invalid input.
Examples:
data class Person(val name: String, val age: Int = 0) {
init {
if (age < 0) {
throw IllegalArgumentException("Age $age is less than 0.")
}
}
}
If you want to return a wrapper or nullable, a data class isn't suitable for preventing invalid input because the generated copy() function will always return a fully constructed object. Sadly, Kotlin does not support overriding the generated copy() function.
sealed class Result<T>
data class Success<T>(val value: T): Result<T>()
data class Failure<T>(val reason: String): Result<T>()
class Person private constructor(val name: String, val age: Int = 0) {
companion object {
fun build(name: String, age: Int = 0): Result<Person> {
return when {
age < 0 -> Failure("Age $age is less than 0.")
else -> Success(Person(name, age))
}
}
}
fun buildCopy(name: String = this.name, age: Int = this.age) = build(name, age)
}
I have a ViewModel class that looks like this:
class EditUserViewModel(
private val initUser: User,
) : ViewModel() {
private val _user = MutableLiveData(initUser)
val user: LiveData<User>
get() = _user
fun hasUserChanged() = initUser != _user.value
}
User can update some properties of the User data class instance through the UI.
To check if there are any changes when navigating from the fragment I use hasUserChanged method.
The problem is that is always false. I checked and it seems that the initialUser changes every time I change the _user MutableLiveData.
Why is that? Is the initial value of MutableLiveData passed by reference? I always thought that Kotlin is a "pass-by-value" type of language.
Update:
The problem seems to disappear when copying initUser before putting it inside the MutableLiveData.
private val _user = MutableLiveData(initUser.copy())
But it still doesn't make sense to me why I have to do that.
Kotlin is like java and they are pass-by-value. If you implement the equals function in User class, or make it as data class (which implements the equals function implicitly), it makes you sure that the content of the user objects is checked by != operator.
Update
If you are changing the value of LiveData directly, for example like this:
_user.value.name = "some name"
it means that you are changing the name property of the initUser, because _user.value exactly refers to the object that the initUser does. Consequently, the != operator always returns false, because we have one object with two references to it.
Now, when you are doing so:
private val _user = MutableLiveData(initUser.copy())
you are creating a deep copy of initUser (let's call it X) which is a new object in memory with the same property values of initUser.
Thus, by changing its properties like: _user.value.name = "some name", in fact, you are making this change on X, not initUser. It leads to preserving the initial values in initUser, meaning do not changing them, and solving the issue.
I am trying to write a function in kotlin but I am not able reassign value to function parameters ,its saying val cannot be reassigned .
class WebView{
var homepage = "https://example.com"
fun webViewLoad(url: String, preferredOrientation: String) {
if (url.equals("homepage")){
url = homepage
}
}
}
when I am trying to assign a value to url = homepage .it is giving me error val cannot be reassigned , I am new to kotlin ,I do not understand what is the issue , little help will be appreciated.
Function parameters works like val variables that couldn't be reassigned. Here you need to add variable with conditional initialization:
fun webViewLoad(url: String, preferredOrientation: String) {
val urlValue = if (url.equals("homepage")){
homepage
} else {
url
}
... //use here "urlValue" variable
}
By the way, in kotlin you don't need to use equals function to compare string: common operator == will be automatically replaced with equals in byte code.
Kotlin parameters are immutable since Kotlin M5.1
(Reference)
The main reason is that this was confusing: people tend to think that this means passing a parameter by reference, which we do not support (it is costly at runtime). Another source of confusion is primary constructors: “val” or “var” in a constructor declaration means something different from the same thing if a function declarations (namely, it creates a property). Also, we all know that mutating parameters is no good style, so writing “val” or “var” infront of a parameter in a function, catch block of for-loop is no longer allowed.
It is giving you error "val cannot be reassigned" because Kotlin function parameters are immutable i.e "val" by default. You don't need to mention the "val" keyword for it.
Quick Solution would be:
class WebView{
var homepage = "https://example.com"
fun webViewLoad(url: String, preferredOrientation: String) {
val finalUrl = if (url.equals("homepage")) homepage else url
}
}
Kotlin function parameters are final. There is no val or final keyword because that's the default (and can't be changed). Have a look at this.
By default parameters passed in the function are final what you can do is to add var. Hope it helps.
fun webViewLoad(var url: String, preferredOrientation: String) {
if (url.equals("homepage")){
url = homepage
}
}