How to convert a function to listOf without lamdas? - kotlin

There example of works code:
package cryptography
typealias taskFun = () -> Unit
var i: Int = 0
object tasks {
val supportTaskTextList = listOf("hide", "show", "exit")
val supportTaskFunList = listOf<taskFun>(fun() { println("hide $i");i++ }, fun() { println("show $i"); i++ } , fun() { println("Bye $i"); i++ })
fun hide() {
println("Hiding message in image.")
}
fun show() {
println("Obtaining message from image.")
}
fun exit() {
println("Bye!")
kotlin.system.exitProcess(0)
}
fun getTask() {
println("Task (${supportTaskTextList.joinToString(", ")}):")
val i = readln()
for (idx in supportTaskFunList.indices)
{
if (supportTaskTextList[idx] == i) return supportTaskFunList[idx]()
}
println("Wrong task: $i")
}
}
fun main() {
while(true) {
tasks.getTask()
}
}
Is code is works. But i want to call my methods without lamda. When i tried to just use them by name i got "main.kt:4:43: error: function invocation 'hide()' expected"
in: val supportTaskFunList = listOf<taskFun> = listOf(hide, show, exit)
Yet, i can to use it like:
package cryptography
typealias taskFun = () -> Unit
// var i: Int = 0
object tasks {
val hide = fun() {
println("Hiding message in image.")
}
val show = fun() {
println("Obtaining message from image.")
}
val exit = fun() {
println("Bye!")
kotlin.system.exitProcess(0)
}
val supportTaskTextList = listOf("hide", "show", "exit")
val supportTaskFunList = listOf<taskFun>(hide, show, exit)
fun getTask() {
println("Task (${supportTaskTextList.joinToString(", ")}):")
val i = readln()
for (idx in supportTaskFunList.indices)
{
if (supportTaskTextList[idx] == i) return supportTaskFunList[idx]()
}
println("Wrong task: $i")
}
}
fun main() {
while(true) {
tasks.getTask()
}
}
May i use it without lamdas? Just, like as in C++ though pointers on methods? And why i can't use functions without lamdas here?

The correct syntax to reference a function by name is not using a plain name, but the :: syntax, for which you still need the name of hte object task I think. You could try just writing ::hide, ::show... but I am not sure it resolves here.,
val supportTaskFunList = listOf(tasks::hide, tasks::show, tasks::exit)
Short mention: This is a function type. According to the documentation this is not a lambda (in Kotlin terminology) as it does not define a new function or use the lambda syntax, just references an existing function.

Related

Read value from Kotlin flow and return immediately

