Android Studio Arctic Fox (Patch 3) flags, "The value true assigned to var isVisited: Boolean in the following composable is never used":
#Composable
fun MainView(navController: NavController) {
var isVisited by rememberSaveable { mutableStateOf(false) }
if (!isVisited) {
isVisited = true // never used?
navController.navigate("NextView")
}
Button(onClick = { navController.navigate("NextView") }) {
Text(text = "MainView")
}
}
while it is clearly working as intended: to prevent MainView from navigating to NextView on subsequent visits to MainView. Here's the definition of NextView:
#Composable
fun NextView(navController: NavController) {
Button(onClick = { navController.popBackStack() }) {
Text(text = "NextView")
}
}
Is Android Studio simply not recognizing variable usage across recompositions? Or is there a more idiomatic way to execute code conditionally upon recompositions? Thanks for any answer or pointer.
I'm building for API Level 31 with Kotlin 1.5.31, Compose 1.1.0-alpha06, navigation-compose 2.4.0-alpha04, lifecycle-runtime-ktx 2.4.0-rc01, though I've seen the same behavior on API Level 30, Kotlin 1.5.21, Compose 1.0.1, navigation-compose 2.4.0-alpha04, lifecycle-runtime-ktx 2.3.1. (I'd be happy to share my MainActivity where I set up NavHost with these two views or other dependency and system info if helpful.)
2022 ANSWER:
The studio code highlighter was designed with the traditional programming model in mind, i.e., the non-declarative View System. In that, a modern Composable would be treated as just a regular function. Now inside that function, you are creating a variable, reading it in a conditional, then updating its value. Then that value is never accessed in the scope of that function, and hence the warning "The updated value is never used".
However, we being Compose-Oriented now, know that it is a declarative paradigm and that recompositions are a thing, by the virtue of which, the function would be called over and over again and hence the updated value will, in fact, be used within that same conditional, which is why the rememberSaveable block is there to begin with. The same goes for the compiler, since you never "type the name of the variable" in the function scope after updating it, looking from the traditional way, the value is never used again, so the update is pointless to the compiler (as well as the highlighter), and hence the warning. It only makes sense to ignore it, but if you are really bugged by it, just type the name of the variable right after the whole loop, it should go away. God I was so dumb a year ago...
WRITTEN BY 2021 ME; NOT AS RELIABLE. FEEL FREE TO IGNORE
TOP OF THE LINE: You can safely ignore that warning, because the purpose of warnings is to prevent developers from using unnecessary system resources. Because of some internal highlighter logic error (perhaps?), it identifies a useful variable as an unused one, but since you know you are actively using it in code flow, you can just ditch that warning. Just suppress it with an annotation if it bugs you.
BODY CONTENT:-
Don't bother, it happens at times. There seems no error to me, just try deleting the line, and then re-adding it while using as much code completion as you can. If that does not help, just compile the project, and see the build log. If it does not show the warning: var 'isVisited' is never used, then you can relax about it, since it would then be a bug in the studio's code highlighter. Long as you don't receive any build time warnings, be sure that it does not pose any threat pertaining to performance or whatsoever.
Related
Android Studio 4.2.2 evaluates a local and global variable, but doesn't evaluate parameter funcion when is inside a inner function.
Until the previous version this worked perfectly.
fun a(p:param) {
fun b(){
var v = p+1 // Here
}
}
Suppose that one try to evaluate the parameter p in the line with comment
// Here with Alt F8
The message in evaluate window is
Cannot find the local variable 'p' with type
This hurts a lot because it forces you to replicate the parameter as a local variable in each routine to be visible in the debugger.
var p = p
Has anyone noticed this? Is there any workaround?
Notice that Variables windows display a parameter with $ prefix, but it also doesn't work in evaluate window.
I've posted this issue in JetBrains.
First things first:
Is there any workaround?
Yes, bind the parameter p to a local variable inside b:
fun a(p: Param) {
fun b() {
val p = p
var v = p + 1
}
}
and it will all work as expected.
The root cause is slightly more convoluted.
Kotlin grew up on a language tool chain tied very closely to the IntelliJ language plug-in for Kotlin. The original JVM compiler was a code generator that consumed the data structures of semantic information used by the IntelliJ language plug-in for diagnostics, quick fixes, and so on.
This architecture was very good for providing language support to an IDE, but less so for building a fast batch compiler.
With Kotlin 1.5, the "IR backend" was enabled as the default code generator for JVM. It uses a more traditional compilation approach of gradually translating an abstract syntax tree (AST) to progressively simpler intermediate languages before outputting JVM byte code.
With this change, a number of new compilation strategies were implemented. In particular, in the old strategy, local functions were treated as lambdas bound to a local variable. Their free variables were recorded in the state of the allocated Function object at the declaration site. When the local function is called, it translates to a call to invoke on that function object. End of story.
In the new approach, local functions are lifted into private static functions on the same class as the outer function. Free variables in the local function are closed by parameters to that lifted function, and instead of recording them at the declaration site in a lambda object, they are passed at the call site as arguments.
In your example. the p is free in the inner function b, so an additional parameter $p is added to b.
While this works for "release builds", the surrounding tooling has not caught up until recently. The "Evaluate Expression..." mechanism has been hit particularly hard, as it's quite sensitive to the layout and shape of the resulting JVM byte code.
In this specific case, it was a matter of adjusting the mechanism in the debugger that maps free variables of the fragment to local variables at the breakpoint. With this change aimed at 2022.3, you should hopefully stop noticing this specific bug, and a host of other improvements as a new and revised version of the evaluation mechanism ships.
as I mentioned in the title, I'm curious about the general differences between the two. Can you help with this? I couldn't find the specific differences as there are complex examples on the internet.
What are the differences in terms of performance?
In which scenarios does it provide advantages?
Using StateFlow with Kotlin Flow is advantageous. But what is the risk of not switching to StateFlow in a project using LiveData?
Is Google deprecating LiveData? :)
I just switched to StateFlow, so this is a great time for me to answer your question.
What are the differences in terms of performance?
Honestly, I don't know, but since it's pushed by Kotlin and Android, just trust them :).
In which scenarios does it provide advantages?
For LiveData you are not forced to give an initial value, it may end up writing more code in init{}; But for StateFlow you are Forced to give an initial value (including null), it may save your code a bit.
For LiveData even if you give an initial value, you still need to do Null Check when you access its value (see this), it's kind of annoying. But that's not gonna happen on StateFlow - it will be what it should be.
For LiveData you cannot easily, or elegantly observe data changes JUST inside ViewModel, you are gona use observeForever() which is also mentioned in here. But for StateFlow it's easy, do it like following:
class FirstViewModel() : ViewModel() {
val uiScope = viewModelScope
val name = MutableStateFlow("Sam") //must have initial value
//val name = MutableStateFlow<String?>(null) //null is acceptable
init {
observeName()
}
private fun observeName() = uiScope.launch { //must run in coroutine scope
name.collect { name -> //for Fragment / Activity, use lifecycleScope.launch{}
//do your stuff
}
}
}
Using StateFlow with Kotlin Flow is advantageous. But what is the risk of not switching to StateFlow in a project using LiveData?
What is the risk of not switching to Kotlin in a project using Java? :)
Is Google deprecating LiveData?
I would say yes, and they would say no, no for "not yet to say it loudly" :).
I'm trying to migrate my MVP application from Rx to Kotlin's coroutines (which I'm new to).
As I was running some trials, I found that any code with the following structure fails to compile with org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: wrong bytecode generated
val scope = CoroutineScope(Dispatchers.IO)
fun a(i: Int) {
scope.launch {
withContext(Dispatchers.Main) {
val b = i + 1
}
}
}
It appears that trying to access the parameter i inside the withContext is the problem. If I assign the value of i to something else inside the function block and use that instead, it works alright. But I have the feeling I might be doing something extra wrong here hehe
This lives in a Presenter. My idea is to use "launch" with the IO dispatcher to call the repository and get some data from the database, then use it to update the UI in the Main dispatcher. It looks solid to me, but I'm a bit worried because apparently no one else is running into that same issue, which might mean this pattern I'm trying to implement should be avoided for some reason.
Any clues?
After some fiddling I found that the problem was with my kotlin plugin version.
I changed it from 1.3.50 to 1.3.72 and... magic :D
Based on the release notes in version 0.8.3 the not-null assertion operator is filtered out, I'm using Jacoco version 0.8.5 like this:
jacoco {
toolVersion = "0.8.5"
}
But it's telling me that Not covered by tests (8 conditions)
I'm using com.dicedmelon.gradle:jacoco-android Github link
I think toolVersion = "0.8.5" not working or something like that, so for that I need a way to force Jacoco version.
Is there any way to fix this issue?
Without seeing your code and your tests I can't say with 100% confidence but it looks like Jacoco is working fine and there are cases not covered there.
You're using the !! 3 times. When you use this operator, in reality, you're creating 2 flows, for when the variable is null and another for not-null. If you add tests for the cases where the variables are null, you should reach 100% coverage.
Just to make explicit, if you would handle your nullable with safe calls, you would have something like this:
val token = authResult.user?.let {
authenticationDataSource.getIdToken(true, it)
?.let { it.token }
?: throw GetIdTokenResultIsNullException()
} ?: throw UserIsNullException()
token?.let {
authenticationDataSource.loginBy(AuthenticationPayload(it))
} ?: throw TokenIsNullException()
Wherever, I'm throwing exceptions you should handle that case as desired, and this is the alternate branch that is created by the nullability.
If you're sure that your values won't be null then you should change your types to make it clear and avoid extra checks.
On a side note, jacoco-android doesn't seem to be maintained anymore here and it's not compatible with newer gradle versions, so I would recommend using Jacoco directly. Migrating from jacoco-android to jacoco shouldn't be that hard.
I'm trying to learn the Arrow library and improve my functional programming by transitioning some of my Android Kotlin code from more imperative style to functional style. I've been doing a type of MVI programming in the application to make testing simpler.
"Traditional" Method
ViewModel
My view model has a LiveData of the view's state plus a public method to pass user interactions from the view to the viewmodel so the view model can update state in whatever way is appropriate.
class MyViewModel: ViewModel() {
val state = MutableLiveData(MyViewState()) // MyViewState is a data class with relevant data
fun instruct(intent: MyIntent) { // MyIntent is a sealed class of data classes representing user interactions
return when(intent) {
is FirstIntent -> return viewModelScope.launch(Dispatchers.IO) {
val result = myRoomRepository.suspendFunctionManipulatingDatabase(intent.myVal)
updateStateWithResult(result)
}.run { Unit }
is SecondIntent -> return updateStateWithResult(intent.myVal)
}
}
}
Activity
The Activity subscribes to the LiveData and, on changes to state, it runs a render function using the state. The activity also passes user interactions to the view model as intents (not to be confused with Android's Intent class).
class MyActivity: AppCompatActivity() {
private val viewModel = MyViewModel()
override fun onCreateView() {
viewModel.state.observe(this, Observer { render(it) })
myWidget.onClickObserver = {
viewModel.instruct(someIntent)
}
}
private fun render(state: MyViewState) { /* update view with state */ }
}
Arrow.IO Functional Programming
I'm having trouble finding examples that aren't way over my head using Arrow's IO monad to make impure functions with side effects obvious and unit-testable.
View Model
So far I have turned my view model into:
class MyViewModel: ViewModel() {
// ...
fun instruct(intent: MyIntent): IO<Unit> {
return when(intent) {
is FirstIntent -> IO.fx {
val (result) = effect { myRoomRepository.suspendFunctionManipulatingDatabase(intent.myVal) }
updateStateWithResult(result)
}
is SecondIntent -> IO { updateStateWithResult(intent.myVal) }
}
}
}
I do not know how I am supposed to make this IO stuff run in Dispatcher.IO like I've been doing with viewModelScope.launch. I can't find an example for how to do this with Arrow. The ones that make API calls all seem to be something other than Android apps, so there is no guidance about Android UI vs IO threads.
View model unit test
Now, because one benefit I'm seeing to this is that when I write my view model's unit tests, I can have a test. If I mock the repository in order to check whether suspendFunctionManipulatingDatabase is called with the expected parameter.
#Test
fun myTest() {
val result: IO<Unit> = viewModel.instruct(someIntent)
result.unsafeRunSync()
// verify suspendFunctionManipulatingDatabase argument was as expected
}
Activity
I do not know how to incorporate the above into my Activity.
class MyActivity: AppCompatActivity() {
private val viewModel = MyViewModel()
override fun onCreateView() {
viewModel.state.observe(this, Observer { render(it) })
myWidget.onClickObserver = {
viewModel.instruct(someIntent).unsafeRunSync() // Is this how I should do it?
}
}
// ...
}
My understanding is anything in an IO block does not run right away (i.e., it's lazy). You have to call attempt() or unsafeRunSync() to get the contents to be evaluated.
Calling viewModel.instruct from Activity means I need to create some scope and invoke in Dispatchers.IO right? Is this Bad(TM)? I was able to confine coroutines completely to the view model using the "traditional" method.
Where do I incorporate Dispatchers.IO to replicate what I did with viewModelScope.launch(Dispatchers.IO)?
Is this the way you're supposed to structure a unit test when using Arrow's IO?
That's a really good post to read indeed. I'd also recommend digging into this sample app I wrote that is using ArrowFx also.
https://github.com/JorgeCastilloPrz/ArrowAndroidSamples
Note how we build the complete program using fx and returning Kind at all levels in our architecture. That makes the code polymorphic to the type F, so you can run it using different runtime data types for F at will, depending on the environment. In this case we end up running it using IO at the edges. That's the activity in this case, but could also be the application class or a fragment. Think about this as what'd be the entry points to your apps. If we were talking about jvm programs the equivalent would be main(). This is just an example of how to write polymorphic programs, but you could use IO.fx instead and return IO everywhere, if you want to stay simpler.
Note how we use continueOn() in the data source inside the fx block to leave and come back to the main thread. Coroutine context changes are explicit in ArrowFx, so the computation jumps to the passed thread right after the continueOn until you deliberately switch again to a different one. That intentionally makes thread changes explicit.
You could inject those dispatchers to use different ones in tests. Hopefully I can provide examples of this soon in the repo, but you can probably imagine how this would look.
For the syntax on how to write tests note that your program will return Kind (if you go polymorphic) or IO, so you would unsafeRunSync it from tests (vs unsafeRunAsync or unsafeRunAsyncCancellable in production code since Android needs it to be asynchronous). That is because we want our test to be synchronous and also blocking (for the latter we need to inject the proper dispatchers).
Current caveats: The solution proposed in the repo still doesn't care of cancellation, lifecycle or surviving config changes. That's something I'd like to address soon. Using ViewModels with a hybrid style might have a chance. This is Android so I'd not fear hybrid styles if that brings better productivity. Another alternative I've got in mind would maybe be something a bit more functional. ViewModels end up retaining themselves using the retain config state existing APIs under the hood by using the ViewModelStore. That ultimately sounds like a simple cache that is definitely a side effect and could be implemented wrapped into IO. I want to give a thought to this.
I would definitely also recommend reading the complete ArrowFx docs for better understanding: https://arrow-kt.io/docs/fx/ I think it would be helpful.
For more thoughts on approaches using Functional Programming and Arrow to Android you can take a look to my blog https://jorgecastillo.dev/ my plan is to write deep content around this starting 2020, since there's a lot of people interested.
In the other hand, you can find me or any other Arrow team maintainers in the Kotlinlang JetBrains Slack, where we could have more detailed conversations or try to resolve any doubts you can have https://kotlinlang.slack.com/
As a final clarification: Functional Programming is just a paradigm that resolves generic concerns like asynchrony, threading, concurrency, dependency injection, error handling, etc. Those problems can be found on any program, regardless of the platform. Even within an Android app. That is why FP is an option as valid for mobile as any other one, but we are still into explorations to provide the best APIs to fulfill the usual Android needs in a more ergonomic way. We are in the process of exploration in this sense, and 2020 is going to be a very promising year.
Hopefully this helped! Your thoughts seem to be well aligned with how things should work in this approach overall.