I have a codebase where all my data calculations and data formatting occur within a single function. Are there any performance benefits to separating these out into separate functions or does separating them only improve readability?
I know that I should separate them but I don't really know all the reasons why.
Here is the function I am referring to:
private fun processData(data: ByteArray) {
progressBar.visibility = GONE
Log.d(TAG, "displayDiagnosticData: ")
val bmsVersionView = findViewById<TextView>(R.id.textview_bms_version)
val boardVersionView = findViewById<TextView>(R.id.textview_board_version)
val cellOneView = findViewById<TextView>(R.id.textview_cell_1)
val cellTwoView = findViewById<TextView>(R.id.textview_cell_2)
val cellThreeView = findViewById<TextView>(R.id.textview_cell_3)
val cellFourView = findViewById<TextView>(R.id.textview_cell_4)
val cellFiveView = findViewById<TextView>(R.id.textview_cell_5)
val cellSixView = findViewById<TextView>(R.id.textview_cell_6)
val cellSevenView = findViewById<TextView>(R.id.textview_cell_7)
val cellEightView = findViewById<TextView>(R.id.textview_cell_8)
val cellNineView = findViewById<TextView>(R.id.textview_cell_9)
val cellTenView = findViewById<TextView>(R.id.textview_cell_10)
val cellElevenView = findViewById<TextView>(R.id.textview_cell_11)
val cellTwelveView = findViewById<TextView>(R.id.textview_cell_12)
val cellThirteenView = findViewById<TextView>(R.id.textview_cell_13)
val cellFourteenView = findViewById<TextView>(R.id.textview_cell_14)
val packTotalView = findViewById<TextView>(R.id.textview_diagnostic_voltage)
val packSocView = findViewById<TextView>(R.id.textview_diagnostic_soc)
val chargeTempView = findViewById<TextView>(R.id.textview_charge_temp)
val dischargeTempView = findViewById<TextView>(R.id.textview_discharge_temp)
val chargeCurrentView = findViewById<TextView>(R.id.textview_diagnostic_charge_current)
// val dischargeCurrentView = findViewById<TextView>(R.id.textview_diagnostic_discharge_current)
val dischargeCircuitStateView = findViewById<TextView>(R.id.textview_discharge_circuit)
val chargeCircuitStateView = findViewById<TextView>(R.id.textview_charge_circuit)
val balanceCircuitStateView = findViewById<TextView>(R.id.textview_balance_circuit)
val emptyCircuitStateView = findViewById<TextView>(R.id.textview_empty_circuit)
val bmsVersion = data[0] + (data[1] * 256)
val cellOne = data[2].toDouble() / 100 + 3.52
val cellTwo = data[3].toDouble() / 100 + 3.52
val cellThree = data[4].toDouble() / 100 + 3.52
val cellFour = data[5].toDouble() / 100 + 3.52
val cellFive = data[6].toDouble() / 100 + 3.52
val cellSix = data[7].toDouble() / 100 + 3.52
val cellSeven = data[8].toDouble() / 100 + 3.52
val cellEight = data[9].toDouble() / 100 + 3.52
val cellNine = data[10].toDouble() / 100 + 3.52
val cellTen = data[11].toDouble() / 100 + 3.52
val cellEleven = data[12].toDouble() / 100 + 3.52
val cellTwelve = data[13].toDouble() / 100 + 3.52
val cellThirteen = data[14].toDouble() / 100 + 3.52
val cellFourteen = data[15].toDouble() / 100 + 3.52
val totalVoltage = 47.8 + (data[16].toDouble() / 10)
val chargeTempCelsius = data[19]
val dischargeTempCelsius = data[20]
val chargeTempFahr = (chargeTempCelsius * 9.0 / 5.0) + 32.0
val dischargeTempFahr = (dischargeTempCelsius * 9.0 / 5.0) + 32.0
val chargeCurrent = data[21]
// val dischargeCurrent = (data[23].toDouble() * 100 + data[22]).toInt()
val chargeCircuitState = data[25].toInt()
val dischargeCircuitState = data[26].toInt()
val balanceCircuitState = data[27].toInt()
val emptyCircuitState = data[28].toInt()
val chargeCircuit: String = if (chargeCircuitState == 1) {
"On"
}else {
"Off"
}
val dischargeCircuit: String = if (dischargeCircuitState == 1) {
"On"
}else {
"Off"
}
val balanceCircuit: String = if (balanceCircuitState == 1) {
"On"
}else {
"Off"
}
val emptyCircuit: String = if (emptyCircuitState == 1) {
"On"
}else {
"Off"
}
val bmsVersionString = SpannableString("BMS Version: $bmsVersion")
bmsVersionString.setSpan(StyleSpan(Typeface.BOLD), 0, 11, 0)
val boardVersionString = SpannableString("Board Version: 2.1")
boardVersionString.setSpan(StyleSpan(Typeface.BOLD), 0, 13, 0)
val cellOneString = SpannableString("Cell 1: %.2fV".format(cellOne))
cellOneString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellTwoString = SpannableString("Cell 2: %.2fV".format(cellTwo))
cellTwoString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellThreeString = SpannableString("Cell 3: %.2fV".format(cellThree))
cellThreeString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellFourString = SpannableString("Cell 4: %.2fV".format(cellFour))
cellFourString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellFiveString = SpannableString("Cell 5: %.2fV".format(cellFive))
cellFiveString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellSixString = SpannableString("Cell 6: %.2fV".format(cellSix))
cellSixString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellSevenString = SpannableString("Cell 7: %.2fV".format(cellSeven))
cellSevenString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellEightString = SpannableString("Cell 8: %.2fV".format(cellEight))
cellEightString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellNineString = SpannableString("Cell 9: %.2fV".format(cellNine))
cellNineString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellTenString = SpannableString("Cell 10: %.2fV".format(cellTen))
cellTenString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
val cellElevenString = SpannableString("Cell 11: %.2fV".format(cellEleven))
cellElevenString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
val cellTwelveString = SpannableString("Cell 12: %.2fV".format(cellTwelve))
cellTwelveString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
val cellThirteenString = SpannableString("Cell 13: %.2fV".format(cellThirteen))
cellThirteenString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
val cellFourteenString = SpannableString("Cell 14: %.2fV".format(cellFourteen))
cellFourteenString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
val packTotalString = SpannableString("Pack Total: %.1fV".format(totalVoltage))
packTotalString.setSpan(StyleSpan(Typeface.BOLD), 0, 10, 0)
val socString = SpannableString("SOC: ${data[17].toInt()}%")
socString.setSpan(StyleSpan(Typeface.BOLD), 0, 3, 0)
val chargeTempString = SpannableString("Charge Temp: ${chargeTempFahr.toInt()}" + "°F")
chargeTempString.setSpan(StyleSpan(Typeface.BOLD), 0, 11, 0)
val dischargeTempString = SpannableString("Discharge Temp: ${dischargeTempFahr.toInt()}" + "°F")
dischargeTempString.setSpan(StyleSpan(Typeface.BOLD), 0, 15, 0)
val chargeCurrentString = SpannableString("Charge Current: $chargeCurrent" + "A")
chargeCurrentString.setSpan(StyleSpan(Typeface.BOLD), 0, 14, 0)
// val dischargeCurrentString = "Discharge Current: $dischargeCurrent" + "A"
val chargeCircuitStateString = SpannableString("Charge Circuit: $chargeCircuit")
chargeCircuitStateString.setSpan(StyleSpan(Typeface.BOLD), 0, 14, 0)
val dischargeCircuitStateString = SpannableString("Discharge Circuit: $dischargeCircuit")
dischargeCircuitStateString.setSpan(StyleSpan(Typeface.BOLD), 0, 17, 0)
val balanceCircuitStateString = SpannableString("Balance Circuit: $balanceCircuit")
balanceCircuitStateString.setSpan(StyleSpan(Typeface.BOLD), 0, 15, 0)
val emptyCircuitStateString = SpannableString("Empty Circuit: $emptyCircuit")
emptyCircuitStateString.setSpan(StyleSpan(Typeface.BOLD), 0, 13, 0)
bmsVersionView.text = bmsVersionString
boardVersionView.text = boardVersionString
cellOneView.text = cellOneString
cellTwoView.text = cellTwoString
cellThreeView.text = cellThreeString
cellFourView.text = cellFourString
cellFiveView.text = cellFiveString
cellSixView.text = cellSixString
cellSevenView.text = cellSevenString
cellEightView.text = cellEightString
cellNineView.text = cellNineString
cellTenView.text = cellTenString
cellElevenView.text = cellElevenString
cellTwelveView.text = cellTwelveString
cellThirteenView.text = cellThirteenString
cellFourteenView.text = cellFourteenString
packTotalView.text = packTotalString
packSocView.text = socString
chargeTempView.text = chargeTempString
dischargeTempView.text = dischargeTempString
chargeCurrentView.text = chargeCurrentString
// dischargeCurrentView.text = dischargeCurrentString
chargeCircuitStateView.text = chargeCircuitStateString
dischargeCircuitStateView.text = dischargeCircuitStateString
balanceCircuitStateView.text = balanceCircuitStateString
emptyCircuitStateView.text = emptyCircuitStateString
}
Performance? No. Maintainability? Yes. Separation of concerns is one of the core tenets of modern software architecture. Combining the two things makes a difficult to read, difficult to debug mess. Separating them allows you to concentrate on one thing at a time, and makes it easier for people maintaining the code (which may even be you 6 months from now when fixing a bug and you've forgotten how it works) to understand the logic and flow of the program.
That function you posted would not be accepted in any professional codebase. It's too long, it does too many things. It needs to be broken up.
Your bigger problem there is that you're repeating yourself, a lot. Not only does that make the whole thing longer, and arguably harder to read, it also makes it hard to maintain like Gabe says, and makes it far more likely a bug will sneak in there. Imagine you needed to add another row of cells - there's a lot of boilerplate involved, a lot of repetitive work, and that's where humans tend to mess up
Like as an example of the kind of thing you can do - see how your cell data is basically taken from a range of values in data, with the same calculation applied to each? You could do this instead:
val cells = (2..15).map { index -> data[index].toDouble() / 100 + 3.52 }
or, to keep things more explicit and separate:
// Except give this a good name since it's doing something specific
// Because this is some kind of conversion formula, putting it in its own function
// makes it easy to maintain and document, and it's clear exactly what it is
fun getCellValue(dataValue: Int) = dataValue.toDouble() / 100 + 3.52
val cells = (2..15).map { index -> getCellValue(data[index]) }
Now you have one or two lines replacing 14 lines of initialisation code. It's also easier to make changes - if the format of data changes, you can easily change the range of indices to use, or the formula applied to each value. It's one place, instead of on each line, where you have to update each one and make sure you haven't made a typo or skipped one.
And when you have structured data like that, it can make your other code easier to work with too - because instead of needing to work with separate variables, you can work with indices and loop over things instead of writing each step out:
// no need for a separate line for each with hardcoded values if you can work it out
// (Because it's a separate function, you can use it for the other display lines too,
// it's not cell-specific)
fun SpannableString.applyBoldPrefix() = apply {
val colonIndex = indexOf(':')
if (colonIndex >= 0) setSpan(StyleSpan(Typeface.BOLD), 0, colonIndex, 0)
}
// you could also just pass in the index and look up cells[index] here -
// this is a more functional approach, but whatever's good
fun getCellDisplayString(cellIndex: Int, cellData: Double) =
SpannableString("Cell ${cellIndex + 1}: %.2fV".format(cellData))
.applyBoldPrefix()
// lots of ways to define/grab a set of views programmatically - putting them all
// in a container you can look up is one way. You can also generate resource identifier
// strings, like "R.id.textview_cell_$i" and look that up
val cellTextViews = findViewById<ViewGroup>(R.id.containerHoldingCells)
.children.filterIsInstance<TextView>
// now you can just iterate over stuff to complete the repetitive task
cellTextViews.forEachIndexed { i, view ->
view.text = getCellDisplayString(i, cells[i])
}
That's about half your code right there. I wouldn't necessarily structure everything this way (I feel like given you're working with a data format here, a more formal structure definition would be helpful, and you can generalise more too) and it's a bit rough-and-ready, but hopefully it gives you a general sense of how you can cut things down, but also make it easier to maintain them and try out changes
I condensed my code a lot more and created functions to process separate chunks of data. I also was able to get rid of a lot of my repetitious code. For anyone who is interested, here is the updated code:
private fun String.withStyling() = SpannableString(this).apply {
setSpan(StyleSpan(Typeface.BOLD), 0, indexOfFirst { it == ':' }, 0)
}
private fun processDiagnosticData(data: ByteArray) {
binding.progressBarCyclic.visibility = GONE
Log.d(TAG, "displayDiagnosticData: ")
processCells(data)
processTemps(data[19], data[20])
processCircuits(data)
processOtherData(data)
}
// Process cells 1-14 and display.
private fun processCells(data: ByteArray) {
val cellViews = listOf(
binding.textviewCell1,
binding.textviewCell2,
binding.textviewCell3,
binding.textviewCell4,
binding.textviewCell5,
binding.textviewCell6,
binding.textviewCell7,
binding.textviewCell8,
binding.textviewCell9,
binding.textviewCell10,
binding.textviewCell11,
binding.textviewCell12,
binding.textviewCell13,
binding.textviewCell14
)
for ((i, cellView) in cellViews.withIndex()) {
val value = data[2 + i].toDouble() / 100 + 3.52
val cellNumberString = (i + 1).toString()
val formattedString = "Cell $cellNumberString: %.2fV".format(value).withStyling()
cellView.text = formattedString
}
}
// Process charge/discharge temps and display.
private fun processTemps(chargeTempCel: Byte, dischargeTempCel: Byte) {
val chargeTempFahr = chargeTempCel * 9.0 / 5.0 + 32.0
val dischargeTempFahr = dischargeTempCel * 9.0 / 5.0 + 32.0
val chargeTempString = "Charge Temp: $chargeTempFahr°F".withStyling()
val dischargeTempString = "Discharge Temp: $dischargeTempFahr°F".withStyling()
binding.textviewChargeTemp.text = chargeTempString
binding.textviewDischargeTemp.text = dischargeTempString
}
// Process circuit states and display.
private fun processCircuits(data: ByteArray) {
val circuitViews = listOf(
binding.textviewChargeCircuit,
binding.textviewDischargeCircuit,
binding.textviewBalanceCircuit,
binding.textviewEmptyCircuit
)
val circuitNames = listOf(
"Charge Circuit: ",
"Discharge Circuit: ",
"Balance Circuit: ",
"Empty Circuit: "
)
for ((i, circuit) in circuitViews.withIndex()) {
val value = if (data[25 + i].toInt() == 1) {
"On"
} else {
"Off"
}
val formattedString = (circuitNames[i] + value).withStyling()
circuit.text = formattedString
}
}
// Process the rest of the data and display.
private fun processOtherData(data: ByteArray) {
val totalVoltage = 47.8 + (data[16].toDouble() / 10)
val packSoc = data[17].toInt()
val chargeCurrent = data[21]
// val dischargeCurrent = (data[23].toDouble() * 100 + data[22]).toInt()
val bmsVersionString = "BMS Version: ${data[0] + (data[1] * 256)}".withStyling()
val boardVersionString = "Board Version: 2.1".withStyling()
val totalVoltageString = "Pack Total: %.1fV".format(totalVoltage).withStyling()
val packSocString = "SOC: ${packSoc}%".withStyling()
val chargeCurrentString = "Charge Current: ${chargeCurrent}A".withStyling()
// val dischargeCurrent = "Discharge Current: $dischargeCurrentA".withStyling()
binding.textviewBmsVersion.text = bmsVersionString
binding.textviewBoardVersion.text = boardVersionString
binding.textviewDiagnosticVoltage.text = totalVoltageString
binding.textviewDiagnosticSoc.text = packSocString
binding.textviewDiagnosticChargeCurrent.text = chargeCurrentString
// binding.textviewDiagnosticDischargeCurrent.text = dischargeCurrentString
}
Info
I have an Item class file as follows:
class Item(var color:String, var numValue:Int, var drawableID:Int){
init {
color = this.color
numValue = this.numValue
drawableID = this.drawableID
}
}
In the main code I create an array which contains 104 objects by default attributes:
var myItemClassArray = Array(104) { Item("", -99, -99) }
Also I have pictures in my drawable folder and I have their IDs in an array which is drawablesIDs:Array<Int>, and it contains 53 elements.
Problem
I want to assign my Item attributes as in this picture: https://i.stack.imgur.com/wFVsn.png I can do it for a similar problem (which has 106 objects and 53 drawables) with the code given in below:
for (i in 0 until 106) {
if (i < 13) {
myItemClassList[i+2].color = "kirmizi"
myItemClassList[i+2].numValue = i+1
myItemClassList[i+2].drawableID = drawablesIDs[i+1]
} else if (i in 13..25) {
myItemClassList[i+2].color = "siyah"
myItemClassList[i+2].numValue = (i+1)-13
myItemClassList[i+2].drawableID = drawablesIDs[i+1]
} else if (i in 26..38) {
myItemClassList[i+2].color = "yesil"
myItemClassList[i+2].numValue = (i+1)-26
myItemClassList[i+2].drawableID = drawablesIDs[i+1]
} else if (i in 39..51) {
myItemClassList[i+2].color = "mavi"
myItemClassList[i+2].numValue = (i+1)-39
myItemClassList[i+2].drawableID = drawablesIDs[i+1]
} else if (i in 52..64) {
myItemClassList[i+2].color = "kirmizi"
myItemClassList[i+2].numValue = (i+1)-52
myItemClassList[i+2].drawableID = drawablesIDs[(i+1)-52]
} else if (i in 65..77) {
myItemClassList[i+2].color = "siyah"
myItemClassList[i+2].numValue = (i+1)-65
myItemClassList[i+2].drawableID = drawablesIDs[i+1-65+13]
} else if (i in 78..90) {
myItemClassList[i+2].color = "yesil"
myItemClassList[i+2].numValue = (i+1)-78
myItemClassList[i+2].drawableID = drawablesIDs[i+1-78+26]
} else if (i in 91..103) {
myItemClassList[i+2].color = "mavi"
myItemClassList[i+2].numValue = (i+1)-91
myItemClassList[i+2].drawableID = drawablesIDs[i+1-91+39]
} else {
myItemClassList[0].color = "sahte"
myItemClassList[0].drawableID = drawablesIDs[0]
myItemClassList[1].color = "sahte"
myItemClassList[1].drawableID = drawablesIDs[0]
}
}
Is there a cleaner way to do this?
One can use lambda expression to create an array. For example:
val test = Array(28){i-> examples[i]}
This works fine with one "i" parameter. But if I want to try something like this:
val test = Array(28){if(i<13)-> examples[i]}
it gives me an error because of it's syntax is wrong.
More Simple Question
Let's say we have an array which has numbers from 0 to 28 like this:
val testNumbers= Array(28){i->i}
Now I want to create an array which will contain numbers from 0 to 10 using lambda.
How do I this:
val player6 = Array(10){(it<10) -> testNumbers[it]} // gives an syntax error
From what I could gather from your picture, I've made these three assumptions:
The numValue is grouped in groups of 13 items
Each group receives a color in the order: kirmizi -> siyah -> yesil -> mavi, then it cycles again
The drawable IDs cycles every 52 items
Based on this, I came up with the following solution:
data class Item(var color: String = "", var numValue: Int = -99, var drawableId: Int = -99)
fun main() {
val colors = listOf("kirmizi", "siyah", "yesil", "mavi")
val drawableIDs = (0..52).toList() // This is just a stub. in your case it will be your drawable lists
val edgeCase = arrayListOf(Item("sahte", drawableId = drawableIDs[0]), Item("sahte", drawableId = drawableIDs[0]))
val pattern = (0 until 104)
.map { index -> Pair(index, index / 13) }
.map { (index, group) ->
Item(
color = colors[group % 4],
numValue = index+1,
drawableId = drawableIDs[(index % 52) + 1]
)
}
val total = pattern + edgeCase
total.forEach { println(it) }
}
You can play around with it on this kotlin playground.
Is there a cleaner way to do this?
From what I gather, you want to initialize only the first 13 values of a contiguous array with 28 spaces, leaving the rest with either their default values or null.
The reason why your code doesn't work is because the Array initializer expects you to return an object. the if block by itself is not an expression in kotlin, so it doesn't evaluate to a value, so you need to provide an else branch for it to work.
val examples = Array(28) { if (i < 13) examples[i] else defaultExample }
This is stated in the Kotlin documentation for control flow:
If you're using if as an expression rather than a statement (for example, returning its value or assigning it to a variable), the expression is required to have an else branch.
More simple question
In this case you could just use take:
// If you don't want to modify it
val player6 = testNumbers.take(10)
.toTypedArray() // Since take returns a List, you need to turn it back into an array
// If you want to modify the items
val player6 = testNumbers.take(10)
.map { value -> modifyNumber(value) }
.toTypedArray()
Tip: In kotlin if you declare your constructor parameter with val or var they are already attributes from your class and you don't need to initialize manually in the init block.
/*
* You don't need to do this:
* class Item(var color: String, var numValue: Int, var drawableId: Int) {
* init {
* this.color = color
* this.numValue = numValue
* this.drawableId = drawableId
* }
* }
*/
// Kotlin already does it for you
class Item(var color: String, var numValue: Int, var drawableId: Int)
fun main() {
val myitem = Item("blue", 20, 100)
println(myitem.color)
println(myitem.numValue)
println(myitem.drawableId)
}
Here is a possible solution :
fun getColor(i: Int) = when (i) {
in 0..1 -> "sahte"
in 2..13, in 52..64 -> "kirmizi"
in 65..77, in 13..25 -> "siyah"
in 26..38, in 78..90 -> "yesil"
in 39..51, in 91..103 -> "mavi"
else -> ""
}
fun getNumValue(i: Int) = when (i) {
in 0..1 -> -99
in 2..13 -> i - 1
in 13..25 -> (i - 1) - 13
in 26..38 -> (i - 1) - 26
in 39..51 -> (i - 1) - 39
in 52..64 -> (i - 1) - 52
in 65..77 -> (i - 1) - 65
in 78..90 -> (i - 1) - 78
in 91..103 -> (i - 1) - 91
else -> -99
}
fun getDrawableID(i: Int) = when (i) {
in 0..1 -> drawablesIDs[0]
in 2..13, in 13..25, in 26..38, in 39..51 -> drawablesIDs[i - 1]
in 52..64 -> drawablesIDs[(i - 1) - 52]
in 65..77 -> drawablesIDs[i - 1 - 65 + 13]
in 78..90 -> drawablesIDs[i - 1 - 78 + 26]
in 91..103 -> drawablesIDs[i - 1 - 91 + 39]
else -> -99
}
val myItemClassArray = Array(104) {
Item(getColor(it), getNumValue(it), getDrawableID(it))
}
Maybe there is some mistakes in the different ranges.
The main advantages are :
each mapping is testable independently
no mutability
I am getting some issue Bar Graph with Dynamic values in X-Axis values setValueFormatter:
I checked with a list view, recycler view and view inflation but always getting issues in the values in X-Axis Formatter that it doesn't work with value when list size is 3 or less than 3.
With View Inflation:
Requirement: I want to set the values in a graph with a set of 8. Like, If my list size is 14,
So the graph view inflates 2 times like:
If my list size is 10, it will inflate 2 times, first with 8 values of bar graph and the second graph only with 2 values. It works for the same when the list size is 9, 8 values for the first graph and 1 value for the second graph.
But the issue is that when the remaining value for the last graph is less than 4 like:
when the list size is 11, the remaining value is 3
when the list size is 25, the remaining value is 1 for the fourth graph
It gives the repeated or duplicated value in the x Axis like this:
Here is the issue:
xAxis.setValueFormatter(object : IAxisValueFormatter {
override fun getFormattedValue(value: Float, axis: AxisBase?): String {
var size : String = ""
try {
if(value>=0&&!mMonths[value.toInt()/10 % mMonths.size].equals("")&&value<mMonths.size*10) {
size = mMonths[0 / 10 % mMonths.size]
}
}catch (e : IndexOutOfBoundsException){
xAxis.isGranularityEnabled=false
}
return size
}
})
Note: This will work fine when mMonths list size is greater than 3 as you can see in the first screenshot but it gives issues for list size 1, 2 or 3.
Source code:
private fun setBarChart(mList:ArrayList) {
var calender_size: Float = 0.0f
if(mList.
size>0){
if(mList.size>8){
if(mList.size/8==0){
calender_size = (mList.size / 8).toFloat()
}else{
calender_size = (mList.size / 8).toFloat() + 1
}
}else{
calender_size = 1f
}
}
var section_variable: Int = 0
var tillvalue: Int = 0
var list_size: Int = 1
allGraphs.clear()
multiplegraphviewtmp.removeAllViews()
for (i in 0 until Math.round(calender_size)) {
val newLayout = layoutInflater.inflate(R.layout.new_functionalgraph, null, false)
multiplegraphviewtmp.addView(newLayout)
allGraphs.add(newLayout)
newLayout.tv_categoryname.setText(mList[0].description)
val entriesnew = ArrayList<BarEntry>()
entriesnew.clear()
var mMonths : ArrayList<String> = ArrayList()
mMonths.clear()
if(mList.size>8){
if (i > 0) {
if (mList.size / 8 == 0) {
list_size = (i + 1) * 8
} else {
val temp = mList.size % 8
if ((i + 1) * 8 < mList.size) {
list_size = (i + 1) * 8
} else {
// section_variable = section_variable + temp
list_size = list_size + temp
}
}
} else {
list_size = 8
}
for (j in section_variable until list_size) {
entriesnew.add(BarEntry((tillvalue * 10).toFloat(), mList[j].percentage.toFloat()))
mMonths.add(mList[j].itemName)
section_variable++
tillvalue++
}
tillvalue = 0
}else{
list_size=1
for (j in 0 until mList.size) {
entriesnew.add(BarEntry((tillvalue * 10).toFloat(), mList[j].percentage.toFloat()))
mMonths.add(mList[j].itemName)
//section_variable++
tillvalue++
}
tillvalue = 0
}
newLayout.barChartmore.legend.isEnabled = false
newLayout.barChartmore.description.isEnabled = false
var leftAxisfornewgraphs = newLayout.barChartmore.getAxisLeft()
leftAxisfornewgraphs.setAxisMaxValue(100f)
leftAxisfornewgraphs.setAxisMinValue(0f)
leftAxisfornewgraphs.setStartAtZero(true)
leftAxisfornewgraphs.setAxisLineWidth(0f)
leftAxisfornewgraphs.setLabelCount(11, true)
leftAxisfornewgraphs.setDrawGridLines(true)
leftAxisfornewgraphs.axisLineColor = activity!!.resources.getColor(R.color.graph_textColor)
leftAxisfornewgraphs.textColor = activity!!.resources.getColor(R.color.graph_textColor)
leftAxisfornewgraphs.typeface = Typeface.DEFAULT_BOLD
leftAxisfornewgraphs.textSize = 12f
leftAxisfornewgraphs.gridColor = activity!!.resources.getColor(R.color.view)
leftAxisfornewgraphs.axisLineColor = activity!!.resources.getColor(R.color.view)
// leftAxisfornewgraphs.setValueFormatter(MyYAxisValueFormatter()) //////////to set labels in %
//////////////////to change Right line/////////////////
var rightAxis = newLayout.barChartmore.axisRight
rightAxis.setDrawGridLines(false)
rightAxis.setDrawLabels(false)
rightAxis.setLabelCount(40, true)
rightAxis.axisLineColor = activity!!.resources.getColor(R.color.view)
var xAxis = newLayout.barChartmore.getXAxis()
xAxis.textSize = 9f
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM)
xAxis.setLabelRotationAngle(-90f)
xAxis.textColor = activity!!.resources.getColor(R.color.graph_textColor)
xAxis.typeface = Typeface.DEFAULT_BOLD
xAxis.gridColor = activity!!.resources.getColor(R.color.view)
xAxis.axisLineColor = activity!!.resources.getColor(R.color.view)
xAxis.granularity=1f
newLayout.barChartmore.setPinchZoom(false)
newLayout.barChartmore.getLegend().setWordWrapEnabled(true)
newLayout.barChartmore.setScaleEnabled(false)
newLayout.barChartmore.isDoubleTapToZoomEnabled=false
val labels = ArrayList<String>()
labels.clear()
for (i in 0 until mMonths.size){
labels.add("18-Jan")
}
xAxis.setValueFormatter(object : IAxisValueFormatter {
override fun getFormattedValue(value: Float, axis: AxisBase?): String {
var size : String = ""
try {
if(value>=0&&!mMonths[value.toInt()/10 % mMonths.size].equals("")&&value<mMonths.size*10) {
size = mMonths[0 / 10 % mMonths.size]
mTotalvalues++
}
}catch (e : IndexOutOfBoundsException){
xAxis.isGranularityEnabled=false
}
return size
}
})
xAxis.spaceMin = 6f
xAxis.spaceMax = 6f
val barDataSet = BarDataSet(entriesnew, "Cells")
barDataSet.setDrawValues(false)
val list: ArrayList<Int> = ArrayList<Int>()
list.clear()
list.add(activity!!.resources.getColor(R.color.graph_skyblue))
list.add(activity!!.resources.getColor(R.color.graph_orange))
list.add(activity!!.resources.getColor(R.color.graph_navyBlue))
barDataSet.setColors(list)
val data = BarData(barDataSet);
if(mMonths.size<2){
data.barWidth= 1f
}else{
data.barWidth= 5f
}
newLayout.barChartmore.data = data
newLayout.barChartmore.invalidate()
newLayout.export_single.setOnClickListener(View.OnClickListener {
Commonclass.createPdf(activity, newLayout.barChartmore.chartBitmap)
})
//newLayout.barChartmore.clear()
}
}
Same Issue getting with recycler view and Listview. please help:
I am getting some issue Bar Graph with Dynamic values in XAxis values setValueFormatter
I try to create an android game on pure kotlin, but I stacked on the bullet move rendering. If I do single shoot and will waiting that it disappear it works well, but if I moves player and do the next one shoot, previous shoot freeze on their position and doesn't finish their rendering. It looks like this:
On the picture: shooted once, moved left on the one point and do another shoot
Seems that the problem is in my drawShoot method. If the player position changed , that shoot position changed too, but I expect that coroutines will solved my problem, but it's isn't. Here is my code, any ideas hot to fix the bug?
Here is my code :
class GameV2 {
companion object {
private const val BLANK_CHAR = " "
private const val BLANK_CHAR_CODE = 0
private const val AREA_BORDER_CHAR = "x"
private const val AREA_BORDER_CHAR_CODE = 1
private const val PLAYER_ONE = "^"
private const val PLAYER_ONE_CODE = 2
private const val SHOOT = "o"
private const val SHOOT_CODE = 3
private var gameFinished = false
lateinit var gameAreaWidth: Number
lateinit var gameAreaHeigth: Number
private var gameArea = Array(0) { IntArray(0) }
private val observer = GameStateObserver()
private val gameState = GameState()
var shootPositionY: Int = 0
var shootPositionX: Int = 0
var playerPositionX: Int = 0
var playerPositionY: Int = 0
var bulletPlayerOnePositionX: Int = 0
var bulletPlayerOnePositionY: Int = 0
var bulletPlayerTwoPositionY: Int = 0
var bulletPlayerTwoPositionX: Int = 0
fun prepareGameEngine() {
observer.addObserver(gameState)
observer.changeState()
}
fun initGameArea(x: Int, y: Int) {
gameAreaWidth = y
gameAreaHeigth = x
gameArea = Array(x) { IntArray(y) }
var i = 1
var j: Int
while (i <= x) {
j = 1
while (j <= y) {
if (i == 1 || i == x || j == 1 || j == y) {
gameArea[i - 1][j - 1] = 1
} else {
gameArea[i - 1][j - 1] = 0
}
j++
}
i++
}
}
fun drawGameArea(): String {
val sb = StringBuffer()
gameArea.forEach { i ->
//println()
sb.append("\n")
i.forEach { j ->
if (j == BLANK_CHAR_CODE) {
// print(BLANK_CHAR)
sb.append(BLANK_CHAR)
}
if (j == AREA_BORDER_CHAR_CODE) {
// print(AREA_BORDER_CHAR)
sb.append(AREA_BORDER_CHAR)
}
if (j == PLAYER_ONE_CODE) {
// print(PLAYER_ONE)
sb.append(PLAYER_ONE)
}
if (j == SHOOT_CODE) {
// print(SHOOT)
sb.append(SHOOT)
}
}
}
return sb.toString()
}
private fun clearGameAreaSpaceInCoords(x: Int, y: Int) {
gameArea[x][y] = BLANK_CHAR_CODE
}
fun updateUserPosition(x: Int, y: Int) {
playerPositionX = x
playerPositionY = y
gameArea[playerPositionX][playerPositionY] = PLAYER_ONE_CODE
observer.changeState()
}
fun moveUserRight() {
if (playerPositionY < gameAreaWidth.toInt() - 2) {
// example: y - 1 = move left; x - 1 = move up
clearGameAreaSpaceInCoords(playerPositionX, playerPositionY)
updateUserPosition(playerPositionX, playerPositionY + 1)
}
}
fun moveUserLeft() {
if (playerPositionY > 1) {
clearGameAreaSpaceInCoords(playerPositionX, playerPositionY)
updateUserPosition(playerPositionX, playerPositionY - 1)
}
}
fun drawShoot() {
// playerPositionX - 1 mean that 1 point higher than player character
shootPositionY = playerPositionY
shootPositionX = playerPositionX
GlobalScope.launch { // launch a new coroutine in background and continue
for(i in 1..gameAreaHeigth.toInt() - 3 ){
gameArea[shootPositionX - i][shootPositionY] = SHOOT_CODE
observer.changeState()
delay(300L)
clearGameAreaSpaceInCoords(shootPositionX - i, shootPositionY)
observer.changeState()
}
}
}
private fun isGameStateChanged(): Boolean {
return GameStateObserver().hasChanged()
}
fun startGame() {
}
}
}
SOLVED. Solution : It's my bad. I call global variable inside drawShoot method. All what i need it's change global var to local, like this:
fun drawPlayerOneShoot() {
// playerOnePositionX - 1 mean that 1 point higher than player character
val shootPositionY = playerOnePositionY
val shootPositionX = playerOnePositionX
// launch a new coroutine in background and continue
GlobalScope.launch {
for(i in 1..gameAreaHeigth.toInt() - 3 ){
gameArea[shootPositionX - i][shootPositionY] = SHOOT_CODE
observer.changeState()
delay(300L)
clearGameAreaSpaceInCoords(shootPositionX - i, shootPositionY)
if (shootPositionX - i == playerTwoPositionX && shootPositionY == playerOnePositionY){
isPlayerOneWin = true
println("PLAYER ONE WIN")
}
observer.changeState()
}
}
}
From the first glimpse it seems that the problem lies not in the Coroutine itself, but inside of observer.changeState() . What kind of code is there? I guess it uses a singular "shot" instance and it does not matter whether you launch a new coroutine to change it.
I am trying to get hotels to the length of the route, using REST request
The request has a parameter compressedRoute, but the conversion algorithm in Java script language.
Is there such an algorithm in Java or Kotlin? Maybe such a request is in the SDK, but I do not see it?
points to take from route.routeElements.geometry.allPoints?
I managed to compress the route. The problem was in the error of the example on JS.
First you need to go through to limit the number of points in the route, I set 120. If you do more, 414 errors may occur
fun execute(route: Route, categoryName: String, radiusInMeters: Int): Single<List<ItemPlaceByCorridor>> {
val allPointList = route.routeGeometry
val pointList = ArrayList<GeoCoordinate>()
for ((index, elem) in allPointList.withIndex()) {
if (index % (allPointList.size / POINTS_IN_ROUTE) == 0 || (index == 0) || (index == allPointList.lastIndex)) {
pointList.add(elem)
}
}
var routeStr = hereEncodePolyline(pointList)
routeStr += ";w=$radiusInMeters"
return mHereMapsApi.getPlacesByCorridor(categoryName, routeStr)
.map { response -> response.results.listItems }
}
Algorithm transferred from JS to Kotlin
private fun hereEncodePolyline(positions: List<GeoCoordinate>): String {
var lastLat = 0.0
var lastLon = 0.0
var result = ""
for (i in 1..positions.lastIndex) {
val elem = positions[i]
result += hereEncodeFloat(elem.latitude - lastLat)
result += hereEncodeFloat(elem.longitude - lastLon)
lastLat = elem.latitude
lastLon = elem.longitude
}
return result
}
private fun hereEncodeFloat(value: Double): String {
val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
var result = ""
// convert to fixed point
var fixedPoint = Math.round(value * 100000)
// make room on the lowest bit
fixedPoint = fixedPoint shl (1)
// flip bits of negative numbers and ensure that the last bit is set
// (should actually always be the case, but for readability it is ok to do it explicitly)
if (fixedPoint < 0) {
fixedPoint = (fixedPoint.inv()) or 0x01
}
// var-length encode the number in chunks of 5 bits starting with the least significant
// to the most significant
while (fixedPoint > 0x1F) {
result += (chars[((fixedPoint and 0x1F) or 0x20).toInt()]).toString()
fixedPoint = fixedPoint shr (5)
}
result += (chars[fixedPoint.toInt()])
return result
}