Why does HorizontalPager's automatic scrolling stop after manual scrolling? - kotlin

I have a HorizontalPager
val pageCount = bannerList.size
val startIndex = Int.MAX_VALUE / 2
val pagerState = rememberPagerState(initialPage = 100)
HorizontalPager(
count = Int.MAX_VALUE,
state = pagerState,
contentPadding = PaddingValues(
horizontal = 20.dp
),
modifier = Modifier
.fillMaxWidth()
) { index ->
// content goes here
}
and I made it scrolling every 4 second like banners with LaunchedEffect
LaunchedEffect(
key1 = Unit,
block = {
repeat(
times = Int.MAX_VALUE,
action = {
delay(
timeMillis = 4000
)
pagerState.animateScrollToPage(
page = pagerState.currentPage + 1
)
}
)
})
it scrolls fine every 4 second but when i scroll it manually HorizontalPager stops scrolling!
any suggestions how to fix this?

animateScrollToPage throws an exception when called during manual scrolling:
java.util.concurrent.CancellationException: Current mutation had a higher priority
You can solve it in many ways. For example just catch the exception and ignore it:
delay(
timeMillis = 4000
)
try {
pagerState.animateScrollToPage(
page = pagerState.currentPage + 1
)
} catch (_: Throwable) {
}
An other option is to check whether the pager is being dragged and stop your LaunchedEffect for this period of time:
val isDragged by pagerState.interactionSource.collectIsDraggedAsState()
if (!isDragged) {
LaunchedEffect(Unit) {
// ...
}
}
I think the second solution is cleaner, because in this case the timer will be restarted, and delay until the next scroll will always be the same.

Related

Compose onValueChange behaviour isn't consistent

I'm making a sudoku game and solver. For my user interface I used LazyVerticalGrid to create a 9x9 grid. I successfully made it so when you click on a cell it will only accept digits [1-9] via an OutLinedTextField. I then added a conditional that only empty cells would have the text field applied. That worked and only those cells could be altered but when I do that the logic that only accepts digits doesn't work and the program crashes. If I comment out the conditional statement and the OutLinedTextField is applied to all cells it works again. I get the following error.
Also if I add conditionals for backgroundColor or Content Color the same thing happens and the program crashes if a non digit is pressed. I'm not sure why the conditionals affect the onValueChange logic. Why is this and how do I fix it?
fun displayPuzzle(answer: Array<Array<IntArray>>) {
var list: SnapshotStateList<String> = mutableStateListOf()
for (x in answer[0]) list.addAll(x.map { it.toString() })
var columnHeighty by remember { mutableStateOf(0F) }
var columnWidthx by remember { mutableStateOf(0f) }
var pad = 20.dp
LazyVerticalGrid(
columns = GridCells.Fixed(9),
contentPadding = PaddingValues(
start = pad,
top = pad,
end = pad,
bottom = pad
)
) {
items(list.size) { index ->
Card(
shape = RectangleShape,
backgroundColor = Color.Red,
modifier = Modifier
.requiredWidth(83.dp)
.fillMaxWidth()
.fillMaxHeight()
.onGloballyPositioned { coordinates ->
columnWidthx = coordinates.size.width.toFloat()
columnHeighty = coordinates.size.height.toFloat()
},
//backgroundColor = if (list[index].toInt() == 0) Color.Yellow else Color.White ,
//contentColor = if (list[index].toInt() == 0) Color.Blue else Color.Black ,
border = BorderStroke(width = 1.dp, color = Color.Black)
) {
Text(
text = list[index],
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
color = Color(0xFF000000),
textAlign = TextAlign.Center,
modifier = Modifier
.padding(23.dp)
.clickable { }
)
}
// When the if statement is included the program crashes on a non digit entry
//if (list[index].toInt() == 0) {
val pattern = remember { Regex("[1-9]") }
var value by remember { mutableStateOf("") }
OutlinedTextField(
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
colors = TextFieldDefaults.outlinedTextFieldColors(cursorColor = Color.Transparent),
textStyle = TextStyle(color = Color.Red),
modifier = Modifier
.fillMaxHeight()
.padding(vertical = 10.dp, horizontal = 10.dp),
value = value,
onValueChange = { if (it.isEmpty() || (it.matches(pattern) && (it.length == 1)))
value = it
list[index] = value}
)
//}
}
}
Your game crashed because you trying to convert for example 'a' to Int value and runtime throws NumberFormatException.
You need to use:
if (list[index].toIntOrNull() == null)
This condition will be triggered if a non-decimical number is obtained from your SnapshotStateList
Explanation: toIntOrNull() returns Int from String (example: "4".toIntOrNull() - returns 4) otherwise it returns null

Kotlin multiplatform. Ktor download big file and save to local file [duplicate]

I've been spending way too much time trying to solve this problem. So the code that I posted below does work in terms of downloading a file, but the problem is, the flow has a very unexpected behaviour. The response.content.readAvailable() method call seems to block until it's completely done downloading the whole file at which point the emit progress happens, so you end up waiting a long time for the file to download, and then in a split second you get all of the progress updates. So I'm wondering if there is a way to do this where I read in a certain number of bytes at a time and then emit a progress and then repeat until the file is done downloading? Or maybe a way to hook into the readAvailable() method and update the progress that way? Any help with this would be greatly appreciated.
Here's the code I found and modified, but still does not work right:
suspend fun HttpClient.downloadFile(
output: File,
downloadUrl: String,
md5Hash: String,
) = flow {
try {
val response = get<HttpResponse> { url(downloadUrl) }
val data = ByteArray(response.contentLength()?.toInt() ?: 0)
val contentLn = response.contentLength()?.toInt() ?: 0
var offset = 0
var bytesRemaining = contentLn
do {
val chunkSize = min(maxChunkSize, bytesRemaining)
logger?.d { "Read Available:" }
val result = response.content.readAvailable(data, offset, length = chunkSize)
val progress = ((offset / contentLn.toDouble()) * 100).toInt()
emit(DownloadResult.Progress(progress))
logger?.d { "logged progress: $progress" }
// delay(6000L) this was to test my assumption that the readAvalible was blocking.
offset += chunkSize
bytesRemaining -= chunkSize
} while (result != -1)
if (response.status.isSuccess()) {
if (data.md5().hex == md5Hash) {
output.write(data)
emit(DownloadResult.Success)
} else {
emit(DownloadResult.ErrorCorruptFile)
}
} else {
emit(DownloadResult.ErrorBadResponseCode(response.status.value))
}
} catch (e: TimeoutCancellationException) {
emit(DownloadResult.ErrorRequestTimeout("Connection timed out", e))
}
}
Finally after a stupid amount of time I solved this. What you need to use is this. That gives you access to the byte channel as it is downloading.
and a very crude implementation (that I'm not yet done with) is this:
get<HttpStatement>(url = downloadUrl).execute {
var offset = 0
val byteBufferSize = 1024 * 100
val channel = it.receive<ByteReadChannel>()
val contentLen = it.contentLength()?.toInt() ?: 0
val data = ByteArray(contentLen)
do {
val currentRead = channel.readAvailable(data, offset, byteBufferSize)
val progress = if(contentLen == 0) 0 else ( offset / contentLen.toDouble() ) * 100
logger?.d { "progress: $progress" }
offset += currentRead
} while (currentRead >= 0)
}
two things to not with this solution. 1.) I'm in the context of HttpClient, so that's how I have access to get(). 2.) I'm creating a byte buffer size of 1024 * 100 in order to not let the readAvailable method block for too long, though this might not be necessary... the one nice thing about it is that it determines how frequently you will be publishing your progress updates.

