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.
Related
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.
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])
}
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.
I am a Java developer who just switched to Kotlin and I haven't completely unterstood it yet.
I cant understand why I cant use my obj in the other methods in my following code..?
I use kotlin on IntelliJ in combination with selneium(which shouldnt really make a difference, should it?)
I tried putting it out of the init method but that didnt work either...
Thanks in advance.
import org.openqa.selenium.By
import org.openqa.selenium.chrome.ChromeDriver
class MapsDriver()
{
init {
var obj : ChromeDriver = ChromeDriver()
}
fun convertToGmURL(string : String) : String{
var string_trimmed : String = ""
string.forEach {
if(it == ' '){
string_trimmed += "+"
}
else{
string_trimmed += it
}
}
return string_trimmed
}
fun searchMaps(search : String){
val searchConverted : String = convertToGmURL(search)
obj.get("https://www.google.com/maps/search/?api=1&query=$searchConverted")
}
fun getXpath(Xpath : String) : String{
loop#while(true) {
try {
var result = obj.findElementByXPath(Xpath)
return result.text
}
catch (NoSuchElementException : Exception){
continue#loop
}
}
}
fun getResultName(resultNumber : Int) : String{
val convertedIndex : Int = (resultNumber * 2) + 1
var result : String = getXpath("//*[#id=\"pane\"]/div/div[1]/div/div/div[4]/div[1]/div[$convertedIndex]/div[1]/div[1]/div[1]/div[2]/h3/span")
return result
}
fun getAllResults(){
Thread.sleep(1_000)
val amountIndexes = getXpath("//*[#id=\"pane\"]/div/div[1]/div/div/div[4]/div[2]/div/div[1]/span/span[2]")
val amountIndexesInt : Int = amountIndexes.toInt()
val resultsName : Array<String> = Array(amountIndexesInt){getResultName(it)}
resultsName.forEach { println(it) }
}
}
fun main(){
System.setProperty("webdriver.chrome.driver", "/home/josef/Java/cdriver/chromedriver")
val md : MapsDriver = MapsDriver()
md.searchMaps("Autohaus Muenchen")
md.getAllResults()
}
The issue is that obj is a variable local to the init method, so it's equivalent to the following Java code:
class MapsDriver {
public MapsDriver() {
ChromeDriver obj = new ChromeDriver();
// you can't use obj anywhere else as it's local to the constructor
}
}
If you want obj to be an instance variable, then you should do something like:
class MapsDriver {
private val obj = ChromeDriver() // instance variable, no need for init block in this case
...
}
I want to combine multiple data sources in a MediatorLiveData. Unfortunately, there are not many examples yet. So in my ViewModel I have
//all lists have been declared before
val playerList = MediatorLiveData<List<Player>>()
init {
playerList.addSource(footballPlayerList) { value ->
playerList.value = value
}
playerList.addSource(basketballPlayerList) { value ->
playerList.value = value
}
}
But apparently this will always override the current value of playerList. I mean I could build some hacky workarounds with helper variables like _playerList but maybe there is an easier solution?
Having done quite some research.. I found it out. Here is an example
fun blogpostBoilerplateExample(newUser: String): LiveData<UserDataResult> {
val liveData1 = userOnlineDataSource.getOnlineTime(newUser)
val liveData2 = userCheckinsDataSource.getCheckins(newUser)
val result = MediatorLiveData<UserDataResult>()
result.addSource(liveData1) { value ->
result.value = combineLatestData(liveData1, liveData2)
}
result.addSource(liveData2) { value ->
result.value = combineLatestData(liveData1, liveData2)
}
return result
}
The actual combination of data is done in a separate combineLatestData method like so
private fun combineLatestData(
onlineTimeResult: LiveData<Long>,
checkinsResult: LiveData<CheckinsResult>
): UserDataResult {
val onlineTime = onlineTimeResult.value
val checkins = checkinsResult.value
// Don't send a success until we have both results
if (onlineTime == null || checkins == null) {
return UserDataLoading()
}
// TODO: Check for errors and return UserDataError if any.
return UserDataSuccess(timeOnline = onlineTime, checkins = checkins)
}
Here is a simple example
class MergeMultipleLiveData : ViewModel() {
private val fictionMenu: MutableLiveData<Resource<RssMenu>> = MutableLiveData()
private val nonFictionMenu: MutableLiveData<Resource<RssMenu>> = MutableLiveData()
val allCategoryMenus: MediatorLiveData<Resource<RssMenu>> = MediatorLiveData()
init {
getFictionMenus()
getNonFictionMenus()
getAllCategoryMenus()
}
private fun getAllCategoryMenus() = viewModelScope.launch(Dispatchers.IO) {
allCategoryMenus.addSource(fictionMenu) { value ->
allCategoryMenus.value = value
}
allCategoryMenus.addSource(nonFictionMenu) { value ->
allCategoryMenus.value = value
}
}
private fun getFictionMenus() = viewModelScope.launch(Dispatchers.IO) {
fictionMenu.postValue( // todo )
}
private fun getNonFictionMenus() = viewModelScope.launch(Dispatchers.IO) {
nonFictionMenu.postValue( // todo )
}
}
And in your fragment you can observer as;
viewModel.allCategoryMenus.observe(viewLifecycleOwner) {
// todo
}