kotlin connect Timeout - time

I'm trying to do a task with an Asynctask in kotlin. I want to show a dialog message to user In the case that user has a very slow or spotty data connection at the time of use, I'd like to make the AsyncTask timeout after a period of time 5 second .My point is where should l put dialog ? after finally in doInBackground ?
inner class Arr : AsyncTask<String, String, String>() {
val progressDialog = AlertDialog.Builder(this#MainActivity)
val dialogView = layoutInflater.inflate(R.layout.progress_dialog, null)
val message = dialogView.findViewById<TextView>(R.id.message_id)
val dialog = progressDialog.create()
override fun onPreExecute() {
super.onPreExecute()
dialog.setMessage("please wait")
dialog.setCancelable(false)
dialog.show()
}
// for build connection
override fun doInBackground(vararg url: String?): String {
var text: String
val connection = URL(url[0]).openConnection() as HttpURLConnection
connection.connectTimeout = 300
try {
connection.connect()
text = connection.inputStream.use { it.reader().use { reader -> reader.readText() } }
} finally {
dialog.setMessage("Sorry you dont have proper net connectivity..!\nCheck your internet settings or retry.")
dialog.setCancelable(false)
dialog.show()
}
return text
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
handleJson(result)
dialog.dismiss();
}
override fun onProgressUpdate(vararg text: String?) {
}
}

Related

Kotlin: High-Performance concurrent file I/O best-practices?

What's the most performant way in Kotlin to allow concurrent file I/O in multi-reader, single-writer fashion?
I have the below, but I'm unsure how much overhead is being created by the coroutine facilities:
AsyncFileChannel, with extension functions to use it in a suspend context
Taken from example here: https://github.com/Kotlin/coroutines-examples/blob/master/examples/io/io.kt
DiskManager class, that uses a custom ReadWriteMutex
Searching for examples of this doesn't turn up much (try searching Github for ReadWriteMutex, there are a tiny handful of Kotlin repos implementing this).
class DiskManagerImpl(file: File) : DiskManager {
private val mutex = ReadWriteMutexImpl()
private val channel = AsynchronousFileChannel.open(file.toPath(),
StandardOpenOption.READ, StandardOpenOption.WRITE)
override suspend fun readPage(pageId: PageId, buffer: MemorySegment) = withContext(Dispatchers.IO) {
mutex.withReadLock {
val offset = pageId * PAGE_SIZE
val bytesRead = channel.readAsync(buffer.asByteBuffer(), offset.toLong())
require(bytesRead == PAGE_SIZE) { "Failed to read page $pageId" }
}
}
override suspend fun writePage(pageId: PageId, buffer: MemorySegment) = withContext(Dispatchers.IO) {
mutex.withWriteLock {
val offset = pageId * PAGE_SIZE
val bytesWritten = channel.writeAsync(buffer.asByteBuffer(), offset.toLong())
require(bytesWritten == PAGE_SIZE) { "Failed to write page $pageId" }
}
}
}
class ReadWriteMutexImpl : ReadWriteMutex {
private val read = Mutex()
private val write = Mutex()
private val readers = atomic(0)
override suspend fun lockRead() {
if (readers.getAndIncrement() == 0) {
read.lock()
}
}
override fun unlockRead() {
if (readers.decrementAndGet() == 0) {
read.unlock()
}
}
override suspend fun lockWrite() {
read.lock()
write.lock()
}
override fun unlockWrite() {
write.unlock()
read.unlock()
}
}
suspend inline fun <T> ReadWriteMutex.withReadLock(block: () -> T): T {
lockRead()
return try {
block()
} finally {
unlockRead()
}
}
suspend inline fun <T> ReadWriteMutex.withWriteLock(block: () -> T): T {
lockWrite()
return try {
block()
} finally {
unlockWrite()
}
}

LazyColumn item not updated accordingly while list in room table already updated

When the Icon clicked, viewModel.onLockIconClicked(it) is called to reverse the value of isLock in db.
The Icon is expected to be updated according based on the value of isLock.
I've checked the value did reversed in db table. But LazyColumn not update accordingly.
What did I miss? Thanks a lot!
Ex, initially, Screen: icon = lock and Db: isLock = true,
when Icon clicked, Screen: icon = lock and Db: isLock = false,
while expected is Screen: icon = lock_open and Db: isLock = false.
ListScreen:
#Composable
fun ListScreen(context: Context) {
val viewModel: ListViewModel =
viewModel(factory = ListViewModelFactory(Db.getInstance(context)))
val list by viewModel.list.collectAsState(initial = emptyList())
Scaffold() {
SwipeRefresh(
state = rememberSwipeRefreshState(viewModel.isRefreshing),
onRefresh = { }
) {
LazyColumn(
state = rememberLazyListState(),
) {
items(list) {
Row() {
Icon(
painter = painterResource(if (it.isLock) R.drawable.ic_baseline_lock_24 else R.drawable.ic_baseline_lock_open_24),
contentDescription = null,
modifier = Modifier.clickable() { viewModel.onLockIconClicked(it) }
)
Text(it.code)
}
}
}
}
}
}
ListViewModel:
class ListViewModel(db: Db) : ViewModel() {
private val sumDao = db.sumDao()
val list = sumDao.getAllRows()
var isRefreshing by mutableStateOf(false)
private set
//init
init {
viewModelScope.launch(Dispatchers.IO) {
val initialCodeList = listOf("aaa", "bbb")
for (code in initialCodeList) {
val sum = Sum()
sum.code = code
sumDao.insert(sum)
}
}
}
fun onLockIconClicked(sum: Sum) {
sum.isLock = !sum.isLock
viewModelScope.launch(Dispatchers.IO) {
sumDao.update(sum)
}
}
}
class ListViewModelFactory(private val db: Db) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ListViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return ListViewModel(db) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
Sum:
#Entity(tableName = "sum", primaryKeys = ["code"])
data class Sum(
#ColumnInfo(name = "code")
var code: String = "",
#ColumnInfo(name = "is_lock")
var isLock: Boolean = true
)
SumDao:
#Dao
interface SumDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(sum: Sum): Long
#Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(sum: Sum): Int
#Delete
suspend fun delete(sum: Sum): Int
#Query("select * from sum")
fun getAllRows(): Flow<List<Sum>>
}
Db:
#Database(entities = [Sum::class], version = 1, exportSchema = false)
abstract class Db : RoomDatabase() {
abstract fun sumDao(): SumDao
companion object {
#Volatile
private var INSTANCE: Db? = null
fun getInstance(context: Context): Db {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
Db::class.java,
"db"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
return instance
}
}
}
}
Consider taking the State-in-Compose for a better understanding of the concepts of state-handling in Compose.
I'm sorry but the information that you have provided is massive, so I can't pinpoint the source of the bug, but here's what you can do for now:
In your Dao class, just replace the words Flow<List<Sum>> with LiveData<List<Sum>>
In your ViewModel, you can get access to the LiveData inside the init like so
var list by mutableStateListOf<Sum>()
init{
sumDao.getAllRows().observeForever{
list = it
}
}
Now, list would ideally be updated every time the value in the databse changes, which infact would trigger recompositions since I am using a direct mutableStateListOf object here.
The problem may lie anywhere:
Since the class Sum is a custom-made class, it may have been experiencing issues triggering recompositions, which is a common problem among new developers, and even some experienced ones nowadays.
Since you are declaring the viewModel inside the Composable, wrong instances of ViewModels may have been passed around, leading to state-inconsistency - always try to declare your viewModels in the top-most layer possibly, i.e., somewhere like the onCreate method of the activity. Fragments are discourages so you should not face any problems over there.
Since you were not actively observing the Flow anywhere, that could have lead to the variable not being updated at all in the ViewModel, which would again lead to UI-inconsistency.