Kotlin ListView IndexOutOfBoundsException

(new to kotlin) I'm making my own music app but i'm having an error that i don't understand :
in the bit of code where i try to access a random view (shuffle), i get the java.lang.IndexOutOfBoundsException: Index:96 Size:11.
96 in this example is the view in the listview that i'm trying to access.
It happens in this line : var iView = listViewMusic.get(idShuffle)
Same if i use listViewMusic[idShuffle]
EDIT : Turns out that 11 is only the 11 visible items on screen at any given moment, even if the list contains hundreds of items. When i use listViewMusic.smoothscrolltoposition(idShuffle) it works, but the size of 11 now relates to the 11 on screen after the scrolling
The function playNext() inside the activity, called when clicking on the shuffle button:
fun playNext(){
try {
// Find the order of the next song to play
var idShuffle = musicAdapter!!.getIdofItem(listMusicShuffled.get(musicNextToPlay).title)
// Find the right record in the list
//var iView = listViewMusic[idShuffle]
//toastIt(applicationContext, "adapter count ${listViewMusic.adapter.count}")
//toastIt(applicationContext, "listview count ${listViewMusic.count}")
var iView = listViewMusic.get(idShuffle) //throws the error
// Play it
playOrPause(iView)
// Prepare the next track
musicNextToPlay += 1
if (musicNextToPlay >= listMusicShuffled.size) {
musicNextToPlay = -1
}
} catch (e:Exception) {toastException(applicationContext, "playnext", e) }
}
The part of the function onCreate that fills in the listViewMusic:
// Retrieve the data
val mediaStoreUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
val cursor = contentResolver.query(mediaStoreUri, null, "", null, null)
//Browse the data
listMusic = mutableListOf()
val listMusicJson = getMusicFromJson()
while (cursor!!.moveToNext()) {
val musicName = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME))
val musicArtist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST))
val musicUrl = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA))
val musicType:String =
if (musicArtist.contains("lmdmf", true)) { "LMDMF" }
else if (musicName.contains("slack", true)) { "SLACK" }
else { "MUSIC" }
listMusic.add(
MusicTrack(
musicName,
musicPlayCount,
musicUrl,
musicType
)
)
}
cursor.close()
musicAdapter = MusicAdapter(listMusic.sortedWith(compareBy<MusicTrack>{ it.title }).filter { it.type == "MUSIC"}.toMutableList())
listViewMusic.adapter = musicAdapter