I have to read a value from a flow just once and then immediately return it from the function. I have a piece of code like this:
fun getValue(): String {
val flow = getFlow()
Mainscope().launch {
flow.collect {
return it
}
}
}
But this is giving me an error saying that I cant return from inside collect. I know that ideally I should be returning the flow object itself and then calling collect on the returned flow. But it is not possible, since getValue() has already been used in several places by now and I cannot change its signature now.
I have tried using suspend and synchronized as follows:
// call the function like this: runBlocking { print(getValue()) }
suspend fun getValue(): String {
val flow = getFlow()
flow.collect {
return it
}
}
and
fun getValue(): String {
val lock = Any()
var value: String? = null
val flow = getFlow()
MainScope().launch {
flow.collect {
synchronized(lock) {
value = it.toString()
lock.notify()
}
}
}
synchronized(lock) {
while (value == null) lock.wait()
return value as String
}
}
But in both cases the control never reaches inside collect. So I tried putting collect inside a new thread:
...
val flow = getFlow()
thread {
MainScope().launch {
flow.collect {
synchronized(lock) {
value = it.toString()
lock.notify()
}
}
}
}
synchronized(lock) {
...
but its still the same. So how do I read the value from the flow in a non-suspending way and return it immediately?
To get first value of the flow:
fun getFlow() = flowOf("one","two","three")
fun getValue(): String {
var r = ""
runBlocking {
r = getFlow().firstOrNull()?:"none"
}
return r
}
println(getValue())
//one
To get last value of the flow:
fun getValue(): String {
var r = ""
runBlocking {
getFlow().collect {
r = it
}
}
return r
}
println(getValue())
//three

While loop doesn't seem to work with .putFile when uploading multiple images to Firebase storage in Kotlin

I have been trying to upload multiple images to Firebase Storage. But, I am not able to do it successfully. I could successfully upload the image (single) to the storage and add the URL of the image to the Firestore, now that I revised my code to upload up to five images, it could be any number of images from 1 to 5.
R.id.btn_submit -> {
if (validateDetails()) {
uploadImage()
}
}
The above code, calls the following function after validating the fields, which then calls the function uploadImageToCloudStorage. mSelectedImageFileUriList is private var mSelectedImageFileUriList: MutableList<Uri?>? = null. It all seems to work correctly.
private fun uploadImage() {
showProgressDialog(resources.getString(R.string.please_wait))
FirestoreClass().uploadImageToCloudStorage(
this#AddProductActivity,
mSelectedImageFileUriList,
Constants.PRODUCT_IMAGE,
Constants.PRODUCT_IMAGE_DIRECTORY_NAME,
et_product_title.text.toString().trim { it <= ' ' }
)
}
Following code is where I guess is a mistake.
fun uploadImageToCloudStorage(
activity: AddProductActivity,
imageFileURI: MutableList<Uri?>?,
imageType: String,
directoryName: String,
title: String
) {
var i = 0
val imageURLList = ArrayList<String>()
val itr = imageFileURI?.iterator()
if (itr != null) {
while (itr.hasNext()) {
val sRef: StorageReference = FirebaseStorage.getInstance().getReference(
"/$directoryName/" + imageType + "." + Constants.getFileExtension(
activity,
imageFileURI[i]
)
)
sRef.putFile(imageFileURI[i]!!)
.addOnSuccessListener { taskSnapshot ->
taskSnapshot.metadata!!.reference!!.downloadUrl
.addOnSuccessListener { uri ->
if (i < imageFileURI.size) {
i += 1
imageURLList.add(uri.toString())
} else {
activity.imageUploadSuccess(imageURLList)
}
}
}
.addOnFailureListener { exception ->
activity.hideProgressDialog()
Log.e(
activity.javaClass.simpleName,
exception.message,
exception
)
}
}
} else {
Toast.makeText(
activity,
"There is no images in the ArrayList of URI",
Toast.LENGTH_SHORT
).show()
}
}
EDIT: After receiving the first answer.
I have created a QueueSyn.kt file and added the code in the Answer. The activity where the images and the button are changed to
class AddProductActivity : BaseActivity(), View.OnClickListener, QueueSyncCallback {
The following function is called when the button is hit.
private fun uploadProductImage() {
showProgressDialog(resources.getString(R.string.please_wait))
QueueSync(
mSelectedImageFileUriList,
Constants.PRODUCT_IMAGE,
Constants.PRODUCT_IMAGE_DIRECTORY_NAME,
et_product_title.text.toString().trim { it <= ' ' },
this
).startUploading()
}
I have also implemented these two methods in the class AddProductActivity, but I don't know what should go inside this.
override fun completed(successList: MutableList<Uri>, failureList: MutableList<Uri>) {
TODO("Not yet implemented")
}
override fun getFileExtension(uri: Uri): String {
TODO("Not yet implemented")
}
Error:
This should work
import android.net.Uri
import com.google.firebase.storage.FirebaseStorage
import com.google.firebase.storage.StorageReference
import java.util.*
import kotlin.collections.ArrayList
interface QueueSyncCallback {
fun completed(successList: MutableList<Uri>, failureList: MutableList<Uri>)
fun getFileExtension(uri: Uri): String
}
class QueueSync(
imageFileURI: MutableList<Uri?>?,
private val imageType: String,
private val directoryName: String,
private val title: String,
private val callback: QueueSyncCallback,
private val maxActive: Int = 5
) {
private val queue: LinkedList<Uri> = LinkedList()
private val runningQueue: MutableList<Uri> = Collections.synchronizedList(
object : ArrayList<Uri>() {
override fun remove(element: Uri): Boolean {
val removed = super.remove(element)
if (isEmpty() && queue.isEmpty()) {
callback.completed(successList, failureList)
} else if (queue.isNotEmpty()) {
addToRunningQueue()
}
return removed
}
}
)
private val successList: MutableList<Uri> = Collections.synchronizedList(ArrayList())
private val failureList: MutableList<Uri> = Collections.synchronizedList(ArrayList())
init {
if (imageFileURI != null)
for (uri in imageFileURI) {
if (uri != null)
queue.add(uri)
}
}
private fun getLocation(uri: Uri) = "/$directoryName/$imageType.${callback.getFileExtension(uri)}"
fun startUploading() {
var i = 0
if (queue.isEmpty()) {
callback.completed(successList, failureList)
return
}
while (i < maxActive && queue.isNotEmpty()) {
addToRunningQueue()
i++
}
}
private fun addToRunningQueue() {
val uri = queue.poll()!!
runningQueue.add(uri)
uploadImageToCloudStorage(uri)
}
private fun uploadImageToCloudStorage(locationUri: Uri) {
val sRef: StorageReference = FirebaseStorage.getInstance().getReference(getLocation(locationUri))
sRef.putFile(locationUri)
.addOnSuccessListener { taskSnapshot ->
taskSnapshot.metadata!!.reference!!.downloadUrl
.addOnSuccessListener { uri ->
successList.add(uri)
runningQueue.remove(locationUri)
}
}
.addOnFailureListener {
failureList.add(locationUri)
runningQueue.remove(locationUri)
}
}
}
Since your need requires usage of threads so to prevent race conditions I had to use Collections.synchronizedList. To use this you need to implement QueueSyncCallback in your activity and pass it as a reference to QueueSync. Make sure that any piece of code written inside completed is wrapped inside runOnMainThread if it is going to access views in any way since completed will not run on main thread as far as I know. This should work however I am not able to test it since it is based on your current code.
Edit:- Answering after edit
override fun completed(successList: MutableList<Uri>, failureList: MutableList<Uri>) {
imageUploadSuccess(successList)
hideProgressDialog()
}
override fun getFileExtension(uri: Uri): String {
Constants.getFileExtension(this, imageFileURI[i])
}

Kotlin - Evaluate functions from code string

General Question:
I would like to run in a kotlin app some code stored as String.
fun Evaluate(str: String,f:(s : String) -> Unit )
{
f(str)
}
For example, an Hello World
var function : String = "fun a(s:String) = println(s)"
Evaluate ("Hello World",function)
Is this possible, or maybe something close to this result ?
Specific Question :
I have an activity containing a layout and a map of variable :
private lateinit var glayout: LinearLayout
val variable : MutableMap<String, Any> = mutableMapOf(),
val code : List<String>
override fun onCreate(savedInstanceState: Bundle?) {
//Some init
glayout = binding.root.findViewById(R.id.gamelayout)
code = getCodeFromJson()
for (c in code){
//Here execute the code
}
}
So i would like to be able in my interpreted code to :
Modify a variable in the map
Instanciate any kind of views in the layout, from text to button with onClickListener
Run some specific android commands, like record, photo and others
I think the most reasonable way is to write a interpreter using your own language.
abstract class Interpreter {
fun run(sentence: String) {
val input = sentence.trim().split(" ")
val cmd = input[0]
val args = input.drop(1)
execute(cmd, args)
}
protected abstract fun execute(command: String, args: List<String>)
}
For example, if you have a map and you want the user to modify it:
class MapInterpreter(private val map: MutableMap<String, String>) : Interpreter() {
override protected fun execute(command: String, args: List<String>) {
when (command) {
"putmap" -> {
require(args.size == 2) { "usage: addmap [key] [value]" }
map[args[0]] = args[1]
}
"remmap" -> {
require(args.size == 1) { "usage: remmap [key]" }
map.remove(args[0])
}
"showmap" -> {
require(args.size == 0) { "usage: showmap" }
println(map)
}
}
}
}
To use it, just call the run method with the user input (from a text field, for example):
val map: MutableMap<String, String> = hashMapOf()
val interpreter = MapInterpreter(map)
interpreter.run("putmap I one")
interpreter.run("putmap V five")
interpreter.run("putmap X ten")
interpreter.run("putmap 2 two")
interpreter.run("showmap")
interpreter.run("remmap 2")
interpreter.run("showmap")
// Output:
// {2=two, V=five, X=ten, I=one}
// {V=five, X=ten, I=one}
Another example; to instantiate a Android View dynamically:
class ViewBuilderInterpreter(private val context: Context, private val parent: View) : Interpreter() {
override protected fun execute(command: String, args: List<String>) {
when (command) {
"textview" -> {
require(args.size >= 1 && args.size <= 2) { "usage: textview [text] {color}" }
parent.addView(Text(context).also {
it.text = args[0]
it.color = if (args.size == 1) Color.BLACK else Color.parseColor(args[1])
})
}
// ...
}
}
}
Of course that's just an idea, you also need to handle invalid commands and exceptions that might occur.

Kotlin poet filet not getting generated

I tried to create a class with annotation processor and Kotlin Poet. This is my code:
#AutoService(Processor::class)
class TailProcessor : AbstractProcessor() {
override fun process(elementTypeSet: MutableSet<out TypeElement>?, roundEnvironment: RoundEnvironment?): Boolean {
roundEnvironment?.getElementsAnnotatedWith(Tail::class.java)?.forEach {
if (it.javaClass.kotlin.isData) {
print("You are doing it right")
val className = it.simpleName.toString()
val pack = processingEnv.elementUtils.getPackageOf(it).toString()
val variables = ElementFilter.fieldsIn(elementTypeSet)
startClassGeneration(className, pack, variables)
} else {
return false
}
}
return false
}
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
override fun getSupportedAnnotationTypes(): MutableSet<String> = mutableSetOf(Tail::class.java.name)
private fun startClassGeneration(
className: String,
pack: String,
variables: MutableSet<VariableElement>
) {
val fileName = "Tail$className"
val stringToBePrinted = generateStringFromIncommingValues(variables)
val printFunction = FunSpec.builder("print").addCode("print($stringToBePrinted)").build()
val generatedClass = TypeSpec.objectBuilder(fileName).addFunction(printFunction).build()
val file = FileSpec.builder(pack, fileName).addType(generatedClass).build()
val kaptKotlinGeneratedDir = processingEnv.options[KOTLIN_DIRECTORY_NAME]
file.writeTo(File(kaptKotlinGeneratedDir, "$fileName.kt"))
}
private fun generateStringFromIncommingValues(variables: MutableSet<VariableElement>): Any {
val stringBuilder = StringBuilder()
variables.forEach {
if (it.constantValue == null) {
stringBuilder.append("null\n ")
} else {
stringBuilder.append("${it.constantValue}\n")
}
}
return stringBuilder.toString()
}
companion object {
const val KOTLIN_DIRECTORY_NAME = "sxhardha.tail"
}
}
The problem, directory and file not generating. I tried to rebuild, invalidate cache + restart, clean but none of them works. The build goes successful without any errors but I see no changes. Can you check what is wrong?
I actually found the issue. I wasn't checking right if that class is a data class or not and the condition was never met.
Instead of:
it.javaClass.kotlin.isData
Should have been:
it.kotlinMetadata as KotlinClassMetadata).data.classProto.isDataClass
But it can only be achieved by using this library here.

Kotlin: return map of async(start = CoroutineStart.LAZY)

I'd like to return map of lazily-started coroutines and use them (start/cancel) in another function.
Problem is that getMap() function in following hangs. Why is that and is it possible to return such map from function?
import kotlinx.coroutines.*
suspend fun getMap(): LinkedHashMap<String, Deferred<Any>> {
return withContext(Dispatchers.Default) {
val map = linkedMapOf<String, Deferred<Any>>()
map["1"] = async(start = CoroutineStart.LAZY) { 1 }
map["2"] = async(start = CoroutineStart.LAZY) { 2 }
map;
}
}
fun main() {
runBlocking {
val map = getMap()
println("not happening")
}
}
withContext doesn't complete until all the coroutines launched within it complete. You can simplify your case to this:
fun main() {
runBlocking {
withContext(Dispatchers.Default) {
launch(start = CoroutineStart.LAZY) { 1 }
}
println("not happening")
}
}
It doesn't complete either. The reason you got into this is that you used withContext inappropriately. Your getMap() has no reason to be a suspend fun.
What you need instead of withContext is setting a coroutine scope for these async calls. For example, this will work:
fun getMap(): Map<String, Deferred<Any>> =
linkedMapOf<String, Deferred<Any>>().also { map ->
with(GlobalScope) {
map["1"] = async(start = CoroutineStart.LAZY) { 1 }
map["2"] = async(start = CoroutineStart.LAZY) { 2 }
}
}
fun main() {
val map = getMap()
println("now it's happening")
}
Here you're using the global coroutine scope so you don't get any automatic cancellation. If you want to take care of that concern, replace it with something else.