By delegate data class on observeAsState - android

I want to use the state by delegate syntax on observeAsState, but it report a error show there is no getValue method in data class.
#Composable
fun ComposeScreen(
...
) {
val item: Item by viewModel.item.observeAsState(Item) // there is an error in `(Item)`, it seems not delegate directly using the model of data class.
}
// viewModel
val item = itemRepository.item // item is a LiveData
// model
data class Item(
...
)
UPDATE
I find the solution reference other one's demo project, but I still not understand why do this.
import androidx.compose.runtime.getValue
val item: Item? by viewModel.item.observeAsState()

You need to import it mannualy, like this:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
or
import androidx.compose.runtime.*
I believe it's a bug in Android Studio where it does not suggest to import it automatically.
[EDIT]
You edited your question asking why we need that import, so I'll try to wrap it up:
When we want to delegate an assignment to a class using "by", we need to make a function called "getValue" with the "operator" modifier that returns the value in the correct type. It's the same logic for "setValue", we need to ask for a parameter of the right type and use "operator", allowing you to use "by" in a "var", besides "val".
What's happening there is that the function "getValue" and "setValue" are declared as extension functions, so it isn't enough to import "State", you also have to import the top level extension functions that are in a separated file.
Let me know if that explanation was enough, I took me a while to understand that, even thought that the documentation was out of date, by I was missing the import.
P.S. Here are the two functions in Compose's code, as you can see, they extends the "State" class.
inline operator fun <T> State<T>.getValue(thisObj: Any?, property: KProperty<*>): T = value
inline operator fun <T> MutableState<T>.setValue(
thisObj: Any?, property: KProperty<*>, value: T) {
this.value = value
}

Related

How to specify in a lambda "accepts generic sub-type of a generic type"?

This question is a derivative of a recent one I made here
While that one was resolved, I immediately faced a new one of a similar nature, but I am also not able to find a solution for it.
Sample example:
abstract class Endpoint<T>() {
private val myList: MutableList<(T) -> Unit> = mutableListOf()
fun <E : T> addToList(cbk: (E) -> Unit) { <-- E extends T
myList.add(cbk)
}
}
Usage example would be
Some sealed class
sealed class MainSealedClass {
data class ChildClass(val someParam: Int): MainSealedClass()
}
And the function call
anEndpointInstance.addToList<ChildClass>{it: ChildClass ->
// do something here
}
I tried doing the following, but it looks like it is not is not allowed
val myList: MutableList<(out T) -> Unit> <--- Unsupported error shows up
Is there a way to do this without having to add an extra declaration at the class level?
I considered using inline reified but the function needs to access private fields of the Endpoint instance, so I do not want to use that option.
I don't think the language supports specifying variance of the parameters of a functional type, or references to functions with generic types that aren't defined. For example, if you want to get a reference to a generic function, you have to specify its generic types in the functional type declaration.
There is dubious usefulness here anyway. If you had a list of functions that each have different input types, then when you retrieve a function from the list, you won't be able to call it because you won't know which input type it supports. You might as well have a MutableList<Any>.
Thanks to Tenfour04's knowledge, I was able to find a workaround to my issue. I do not consider this to be a fix for my issue, but given that it apparently cannot be done as I expected, the next best thing was to work around the issue with the help of a wrapper.
Here is how my code turned out with the work using the sample in my question
abstract class Endpoint<T>() {
private val myList: MutableList<(T) -> Unit> = mutableListOf()
fun add(cbk: (T) -> Unit) {
myList.add(cbk)
}
inline fun <reified N : T> addToList(crossinline callback: (N) -> Unit) {
add { if (it is N) callback.invoke(it) }
}
}
And its usage is the way I wanted in my question, nothing changes at this point.

Type 'State<List<User>?>' has no method 'getValue(Nothing?, KProperty<*>)' and thus it cannot serve as a delegate

I'm trying to get a value from LiveData with observeAsState in jetpack compose, but I get a weird error
Type 'State<List?>' has no method 'getValue(Nothing?,
KProperty<*>)' and thus it cannot serve as a delegate
Code
#Composable
fun UserScreen(userViewModel:UserViewModel){
val items: List<User> by userViewModel.fetchUserList.observeAsState()
UserList(userList = items)
}
ViewModel
class UserViewModel: ViewModel() {
private val dataSource = UserDataSource()
val fetchUserList = liveData {
emit(dataSource.dummyUserList)
}
}
If you get a compiler error that observeAsState or getValue are not defined make sure you have the following imports:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
This information is from Step #4 in the "Using State in Jetpack Compose" codelab.
To fix the error add the following imports:
// for a 'val' variable
import androidx.compose.runtime.getValue
// for a `var` variable also add
import androidx.compose.runtime.setValue
// or just
import androidx.compose.runtime.*
To use a variable as a property delegate you should provide getValue operator function for read-only val variables and getValue and setValue functions for var variables.
To read more about how property delegates and state are combined in jetpack compose see Use remember to create internal state in a composable documentation section. There's also an explanation in Thinking in Compose video.
You could use: import androidx.compose.runtime.*
Necessary imports are:
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
var value by remember { mutableStateOf("") }
For me manually/explicitly importing both the below apis worked to resolve this compilation issue,
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
Here is the reference,
https://developer.android.com/jetpack/compose/state#state-in-composables
I think type of items must be nullable since you observing LiveData:
val items: List<User>? by userViewModel.fetchUserList.observeAsState()
Adding a dependency fixed the problem for me:
implementation "androidx.compose.runtime:runtime:$compose_version"
Thanks to Tayaab Mizhar
You need to import
import androidx.compose.runtime.getValue
which will import this function which is basically by
inline operator fun <T> State<T>.getValue(thisObj: Any?, property: KProperty<*>): T = value
Just add this line manually below all the import libraries.
import androidx.compose.runtime.*
In my case, in a compose application it was this missing import that provoc the error
import androidx.compose.getValue
I had this problem despite imports! Took a bit but then I realised where my problem was the variable for whatever you're observing needs to be a val not a var:
In my case it was
var background: Int by currentBackgroundColor.observeAsState(0)
Should have been:
val background: Int by currentBackgroundColor.observeAsState(0)

