There are many places in the Android development documentation that use Kotlin delegates, but I don't quite understand the following example:
val uiState by viewModel.userFlow.collectAsStateWithLifecycle()
I looked at the collectAsStateWithLifecycle function
#ExperimentalLifecycleComposeApi
#Composable
fun <T> Flow<T>.collectAsStateWithLifecycle(
initialValue: T,
lifecycle: Lifecycle,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
context: CoroutineContext = EmptyCoroutineContext
): State<T> {
return produceState(initialValue, this, lifecycle, minActiveState, context) {
lifecycle.repeatOnLifecycle(minActiveState) {
if (context == EmptyCoroutineContext) {
this#collectAsStateWithLifecycle.collect { this#produceState.value = it }
} else withContext(context) {
this#collectAsStateWithLifecycle.collect { this#produceState.value = it }
}
}
}
}
From this function I don't see anything related to the delegate, what am I missing?
When I use the = operator to define uiState, the type of uiState is Sate<List<User>>, and when using by, the type is List<User>. But I don't know what's the rationale.
If you don't read the documentation, can you tell me how to see from the source code that the by keyword must be used to define a variable?
It returns a State. State is a type that can optionally be used as a delegate. Any type that has the appropriate getValue operator function available can be used as a property delegate. Using it as a delegate is optional. It’s still a type that can be referenced like any other, and therefore assigned to a val with =.
You wouldn’t see anything about that in this function you posted. The ability to use it as a delegate is defined by the applicable operator function on the State type (it’s an extension function).
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()
There is function collectAsState() applicable to a StateFlow property in order to observe it in a Composable.
A composable requires a StateFlow because StateFlow guarantees an initial value. A Flow doesn't come with that guarantee.
Now, what is the way to go if I have a StateFlow property but I want to apply an operator (like map) before collecting the Flow in the Composable?
Here an example:
Let's say a repository exposes a StateFlow<MyClass>
val myClassStateFlow: StateFlow<MyClass>
data class MyClass(val a: String)
... and a view model has a dependency on the repository and wants to expose only the property a to its Composable...
val aFlow = myClassState.Flow.map { it.a } // <- this is of type Flow<String>
The map operator changes the type from StateFlow<MyClass> to Flow<String>.
Is it semantically justified that aFlow has no initial value anymore? After all its first emission is derived from the initial value of myClassStateFlow.
It's required to convert Flow back into StateFlow at some point. Which is the more idiomatic place for this?
In the view model using stateIn()? How would the code look like?
In the composable using collectAsState(initial: MyClass) and come up with an initial value (although myClassStateFlow had an initial value)?
See this issue on GitHub
Currently there is no built-in way to transform StateFlows, only Flows. But you can write your own.
Way I ended up solving was to use the example in that post.
First create a notion of a DerivedStateFlow.
class DerivedStateFlow<T>(
private val getValue: () -> T,
private val flow: Flow<T>
) : StateFlow<T> {
override val replayCache: List<T>
get () = listOf(value)
override val value: T
get () = getValue()
#InternalCoroutinesApi
override suspend fun collect(collector: FlowCollector<T>) {
flow.collect(collector)
}
}
Then have an extension on StateFlow like the current map extension on Flow
fun <T1, R> StateFlow<T1>.mapState(transform: (a: T1) -> R): StateFlow<R> {
return DerivedStateFlow(
getValue = { transform(this.value) },
flow = this.map { a -> transform(a) }
)
}
Now in your Repository or ViewModel, you can use it as below.
class MyViewModel( ... ) {
private val originalStateFlow:StateFlow<SomeT> = ...
val someStateFlowtoExposeToCompose =
originalStateFlow
.mapState { item ->
yourTransform(item)
}
}
Now you can consume it as you expect in Compose without any special work, since it returns a StateFlow.
I think two use case of remember exist.
first one is
#Composable
fun abc() {
var aa = remember { mutableStateOf(true) }
}
and second
#Composable
fun abc() {
var aa by remember { mutableStateOf(true) }
}
Is there any functional differencies exist? or just for convenience?
It is just for convenience, to shortify syntax. By using delegate (by keyword) you can skip relating to value because it is done under the hood.
In the documentation you can read
There are three ways to declare a MutableState object in a composable:
val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }
These declarations are equivalent, and are provided as syntax sugar
for different uses of state. You should pick the one that produces the
easiest-to-read code in the composable you're writing.
Cheers
i've noticed a slight difference between both expressions :
when using
#Composable
fun abc() {
var aa = remember { mutableStateOf(true) }
}
var aa will be of type MutableState.
in case "by" is used
#Composable
fun abc() {
var aa by remember { mutableStateOf(true) }
}
var aa will be considered of type Boolean.
personally i prefer using by to preserve the primitive types.
in the first case: you have to use "aa.value" whenever you want to use the value of "aa".
in the second case: you don't have to, because under the hood you aren't using the getter of the var.
additional:
you know that whenever you call or edit a variable you are under the hood calling its getter and setter functions, so when you use "by" you are using another getter and setter which delegated to another piece of code (usually to the function the comes after "by" keyword) and then you do not have to use "aa.value", you just can write "aa" when call it or "aa = true" when you set it.
"var" have setter and getter functions because it is editable, while "val" have just getter and no setter cause it is not editable (read only).
note: if you want to read more then check this answer for a well understanding of the concept: read more
I would like get the class property from a generic type T.
I've decided to extend to Any but I'm getting an error.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/index.html#extension-properties
I have the following code:
class FirebaseDBRepo<T : Any>(val child:String) {
private var callback: FirebaseDatabaseRepositoryCallback<T>? = null
private val ref: DatabaseReference
private val listener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
//T::class.java is showing the error cannot use t as reified type parameter use class instead
val gameDS = dataSnapshot.getValue(T::class.java)
callback!!.onSuccess(gameDS!!)
}
override fun onCancelled(databaseError: DatabaseError) {
}
}
init {
ref = FirebaseDatabase.getInstance().reference.child(child)
}
fun addListener(callback: FirebaseDatabaseRepositoryCallback<T>) {
this.callback = callback
ref.addValueEventListener(listener)
}
fun removeListener() {
ref.removeEventListener(listener)
}
}
You can only get the class on reified variables. The same thing happens in java, but with a slightly different message:
public <T> void x(){
T t = T.class.newInstance();
}
In Java, you'd solve this like:
public <T> void x(Class<T> cls){
T t = cls.newInstance();
}
The same applies to Kotlin, and any calls. You'd need to get a class instance in most cases. However, Kotlin supports reified generics using a keyword, but only on inline generic functions. You could pass a class, but in functions, it's really easy just using the reified keyword.
As in you can't declare a class with reified generics, which means this is invalid:
class SomeClass<reified T>
But it is valid for inline functions, meaning you can do:
inline fun <reified T> someFunction()
So you have two options. But since you extend a listener, the first option of adding the generics to the function isn't an option. You can't override a non-generic method with generics. It won't compile.
Which leaves the second option, which unfortunately is rather hackish; passing the class to the constructor. So it should look like this:
class FirebaseDBRepo<T : Any>(val child: String, private val cls: Class<T>) {
Now, I don't use Firebase, so I have no clue what classes you'd pass, so for this next example, I just use String.
Kotlin supports some type minimization without going over to raw types. This:
val t = FirebaseDBRepo<String>("", String::class.java)
Could be shortened to this:
val t = FirebaseDBRepo("", String::class.java)
The inferred type in both cases is FirebaseDBRepo<String>.
Since you are running on the JVM, type erasure is a thing.
This means (in simplified terms), that during compilation, the generics are simply ignored. Therefore, you cannot get the class of T, as the JVM doesn't even know what you mean by "T".
Kotlin uses a clever trick to come around this limitation in some cases. When you are using inline functions, the compiler does not call the function you defined, but instead, copies the whole body to the location where you called it. This can only be done for inline functions. Not classes.
There is a workaround tough: Just add private val classT: Class<T>
to the constructor and use the parameter instead!
Maybe it is too late but you could get the memory address from the generic class.
try to use:
object: GenericTypeIndicator<"T>() {}
to get the memory address from ur generic value.
It looks then so:
val gameDS = dataSnapshot.getValue(object: GenericTypeIndicator<"T">(){}
But you need to give your genericType without the ""
Maybe it is a solution for you.
I have the code below. It is Kotlin. Any idea why textToSpeech from textToSpeech.setLanguage(Locale.UK) is telling that there is no reference resolved for textToSpeech?
val textToSpeech = TextToSpeech(
applicationContext,
object : TextToSpeech.OnInitListener {
override fun onInit(status: Int) {
if (status == TextToSpeech.SUCCESS) {
textToSpeech.setLanguage(Locale.UK)
}
}
})
At first I assumed it is an Idea kotlin plugin bug, but it seems that it actually can't be compiled
Kotlin has hardened the variables initialization policy, and it's now prohibited to reference the variable inside its initializer, even in lambdas and object expressions, which seems reasonable: imagine that a lambda is called immediately before the variable assignment.
For your case, I can suggest as a workaround using an object expression in this quite cumbersome construct:
val textToSpeech = object {
val value: TextToSpeech get() = inner
private val inner = TextToSpeech(
applicationContext,
{ value.setLanguage(Locale.UK) }
)
}.value
This will initialize an anonymous object with inner inside that is acceptable through value property. Note that the inner initializer uses value property. Then the value is extracted and can be used.
But please keep in mind that this trick is unsafe: in runtime, using value before inner is assigned (e.g. in TextToSpeech constructor) will throw NullPointerException.
Also, I've replaced the OnInitListener with a lambda using SAM conversion to be short, but object expression can still be used there.
UPD: check this question for my effort to generalize this approach. Using it, you can write
val textToSpeech = selfReference {
TextToSpeech(
applicationContext,
{ self.setLanguage(Locale.UK) }
)
}
See the sources on Github.
This is a very readable and clear way to face that problem. First you should define this:
fun <T> selfReferenced(initializer: () -> T) = initializer.invoke()
operator fun<T> T.getValue(any: Any?, property: KProperty<*>) = this
and later use
val valueName: ValueType by selfReferenced{
//here you can create and use the valueName object
}
Using your problem as example you can do:
val textToSpeech:TextToSpeech by selfReferenced {
TextToSpeech(
applicationContext,
TextToSpeech.OnInitListener { status ->
if (status == TextToSpeech.SUCCESS) {
textToSpeech.setLanguage(Locale.UK)
}
})
}
Inside the selfReferenced block you can use the outer object with no restrictions. The only thing you should take care of, is declaring the type explicitly to avoid recursive type checking issues.