spring-boot TCP server: wait for `empty line` before answering a `multi-line` request

I would like to write a custom check_policy_service (http://www.postfix.org/SMTPD_POLICY_README.html) for postfix with spring-boot.
Short: postfix sends multiple lines like:
foo=bar
me=you
year=123
[empty line]
The empty line indicates, that the request is complete, now the spring-boot app should handle the data and return something like action=ok
Problem:
In my current setup the app tries to handle line 1 foo=bar immediately and does not wait until the [empty line] is sent. How can I make the app wait for the empty line?
Current setup:
spring-boot 2.6.3
#Configuration
class TcpServerConfig {
// #Value("\${tcp.server.port}")
private val port = 6676
#Bean
fun serverConnectionFactory(): AbstractServerConnectionFactory {
val serverConnectionFactory = TcpNioServerConnectionFactory(port)
serverConnectionFactory.setUsingDirectBuffers(true)
serverConnectionFactory.isSingleUse = false // reuse socket
return serverConnectionFactory
}
#Bean
fun inboundChannel(): MessageChannel {
return DirectChannel()
}
#Bean
fun inboundGateway(
serverConnectionFactory: AbstractServerConnectionFactory,
inboundChannel: MessageChannel
): TcpInboundGateway {
val tcpInboundGateway = TcpInboundGateway()
tcpInboundGateway.setConnectionFactory(serverConnectionFactory)
tcpInboundGateway.setRequestChannel(inboundChannel)
return tcpInboundGateway
}
}
#MessageEndpoint
class TcpServerEndpoint {
#ServiceActivator(inputChannel = "inboundChannel")
fun process(message: ByteArray): ByteArray {
// I would need to have `message` contain every line until empty line - not a single line
println(String(message))
// TODO handle message accordingly
return "action=ok\n".toByteArray()
}
}
I am not sure where to hook in. Maybe DirectChannel needs to be something else?
I wrote a quick non spring-boot implementation which works, so basically I need the spring-boot-ified version of:
fun main(args: Array<String>) {
val server = ServerSocket(9999)
println("Server is running on port ${server.localPort}")
while (true) {
val client = server.accept()
println("Client connected: ${client.inetAddress.hostAddress}")
// Run client in it's own thread.
thread { ClientHandler(client).run() }
}
}
class ClientHandler(client: Socket) {
private val client: Socket = client
private val reader: BufferedReader = BufferedReader(InputStreamReader(client.getInputStream()))
private val writer: PrintWriter = PrintWriter(client.getOutputStream(), true)
private var running: Boolean = false
fun run() {
running = true
while (running) {
try {
do {
val line = reader.readLine() ?: break
// TODO collect all lines in a list and handle
} while (line.isNotEmpty())
write("action=ok")
} catch (ex: Exception) {
// TODO: Implement exception handling
ex.printStackTrace()
shutdown()
} finally {
}
}
}
private fun write(message: String) {
writer.write((message + "\n\n"))
writer.flush()
}
private fun shutdown() {
running = false
client.close()
println("${client.inetAddress.hostAddress} closed the connection")
}
}
The magic part is read all lines until line is empty and then handle the request(s)
You need to set the Deserializer so that Spring knows when to release the read content (so far) to the handler as described in the docs.
In your case, you should write a custom deserializer implementing the interface org.springframework.core.serializer.Deserializer that releases the content to the handler as soon as it has detected two newlines in a row. Make it return a List<String> with the lines sent by Postfix:
class DoubleNewLineDeserializer: Deserializer<List<String>> {
companion object {
const val UNIX_NEWLINE = "\n\n"
const val WINDOWS_NEWLINE = "\r\n\r\n"
val CRLFRegex = Regex("[\r\n]+")
}
override fun deserialize(inputStream: InputStream): List<String> {
val builder = StringBuilder()
while(!builder.endsWith(UNIX_NEWLINE) && !builder.endsWith(WINDOWS_NEWLINE))
builder.append(inputStream.read().toChar())
return builder.toString().split(CRLFRegex).filter { it.isNotEmpty() }
}
}
This deserializer reads input until it finds a double new line (here either \n\n or \r\n\r\n, you can modify this as you want) at which point it removes the message delimiter and then returns the message as a list of lines.
Finally, set your new deserializer on the ServerConnectionFactory:
serverConnectionFactory.deserializer = DoubleNewLineDeserializer()
Input to TCP socket:
foo=bar
me=you
year=123
[empty line]
[empty line]
Input to handler:
[foo=bar, me=you, year=123]

How to convert ByteReadChannel into Flow<ByteBuffer>

How can I convert io.ktor.utils.io.ByteReadChannel into kotlinx.coroutines.flow.Flow<java.nio.ByteBuffer>?
I use Ktor with this routing:
post("/upload") {
val channel: ByteReadChannel = call.receiveChannel()
val flow: Flow<ByteBuffer> = channel.asByteBufferFlow() // my custom extension method
transaction.execute {
testDao.saveFile(flow)
}
call.respond("OK")
}
The DAO uses R2DBC and Blob like this:
override suspend fun saveFile(input: Flow<ByteBuffer>) {
val connection = requireR2DBCTransactionConnection()
val publisher: Publisher<ByteBuffer> = input.asPublisher()
val statement: Statement = connection.createStatement("insert into bindata (data) values ($1)")
statement.bind(0, Blob.from(publisher))
val count: Int = statement.execute().awaitFirst().rowsUpdated.awaitFirst()
if (count != 1) {
throw IllegalStateException()
}
}
I tried to write this extension method but I failed:
fun ByteReadChannel.asByteBufferFlow(): Flow<ByteBuffer> = object : AbstractFlow<ByteBuffer>() {
override suspend fun collectSafely(collector: FlowCollector<ByteBuffer>) {
/* I have no idea */
}
}
My main problem is that I have not found any similar sample and both ByteBuffer and ByteReadChannel is new for me.

LiveData Transformation not getting triggered

I subscribed to ids and search in the ui but i wasn't getting any results so i stepped through with the debugger and found out that the transformation is not getting triggered after the first time. So when i call setIds the first time ids gets updated but for every call after the first one the transformation won't trigger. Same goes for the search.
Any ideas what might possible go wrong?
class MyViewModel : ViewModel() {
private val repository = Repository.sharedInstance
var recentRadius: LiveData<List<RecentRadius>>?
var recentRoute: LiveData<List<RecentRoute>>?
init {
recentRadius = repository.recentRadius()
recentRoute = repository.recentRoute()
}
private val idsInput = MutableLiveData<String>()
fun setIdsInput(textId: String) {
idsInput.value = textId
}
val ids: LiveData<List<String>> = Transformations.switchMap(idsInput) { id ->
repository.ids(id)
}
private val searchInput = MutableLiveData<Search>()
fun setSearchInput(search: Search) {
searchInput.value = search
}
val search: LiveData<SearchResult> = Transformations.switchMap(searchInput) { search ->
when (search.type) {
SearchType.ID -> repository.id(search)
SearchType.RADIUS -> repository.radius(search)
SearchType.ROUTE -> repository.route(search)
}
}
}
The most common reason why transformation don't get triggered is when there is no Observer observing it or the input LiveData is not getting changed.
Below example illustrates use of map when observer is attached in the activity.
Activity
class MainActivity : AppCompatActivity() {
lateinit var mBinding : ActivityMainBinding
private val mViewModel : MainViewModel by lazy {
getViewModel { MainViewModel(this.application) }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
mBinding.vm = mViewModel
// adding obeserver
mViewModel.videoName.observe(this, Observer<String> { value ->
value?.let {
//Toast.makeText(this, it, Toast.LENGTH_LONG).show()
}
})
}
}
ViewModel with map
class MainViewModel(val appContext : Application) : AndroidViewModel(appContext) {
private val TAG = "MainViewModel"
var videoData = MutableLiveData<VideoDownload>()
var videoName : LiveData<String>
init {
// Update the data
videoName = Transformations.map(videoData) { "updated : "+it.webUrl }
}
fun onActionClick(v : View) {
// change data
videoData.value = VideoDownload(System.currentTimeMillis().toString())
}
fun onReActionClick(v : View) {
// check data
Toast.makeText(appContext, videoName.value, Toast.LENGTH_LONG).show()
}
}
ViewModel with switchMap
class MainViewModel(val appContext : Application) : AndroidViewModel(appContext) {
private val TAG = "MainViewModel"
var videoData = MutableLiveData<VideoDownload>()
var videoName : LiveData<String>
init {
// Update the data
videoName = Transformations.switchMap(videoData) { modData(it.webUrl) }
}
private fun modData(str: String): LiveData<String> {
val liveData = MutableLiveData<String>()
liveData.value = "switchmap : "+str
return liveData
}
fun onActionClick(v : View) {
// change data
videoData.value = VideoDownload(System.currentTimeMillis().toString())
}
fun onReActionClick(v : View) {
// check data
Toast.makeText(appContext, videoName.value, Toast.LENGTH_LONG).show()
}
}
for me, it was because the observer owner was a fragment. It stopped triggering when navigating to different fragments. I changed the observer owner to the activity and it triggered as expected.
itemsViewModel.items.observe(requireActivity(), Observer {
The view model was defined as a class property:
private val itemsViewModel: ItemsViewModel by lazy {
ViewModelProvider(requireActivity()).get(ItemsViewModel::class.java)
}
If you really want it to be triggered.
fun <X, Y> LiveData<X>.forceMap(
mapFunction: (X) -> Y
): LiveData<Y> {
val result = MutableLiveData<Y>()
this.observeForever {x->
if (x != null) {
result.value = mapFunction.invoke(x)
}
}
return result
}

Resources