I'm very new to App development. I'm stuck on a piece of code that's telling me "The Expression is unused".
The App itself is written in Kotlin, it's meant to be a conversion app. I'm pretty sure I got the math right, since 1 foot comes out to 30.48cm, which is correct. But, whenever I write a number (in the app) greater than 1 foot, it still always comes to 30.48cm. An example being if I were to type 5foot 9 in the app, the answer would still be 30.48cm. Here is the two blocks of code I'm pretty sure one of them is the culprit.
The first one.
'calculateHeight' is the line thats giving me "The Expression is unused"
private fun calculateButton() {
val feetString: String = binding.editTextFeet.text.toString()
val inchesString: String = binding.editTextInches.text.toString()
val calculateHeight = calculateHeight()
if (feetString.isEmpty()) {
Toast.makeText(context, "Please select a foot value", Toast.LENGTH_SHORT).show()
} else {
calculateHeight
displayText()
}
if (inchesString.isEmpty()) {
Toast.makeText(context, "Please select a inch value", Toast.LENGTH_SHORT).show()
} else {
calculateHeight
displayText()
}
}
And the second one.
private fun calculateHeight(): Double {
val feetHint = binding.editTextFeet.toString()
val inchesHint = binding.editTextInches.toString()
var feet = 1
try {
feet = feetHint.toInt()
} catch (e: NumberFormatException) {
e.printStackTrace()
}
var inches = 0f
try {
inches = inchesHint.toInt().toFloat()
} catch (e: NumberFormatException) {
e.printStackTrace()
}
val totalFeet = feet * 12
val totalInches = inches + 1f
val heightInCentimeters = 2.54
return ((totalFeet * totalInches) * heightInCentimeters)
}
}
Edit
This is the displayText():
private fun displayText() {
val dec = DecimalFormat(".##")
val resultString: String = dec.format(calculateHeight())
binding.textViewCm.text = "$resultString - Centimeters"
}
val calculateHeight = calculateHeight()
This line is calling the calculateHeight() function. That function reads the contents of your fields at that moment in time and performs calculations upon their contents. The result of the calculateHeight() function is then stored in the variable, itself confusingly named calculateHeight.
You then do not do anything meaningful with calculateHeight. You reference that variable twice, in statements that then do not do anything:
calculateHeight
Those lines will give you "expression is unused", because the expression (calculateHeight) is unused. You are not doing anything with it.
it still always comes to 30.48cm
You do not state where and how you are seeing any results. If I had to guess, displayText() is supposed to something like that, given the name of that function. Your question does not include the source for displayText(), so we are having to guess.
But, it is unclear where displayText() is getting anything to display. You are not passing any parameters to displayText(). Perhaps you should have displayText(calculateHeight), and have your displayText() function take that parameter and do something with it. Or, perhaps displayText() should be calling calculateHeight() directly, and you can remove all the calculateHeight()/calculateHeight stuff from your calculateButton() function.
Related
I am using Health Connect to read records, like steps and exercises. I use Health Connect in a few different places in Kotlin, and the code generally looks something like:
suspend fun fetchStepData(
healthConnectClient: HealthConnectClient,
viewModel: StepViewViewModel,
): StepViewViewModel {
kotlin.runCatching {
val todayStart = Instant.now().atZone(ZoneId.systemDefault()).toLocalDate().atStartOfDay();
val response: ReadRecordsResponse<StepsRecord>
try {
response = healthConnectClient.readRecords(
ReadRecordsRequest(
StepsRecord::class,
timeRangeFilter = TimeRangeFilter.after(todayStart)
)
)
var steps: Long = 0;
if (response.records.isNotEmpty()) {
for (stepRecord in response.records) {
steps += stepRecord.count
}
}
return viewModel
} catch (e: Exception) {
Log.e("StepUtil", "Unhandled exception, ", e)
}
}
return viewModel
}
I have an update function that is run when focus changes to ensure that the app is in the foreground.
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
binding.root.invalidate()
val fragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_main)?.childFragmentManager?.primaryNavigationFragment
if (fragment is MyFragment) {
displayLoadingIndicator(true)
runBlocking {
if (fragment.fetchStepData(this#MainActivity.healthConnectClient, fragment.getViewModel()) != null) {
displayLoadingIndicator(false)
}
}
}
I have a loading indicator present when I am fetching the data.
I use a drawer, and if I wait about 15 seconds and press the drawer button corresponding with MyFragment, the application hangs on the loading indicator, never successfully dismissing it.
I've tried stepping through the application in debug mode, and as I do, I always hang on
response = healthConnectClient.readRecords(
ReadRecordsRequest(
StepsRecord::class,
timeRangeFilter = TimeRangeFilter.after(todayStart)
)
)
in fetchStepData. I did at one point have my application making multiple requests for HealthConnectClient.getOrCreate(context), but I have since consolidated them to one instantiation call. I'm thinking I may be reading the data wrong and maybe I need to use getChanges, or maybe I'm being rate limited. Does anyone have any insight? Thanks in advance!
Let's say we have a Channel like this
private val channel = Channel<String>(1)
And we are listening to the Channel elements like this
channel.receiveAsFlow().collect { myStr ->
println(myStr)
}
If I run something like this
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
...
fun sendMessage(myMessage: String) {
scope.launch {
channel.send(myMessage)
}
}
...
sendMessage("a")
sendMessage("b")
sendMessage("c")
sendMessage("d")
The output is going to be
a
b
c
d
Now, what I'm trying to achieve is that if I send "b" it delays the processing of the elements in the channel for 1 second.
For example, if I do
...
sendMessage("a")
sendMessage("b")
sendMessage("c")
sendMessage("b")
sendMessage("d")
sendMessage("e")
The output that I would expect would be like
a // prints immediately
b // prints right after a
c // prints after 1 second
b // prints right after c
d // prints after 1 second
e // prints right after d
My question is, how would I achieve this behavior? I've been trying to add delay() here and there, but I didn't have any luck.
Here's an idea, but it feels a little hacky to me. trySend would not work with this. I'm not sure how to make trySend make sense with your criteria, because it's supposed to return immediately with a result about whether the value posted.
Here, send() suspends until the possible delay is over. If you don't want to wait for it, you'd have to launch a coroutine each time you send something.
Since the Channel "constructor" is not a true constructor, you can't subclass it. My workaround is to create a class that uses it as a delegate.
val backingChannel = Channel<String>(1)
val channel = object: Channel<String> by backingChannel {
var delayNext = false
val mutex = Mutex()
override suspend fun send(element: String) = mutex.withLock {
if (delayNext) {
delay(1000)
}
delayNext = element == "b"
backingChannel.send(element)
}
}
I wanted to make a method that determine if the application is started for the very first time, no matter the current version of the application. People suggest that we should use SharedPreferences as seen from this qustion. Below is the function that determine if application is started for the very first time.
companion object {
const val APP_LAUNCH_FIRST_TIME: Int = 0 // first start ever
const val APP_LAUNCH_FIRST_TIME_VERSION: Int = 1 // first start in this version (when app is updated)
const val APP_LAUNCH_NORMAL: Int = 2 // normal app start
/**
* Method that checks if the application is started for the very first time, or for the first time
* of the updated version, or just normal start.
*/
fun checkForFirstAppStart(context: Context): Int {
val sharedPreferencesVersionTag = "last_app_version"
val sharedPreferences = androidx.preference.PreferenceManager.getDefaultSharedPreferences(context)
var appStart = APP_LAUNCH_NORMAL
try {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
val lastVersionCode = sharedPreferences.getLong(sharedPreferencesVersionTag, -1L)
val currentVersionCode = PackageInfoCompat.getLongVersionCode(packageInfo)
appStart = when {
lastVersionCode == -1L -> APP_LAUNCH_FIRST_TIME
lastVersionCode < currentVersionCode -> APP_LAUNCH_FIRST_TIME_VERSION
lastVersionCode > currentVersionCode -> APP_LAUNCH_NORMAL
else -> APP_LAUNCH_NORMAL
}
// Update version in preferences
sharedPreferences.edit().putLong(sharedPreferencesVersionTag, currentVersionCode).commit()
} catch (e: PackageManager.NameNotFoundException) {
// Unable to determine current app version from package manager. Defensively assuming normal app start
}
return appStart
}
}
Now in my MainActivity I make the check in this way, but strangely enough I always end up inside the if statement, although appLaunch is different from MainActivityHelper.APP_LAUNCH_FIRST_TIME
val appLaunch = MainActivityHelper.checkForFirstAppStart(this)
if (appLaunch == MainActivityHelper.APP_LAUNCH_FIRST_TIME) {
val c = 299_792_458L
}
Here we see that appLaunch is 2
Here we see that MainActivityHelper.APP_LAUNCH_FIRST_TIME is 0
I am in the main thread I check using Thread.currentThread(), and when I add watches in the debugger (appLaunch == MainActivityHelper.APP_LAUNCH_FIRST_TIME) I get false.
So I suggest that there is some delay, and by the time the if check is made the result is changed?
There's nothing wrong with the code. I tested it and it works as intended. I get all three return values depending on the circumstances. I simplified the code a bit but the original code should nevertheless works.
enum class AppLaunch {
LAUNCH_FIRST_TIME, // first start ever
FIRST_TIME_VERSION, // first start in this version (when app is updated)
NORMAL // normal app start
}
/**
* Method that checks if the application is started for the very first time, or for the first time
* of the updated version, or just normal start.
*/
fun checkForFirstAppStart(context: Context): AppLaunch {
val sharedPreferencesVersionTag = "last_app_version"
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
return try {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
val lastVersionCode = sharedPreferences.getLong(sharedPreferencesVersionTag, -1L)
val currentVersionCode = PackageInfoCompat.getLongVersionCode(packageInfo)
// Update version in preferences
sharedPreferences.edit().putLong(sharedPreferencesVersionTag, currentVersionCode).commit()
when (lastVersionCode) {
-1L -> AppLaunch.LAUNCH_FIRST_TIME
in 0L until currentVersionCode -> AppLaunch.FIRST_TIME_VERSION
else -> AppLaunch.NORMAL
}
} catch (e: PackageManager.NameNotFoundException) {
// Unable to determine current app version from package manager. Defensively assuming normal app start
AppLaunch.NORMAL
}
}
I experimented a bit and the issue you see looks like a bug in Android Studio. If the code in the if statement is a NOP (no operation) then the debugger seems to stop there. If the code does have a side effect, the debugger doesn't stop.
Things like this can be infuriating but with Android, Android Studio and the tooling, bugs like this are pretty common (unfortunately).
if (appLaunch == APP_LAUNCH_FIRST_TIME) {
val c = 299_792_458L
}
translates to the following byte code:
L3 (the if statement)
LINENUMBER 32 L3
ILOAD 4
IFNE L4
L5
LINENUMBER 33 L5
LDC 299792458
LSTORE 2
Converting c to a var
var c = 1L
if (appLaunch == APP_LAUNCH_FIRST_TIME) {
c = 299_792_458L
}
results in identical byte code so it's certainly not a code problem but an issue with Android Studio.
Update
If you need fast writes with enums you can use something like this:
fun appLaunchById(id: Int, def: AppLaunch = AppLaunch.NORMAL) = AppLaunch.values().find { it.id == id } ?: def
enum class AppLaunch(val id: Int) {
LAUNCH_FIRST_TIME(0), // first start ever
FIRST_TIME_VERSION(1), // first start in this version (when app is updated)
NORMAL(2); // normal app start
}
^^^ writes an Int so fast and short. Reading is certainly not super fast though.
Update 2
Generic version of the enum solution:
inline fun <reified T : Enum<*>> enumById(hash: Int, def: T) = enumValues<T>()
.find { it.hashCode() == hash }
?: def
enum class AppLaunch {
LAUNCH_FIRST_TIME, // first start ever
FIRST_TIME_VERSION, // first start in this version (when app is updated)
NORMAL // normal app start
}
Usage:
val enum = enumById(value.hashCode(), AppLaunch.NORMAL)
I have an app with a splashscreen, which stays for about 2 seconds.
After that, it switches to another activity A.
In A, I set a value in a SeekBar and after that, click a Button to confirm.
When I simply start a recorded Espresso test doing this, it tries to play while on the splashscreen. So when it tried to set the SeekBar value or click the Button, I get a NoMatchingViewException. So my first attempt at fixing this was to simply add a sleep(5000). This worked.
However, I don't want to put a manual sleep in after every Activity switch.
Because it seems like unnecessary code
Because it would mean unnecessary waiting time for running the test
The timing might be arbitrary and could be different for different devices
So I tried to check whether or not I'm in the right Activity/can see the right views. I did this using some SO links: Wait for Activity and Wait for View.
However, even that does not work 100%.
I have these two functions:
fun <T: AppCompatActivity> waitForActivity(activity: Class<T>, timeout: Int = 5000, waitTime: Int = 100) {
val maxTries = timeout / waitTime
var tries = 0
for(i in 0..maxTries) {
var currentActivity: Activity? = null
getInstrumentation().runOnMainSync { run { currentActivity = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(
Stage.RESUMED).elementAtOrNull(0) } }
if(activity.isInstance(currentActivity)) {
break
} else {
tries++
sleep(waitTime.toLong())
}
}
}
fun waitForView(
#IntegerRes id: Int,
waitMillis: Int = 5000,
waitMillisPerTry: Long = 100
): ViewInteraction {
// Derive the max tries
val viewMatcher = allOf(
withId(id),
isDisplayed()
)
val maxTries = waitMillis / waitMillisPerTry.toInt()
var tries = 0
for (i in 0..maxTries)
try {
tries++
val element = onView(viewMatcher)
element.check { view, noViewFoundException ->
if(view == null) {
throw noViewFoundException ?: Exception("TEST")
}
if(view.hasWindowFocus()) {
throw noViewFoundException ?: Exception("TEST2")
}
}
return element
} catch (e: Exception) {
if (tries == maxTries) {
throw e
}
sleep(waitMillisPerTry)
}
throw Exception("Error finding a view matching $viewMatcher")
}
Neither of those work 100%. Both of them seem to return within the timeout restrictions, and have "found" the activity/view. However, the expected view, e.g. a Button is not yet ready to perform, for example, element.perform(click()). It does not lead to a NoMatchingViewException, but it does not perform the click I did either. For the SeekBar, I use this:
private fun setSeekValue(seekBar: ViewInteraction, age: Int) {
val fullPercentage = .9f
val step = 1/99f
seekBar.perform(
GeneralClickAction(
Tap.SINGLE,
CoordinatesProvider { view ->
val pos = IntArray(2)
view?.getLocationOnScreen(pos)
FloatArray(2).apply {
this[0] = pos[0] + view!!.width * (.05f + fullPercentage*step*age)
this[1] = pos[1] + view.height * .5f
}
},
PrecisionDescriber {
FloatArray(2).apply {
this[0] = .1f
this[1] = 1f
}
},
InputDevice.SOURCE_MOUSE,
MotionEvent.ACTION_BUTTON_PRESS
)
)
}
However, when I use these functions and just put a very short sleep, e.g. sleep(100) after it, it works. This again however, would go against the three reasons listed above, which I'm trying to avoid here.
As you can see in the function waitForView, I tried to check if the View is "usable", using hasWindowFocus(). But this still does not perform the click, except for when I again put a sleep(80) or something after it. So it waits for the splashscreen to switch to A, finds the view it's looking for and then can't perform the click, except for when I wait a little bit.
I have also tried these functions of View:
isEnabled
isShown
visibility
getDrawingRect
isFocusable
isFocused
isLayoutDirectionResolved
Neither of them worked as I expected. With all of them, after the needed value was returned on the element.check part of waitForView, they would still not be accessible without putting a short sleep after.
Is there a way to reliably check if I can perform a click on a view/safely can perform ViewInteraction.perform()
Either by checking, if an activity is fully loaded to a point where its views are usable. Or by directly checking if a view is usable.
I am replacing all the null checks with let block in my code as part of a code review process.
1.
Code example with null check:
if(someValue != null){//run if someValue is not null}
else {//run if someValue is null}
2.
Code base after using let-run block if for null check,
var someValue : String? = null
someValue = "SOF"
someValue?.let {safeSomeValue->
//This code block will run only if someValue is not null
}?.run {
//This code block should run only when if someValue is null, like else condition
}
Now problem with let-run block is that both the code blocks are running even if the someValue is not null. So i am not able to replicate behaviour of if-else condition in code example 1 to run-let condition in code example 2.
Expected behaviour is to execute either let or run code block based upon if value is null or not-null.
Source - kotlinlang.org
?. performs a safe call (calls a method or accesses a property if the receiver is non-null)
?: takes the right-hand value if the left-hand value is null (the elvis operator)
Change ?. with ?: will solve this issue,
Code base as following, will run either let or run block based upon the null check.
var someValue : String? = null
someValue = "SOF"
someValue?.also {safeSomeValue->
//This code block will run only if someValue is not null
}?:run {
//This code block will run only if someValue is null, like else condition
}
I am replacing all the null check with let block in my code
The first question here is, why? Is this somehow more readable for you than the regular if-else structure? I'd be generally wary of refactoring just for refactoring's sake.
The second consideration is much more important: this conversion you're doing is not equivalent to the original code, you're actually modifying the behavior with this change. Take the following piece of code:
var someValue : String? = null
someValue = "SOF"
someValue?.let {safeSomeValue->
foo(someSafeValue)
bar(someSafeValue)
} ?: run {
println("shouldn't happen if someValue != null")
}
You expect the run block to execute only if someValue == null, but that's actually not the only case when it will run. The entire someValue?.let { ... } expression can produce null values not just when someValue itself was null, but also if the block passed to let returned null. In this case, if the bar() function call results in null, the run statement will be executed afterwards, therefore running both branches of what you thought was a fancied up if-else statement.
To give a very concrete example of what zsmb13's answer talks about:
val someValue = 0
someValue?.let { safeSomeValue->
println("then")
null
} ?: run {
println("else")
}
prints both then and else. You can fix this by using also instead of let:
val someValue = 0
someValue?.also { safeSomeValue->
println("then")
null
} ?: run {
println("else")
}
will only print then. It may be useful to read https://kotlinlang.org/docs/reference/scope-functions.html and figure out why, and to prove it's indeed always equivalent to the original if ... else. But I also agree with zsmb13 that it's probably a bad idea.
You can create an extension function, like this
fun <T> T?.whenNull(block: () -> Unit) = this ?: block()
then you call it like this
somevalue?.let { safeSomeValue ->
// TODO
}.whenNull {
// execute when someValue is null
}
You can write two extension functions, eg: "ifSafe" and "ifNull", that can be used individually or chained together to mimic the if/else pattern:
fun <T> T?.ifSafe(block: (t:T?) -> Unit) = if (this!=null) block(this) else this
fun Any?.ifNull(block: () -> Unit?) = if (this==null) block().also{return null} else this
fun main() {
var someValue = 4
var nullValue = null
someValue.ifSafe { safeSomeValue ->
println("someValue: "+safeSomeValue.toString())
null
}.ifNull{
println("someValue is null.")
}
someValue.ifNull{
println("someValue is null.")
}.ifSafe{ safeSomeValue ->
println(safeSomeValue)
}
nullValue.ifNull{
println("nullValue is null.")
}
}
Sometimes you will find the following useful
inline fun <T> T?.itOrNull(
ifValue: (T) -> Unit,
ifNull: () -> Unit
): Unit = when (this) {
null -> ifNull()
else -> ifValue(this)
}
usage:
data.title?.itOrNull(
{ view.text = it },
{ view.visibility = View.GONE }
)