How to use a Kotlin extension function on a class?

I have a pretty short question about an extension function that would help clear some of my code. Basically I have some transformations on the hashCode of a class name and I want an extension function to do the transformations.
Example:
Getting the name hashCode: StateA::class.java.name.hashCode() where StateA is a simple class.
I want to the extension function like:
fun Class<*>.transformName(): String {
var hashString = this.javaClass.name.hashCode()
//Do my stuff on that hashString
return hashString
}
But this doesn't seem to work. When I apply the extension function with StateA.transformName(), the function gives me an error with Unresolved Reference.
I tried various things like applying the function to StateA::class or having the hashString equal to this::class.java.name.hashCode() but nothing works. Any tips?
You can't really achieve the StateA.transformName() syntax, as StateA just on its own refers to the companion object inside that class. So to get that syntax, you'd need to have a companion object inside every class that you want to use this extension on.
What you can do in a very general way is get the KClass that describes your class first. This gives you an object (the KClass instance) that you can then call an extension on:
fun KClass<*>.transformName() {
val clazz: Class<*> = this.java
clazz.name.hashCode()
}
StateA::class.transformName()
Another approach, which is less verbose on the call site could be a generic function like this, where the reified keyword allows you to access the concrete class that was used as the generic type parameter inside the function:
inline fun <reified T> transformName() {
val clazz: Class<*> = T::class.java
clazz.name.hashCode()
}
transformName<StateA>()

Jetpack Compose: Exception while analyzing expression

I am trying to build simple app using Jetpack Compose.
I followed this documentation, downloaded repository and created my own module.
Code is pretty simple:
import android.app.Activity
import android.os.Bundle
import androidx.compose.Composable
import androidx.ui.core.Text
import androidx.ui.core.setContent
import androidx.ui.material.surface.Card
import androidx.ui.graphics.Color
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp()
}
}
#Composable
fun MyApp() {
Card(color = Color.Cyan) {
Text("test")
}
}
}
But I noticed that some composable widgets doesnt work and I have following error:
Exception while analyzing expression at (23,9) in
/path/Projects/androidx-master-dev/frameworks/support/ui/compose/src/main/java/app/myown/MainActivity.kt
Where (23,9) references to Card widget
By the way other widgets work, for example I dont have problems with
#Composable
fun MyApp() {
Padding(10.dp) {
Text("test")
}
}
It compiles and runs perfectly.
I got following problem with:
Card
Column
Row
Center
FlexColumn
and I guess many others widgets
I ran into this problem earlier.
There is an implicit need to have import androidx.compose.composer in every Kotlin source file that has #Composable functions. I say "implicit" because Android Studio thinks that it is unnecessary and has a tendency to remove that line (e.g., you ask it to optimize imports). Some #Composable functions can survive without this import, but others cannot.
As I understand it, this is one of those things that will get better as the libraries and tooling evolve, but at the moment, just keep an eye out for that import and add it if it is missing and you are getting weirder-than-normal results.

Using JSON.stringify on object without serializer needs to be marked as experimental

Using kotlin plugin 1.3.10 in Android Studio,
when I try to stringify a simple class' object to JSON, it wont compile:
This declaration is experimental and its usage must be marked with '#kotlinx.serialization.ImplicitReflectionSerializer' or '#UseExperimental(kotlinx.serialization.ImplicitReflectionSerializer::class)'
#Serializable data class Data(val a: Int, val b: Int)
val data = Data(1, 2)
val x = JSON.stringify(data)
However, giving a serialiser works:
val x = JSON.stringify(Data.serializer(), data)
I can't see anybody else having this problem, any idea what the problem is? I've set up using serialisation in gradle.build.
I import with:
import kotlinx.serialization.*
import kotlinx.serialization.json.JSON
The overload of StringFormat.stringify which doesn't take in a serializer (SerializationStrategy) is still experimental. If you view its definition (e.g. ctrl+click on it in the IDE) you'll see it looks as follows:
#ImplicitReflectionSerializer
inline fun <reified T : Any> StringFormat.stringify(obj: T): String = stringify(context.getOrDefault(T::class), obj)
Where that ImplicitReflectionSerializer annotation is itself declared in that same file (SerialImplicits.kt):
#Experimental
annotation class ImplicitReflectionSerializer
So because it's still experimental, you need to do exactly as the warning says, i.e. tell the compiler to allow the use of experimental features, by adding an annotation such as #UseExperimental... where you're using it.
Note that the quick example shown on the kotlinx.serialization GitHub repo's main readme shows that you need to pass in a serializer when calling stringify.

Categories

Resources