Access to object not working and return null - kotlin

I am trying to save data into a kotlin object but I feel that it's not working.
the object is
object User {
var userInfo: UserInfo? = null
var listOfVehicles: MutableList<Vehicle> = mutableListOf()
init {}
fun clear(){
userInfo = null
listOfVehicles = mutableListOf()
}
}
and I would like to be able to access it from everywhere and be able to modify it from any activities, fragments....
to add a value inside, I just do : User.userInfo.name = "blabla" but it keep being null
any idea how to fix it ? I tried as well to use val user: User? = null but it's the same.
I want to have User a single data structure used by any part of the code to retrieve or add data.
What did I miss ?
Thanks

You need to instantiate the userInfo before assigning values to it.
object User {
var userInfo: UserInfo? = UserInfo()
var listOfVehicles: MutableList<Vehicle> = mutableListOf()
init {
}
fun clear() {
userInfo = null
listOfVehicles = mutableListOf()
}
}
Now to use this, User.userInfo?.name = "blabla"

Related

Kotlin / value passing to List<>()

I have a question in List<Contact>() I'm asked to pass init and size. I'm not sure if it's obligated to pass it as in my following tutorial ArrayList<String>() was empty, maybe it's because I was using List<>? Also, it doesn't recognize lowercase() and add() is it also related to List<>?
Code Snippet
val contacts = remember { DataProvider.contactList }
var filteredContacts: List<Contact>
val textState = remember { mutableStateOf(TextFieldValue("")) }
LazyColumn(
...
) {
val searchText = textVal.value.text
filteredContacts = if (searchText.isEmpty()){
contacts
}
else{
val resultList = List<Contact>()
for (contact in contacts) {
if (contact.lowercase(Locale.getDefault()).contains(searchText.lowercase(Locale.getDefault()))) {
resultList.add(contact)
}
}
resultList
}
In kotlin, List has no add method. For that you would need to have a MutableList.
Regarding lowercase method, this is available for Strings. You are trying to apply that to a Contact object, which I guess has no lowercase method.

Syncing data fetched from retrofit (MutableLiveData) with data from Room DB (LiveData) in an android app

The basic idea of the app is the following: fetch data from server using retrofit whenever its up, fall back on local room database whenever the server is unreachable. I have an issue with the way I'm saving data in my view models however. I'm using LiveData for the data fetched from Room and MutableLiveData for the data fetched from the server. Not sure how to have a single source for the data though. The Retrofit API returns my entities (Recipe) as List<Recipe>. Only way I know to persist that is using MutableLiveData:
var readAllIngredients = MutableLiveData<List<Ingredient>>().apply { value = emptyList() }
...
readAllIngredients.postValue(NetworkService.service.getIngredients())
Changing readAllIngredients to LiveData<List<Ingredient>> and then trying to set the value field of the list doesn't work since apparently value is not assignable to.
Can't make the DAO return MutableLiveData<List<Ingredient>> either (getting some compile errors). And I heard trying to cast one of these types to the other isn't exactly best practice. So I'm not sure what else I could try.
class InventoryViewModel(application: Application): AndroidViewModel(application) {
var readAllIngredients = MutableLiveData<List<Ingredient>>().apply { value = emptyList() }
private val ingredientRepository: IngredientRepository
init {
val ingredientDAO = ShoppingAppDatabase.getDatabase(application).ingredientDAO()
ingredientRepository = IngredientRepository(ingredientDAO)
viewModelScope.launch(Dispatchers.IO) {
// logic moved from repository in order to make example more concise
if(NetworkService.serverReachable()) {
readAllIngredients.postValue(NetworkService.service.getIngredients())
}
else {
//save the data from the database somehow; used to do it like
//readAllIngredients = ingredientRepository.allIngredients
//when readAllIngredients was of type `LiveData<List<Ingredient>>`
}
}
}
MediatorLiveData is used when you want to combine multiple sources. Example:
class InventoryViewModel(application: Application): AndroidViewModel(application) {
var readAllIngredients = MediatorLiveData<List<Ingredient>>().apply { value = emptyList() }
private val ingredientRepository: IngredientRepository
private var gotNetworkIngredients = false
init {
val ingredientDAO = ShoppingAppDatabase.getDatabase(application).ingredientDAO()
ingredientRepository = IngredientRepository(ingredientDAO)
readAllIngredients.addSource(ingredientRepository.allIngredients) { repoValue ->
if (!gotNetworkIngredients) {
readAllIngredients.value = repoValue
}
}
viewModelScope.launch(Dispatchers.IO) {
// logic moved from repository in order to make example more concise
if(NetworkService.serverReachable()) {
gotNetworkIngredients = true
readAllIngredients.postValue(NetworkService.service.getIngredients())
}
else {
//save the data from the database somehow; used to do it like
//readAllIngredients = ingredientRepository.allIngredients
//when readAllIngredients was of type `LiveData<List<Ingredient>>`
}
}
}
}

Kotlin Data class copy extension

I am trying to find a solution for a nice kotlin data class solution. I have already this:
data class Object(
var classMember: Boolean,
var otherClassMember: Boolean,
var example: Int = 0) {
fun set(block: Object.() -> kotlin.Unit): Object {
val copiedObject = this.copy()
copiedObject.apply {
block()
}
return copiedObject
}
fun touch(block: Object.() -> kotlin.Unit): Object {
return this.set {
classMember = true
otherClassMember = false
block() }
}
}
val test = Object(true,true,1)
val changedTest = test.touch { example = 2 }
the result of this method is that the changedTest object has classMember = true, otherClassMember = false and example = 2
The problem with this solution is, the class properties are not immutable with var declaration. Does somebody have an idea how to optimize my methods to change var to val?
val says that a variable can't change it's value after initialization at the definition point. Kotlin's generated copy method does not modify an existing copy after construction: this method actually uses retrieved values from an object, replaces these values with ones that provided in copy method (if any), and after that just constructs a new object using these values.
So, it is not possible to perform such an optimization if you are going to change object's state after construction.
If I understood what you want correctly, you can do
data class Object(
val classMember: Boolean,
val otherClassMember: Boolean,
val example: Int = 0) {
fun touch(example: Int = this.example): Object {
return copy(
classMember = true,
otherClassMember = false,
example = example)
}
}
val test = Object(true,true,1)
val changedTest = test.touch(example = 2)
Though you need to repeat parameters other than classMember and otherClassMember but without reflection you can't do better.

Simple casting in Kotlin/Java

I have an object User defined as below
class User(){
var id: Int? = null
var name: String? = null}
For certain reasons, I have to create new object User of same parameters and I have to copy data from old to new type.
class UserNew(){
var id: Int? = null
var name: String? = null}
I was looking for easiest way to convert from old type to a new one. I want to do simply
var user = User()
var userNew = user as UserNew
But obviously, I am getting This cast can never succeed. Creating a new UserNew object and set every parameter is not feasible if I have a User object with lots of parameters. Any suggestions?
as is kotlin's cast operator. But User is not a UserNew. Therefore the cast fails.
Use an extension function to convert between the types:
fun User.toUserNew(): UserNew {
val userNew = UserNew()
userNew.id = id
userNew.name = name
return userNew
}
And use it like so
fun usingScenario(user: User) {
val userNew = user.toUserNew()
If you don't want to write a boilerplate code, you can use some libraries that will copy values via reflection (for example http://mapstruct.org/), but it's not the best idea.
To achieve you can Simply use Gson and avoid boilerplate code:
var user = User(....)
val json = Gson().toJson(user)
val userNew:UserNew =Gson().fromJson(json, UserNew::class.java)
you should follow this logic for this case.
note: #Frank Neblung answer i implemented
fun main(args: Array<String>) {
val user = User()
user.id = 10
user.name = "test"
var userNew = user.toUserNew()
println(userNew.id) // output is 10
println(userNew.name)// output is test
}
class User()
{
var id: Int? = null
var name: String? = null
fun toUserNew(): UserNew {
val userNew = UserNew()
userNew.id = id
userNew.name = name
return userNew
}
}
class UserNew() {
var id: Int? = null
var name: String? = null
}
You have two options. Either create interface and implement it in both classes. then you can use this interface in both places (User,UserNew) If this is not what you want, i would use copy constructor in UserNew taking User as parameter, You can create new
NewUser nu = new UserNew(userOld)
if you have lots of properties answer from ppressives is way to go
To achieve that you can use the concept of inheritance:
https://www.programiz.com/kotlin-programming/inheritance
Example:
open class Person(age: Int) {
// code for eating, talking, walking
}
class MathTeacher(age: Int): Person(age) {
// other features of math teacher
}

Ignore setter and set property directly

I have a class that writes a user to SharedPreferences every time it is set:
class UserManager #Inject constructor(
val prefs: SharedPreferences,
val jsonAdapter: JsonAdapter<User>
) {
companion object {
val USER = "user"
}
var user: User = User()
set(value) {
field = value
prefs.edit().putString(USER, jsonAdapter.toJson(user)).apply()
}
init {
val userString = prefs.getString(USER, null)
if (userString != null) {
user = jsonAdapter.fromJson(userString)
}
}
}
Problem: If the user is set in the init block, it calls the setter and writes the user that we just got from the shared prefs... to the shared prefs.
Question 1: How can I directly set the property from the init block?
Question 2: Why do I have to initialize the User when I define a custom setter, but can omit the initialization when the default setter is used?
You need to directily initiliaze the property with the correct value. You can do this using the run function from the stdlib:
class UserManager #Inject constructor(
val prefs: SharedPreferences,
val jsonAdapter: JsonAdapter<User>
) {
companion object {
val USER = "user"
}
var user: User = run {
val userString = prefs.getString(USER, null)
if (userString != null) {
jsonAdapter.fromJson(userString)
} else {
User()
}
}
set(value) {
field = value
prefs.edit().putString(USER, jsonAdapter.toJson(user)).apply()
}
}
Shorter syntax proposed by Ilya Ryzhenkov on the Kotlin Slack:
var user: User = prefs.getString(USER, null)?.let { jsonAdapter.fromJson(it) } ?: User()
set(value) {
field = value
prefs.edit().putString(USER, jsonAdapter.toJson(user)).apply()
}
I believe the best solution is to use the 'backing property' concept described here: https://kotlinlang.org/docs/reference/properties.html#backing-properties
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null)
_table = HashMap() // Type parameters are inferred
return _table ?: throw AssertionError("Set to null by another thread")
}
Then initialize the backing property in the constructor and do <backingproperty> = value instead of field = value as well as point the getter to the backing property.
Take a look at by map delegate, seems like this is the pattern you want:
class User(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
https://kotlinlang.org/docs/reference/delegated-properties.html#storing-properties-in-a-map