How can I download a large file with Ktor and Kotlin with a progress indicator?

I've been spending way too much time trying to solve this problem. So the code that I posted below does work in terms of downloading a file, but the problem is, the flow has a very unexpected behaviour. The response.content.readAvailable() method call seems to block until it's completely done downloading the whole file at which point the emit progress happens, so you end up waiting a long time for the file to download, and then in a split second you get all of the progress updates. So I'm wondering if there is a way to do this where I read in a certain number of bytes at a time and then emit a progress and then repeat until the file is done downloading? Or maybe a way to hook into the readAvailable() method and update the progress that way? Any help with this would be greatly appreciated.
Here's the code I found and modified, but still does not work right:
suspend fun HttpClient.downloadFile(
output: File,
downloadUrl: String,
md5Hash: String,
) = flow {
try {
val response = get<HttpResponse> { url(downloadUrl) }
val data = ByteArray(response.contentLength()?.toInt() ?: 0)
val contentLn = response.contentLength()?.toInt() ?: 0
var offset = 0
var bytesRemaining = contentLn
do {
val chunkSize = min(maxChunkSize, bytesRemaining)
logger?.d { "Read Available:" }
val result = response.content.readAvailable(data, offset, length = chunkSize)
val progress = ((offset / contentLn.toDouble()) * 100).toInt()
emit(DownloadResult.Progress(progress))
logger?.d { "logged progress: $progress" }
// delay(6000L) this was to test my assumption that the readAvalible was blocking.
offset += chunkSize
bytesRemaining -= chunkSize
} while (result != -1)
if (response.status.isSuccess()) {
if (data.md5().hex == md5Hash) {
output.write(data)
emit(DownloadResult.Success)
} else {
emit(DownloadResult.ErrorCorruptFile)
}
} else {
emit(DownloadResult.ErrorBadResponseCode(response.status.value))
}
} catch (e: TimeoutCancellationException) {
emit(DownloadResult.ErrorRequestTimeout("Connection timed out", e))
}
}
Finally after a stupid amount of time I solved this. What you need to use is this. That gives you access to the byte channel as it is downloading.
and a very crude implementation (that I'm not yet done with) is this:
get<HttpStatement>(url = downloadUrl).execute {
var offset = 0
val byteBufferSize = 1024 * 100
val channel = it.receive<ByteReadChannel>()
val contentLen = it.contentLength()?.toInt() ?: 0
val data = ByteArray(contentLen)
do {
val currentRead = channel.readAvailable(data, offset, byteBufferSize)
val progress = if(contentLen == 0) 0 else ( offset / contentLen.toDouble() ) * 100
logger?.d { "progress: $progress" }
offset += currentRead
} while (currentRead >= 0)
}
two things to not with this solution. 1.) I'm in the context of HttpClient, so that's how I have access to get(). 2.) I'm creating a byte buffer size of 1024 * 100 in order to not let the readAvailable method block for too long, though this might not be necessary... the one nice thing about it is that it determines how frequently you will be publishing your progress updates.

Try to avoid “StaleElementReferenceException” in Selenium - is this approach OK?

I'm implementing a lot of Selenium tests. Sometimes, my tests fail due to a StaleElementReferenceException so i want to check this approach:
Click Handle:
def clickHandle(expectedCondition: (By) => ExpectedCondition[WebElement], by: By, timeOut: Long): Unit = {
var count = 0
var isClicked = false
breakable {
while (count < 4 || !isClicked) {
try {
val element = driver.findElement...
element.click()
isClicked = true
break
} catch {
case e: StaleElementReferenceException => println("Trying to recover from a stale element")
count = count + 1
}
}
}
}
Get attribute handle:
def getAttributeHandle(expectedCondition: (By) => ExpectedCondition[WebElement], by: By, timeOut: Long, attribute: String): String = {
var count = 0
var result = false
var str = ""
breakable {
while (count < 4 || !result) {
try {
val element = driver.findElement...
str = element.getAttribute(attribute)
result = true
break
} catch {
case e: StaleElementReferenceException => println("Trying to recover from a stale element")
count = count + 1
}
}
}
str
}
Generally When you get the StaleElementReferenceException it is a page load error. All you need to do is add a page wait in to wait for a text value or whatever is on the page or you could just put in a sleep such as:
sleep(10)
I use ruby so not 100% sure how to help but generally what I would do when I come across this is add sleeps in or debug your code and add break points in and step through your code to see which point it breaks at.