We are making custom lint for the purpose of applying them to the Android project.
The problematic part is the lint that requires the use of NamedArgument in the Composable function.
#Composable
fun MyComposable(
a: String,
b: Int,
onClick: () -> Unit = {},
) {}
#Composable
fun success1() {
MyComposable(
a = "success",
b = 1,
) {
}
}
I was thinking about how to determine if MyComposable is #Composable when calling MyComposable in the success1 function in the following cases:
class NamedArgumentDetector : Detector(), SourceCodeScanner {
override fun getApplicableUastTypes() = listOf(
UExpression::class.java,
)
override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
override fun visitExpression(node: UExpression) {
if (node !is KotlinUFunctionCallExpression || !node.isInvokedWithinComposable()) return
val firstMethodName = node.methodName?.first() ?: return
if (firstMethodName !in 'A'..'Z') return
val lastArgumentIndex = node.valueArguments.lastIndex
node.valueArguments.fastForEachIndexed { index, argument ->
if (index == lastArgumentIndex && argument is KotlinULambdaExpression) return
val expressionSourcePsi = argument.sourcePsi
val argumentParent = expressionSourcePsi?.node?.treeParent ?: return
val argumentFirstChildNode = argumentParent.firstChildNode
val argumentParentFirstChildNode = argumentParent.treeParent.firstChildNode
if (!(
argumentFirstChildNode.isValueArgumentName() ||
argumentParentFirstChildNode.isValueArgumentName()
)
) {
context.report(
issue = NamedArgumentIssue,
scope = expressionSourcePsi,
location = context.getLocation(expressionSourcePsi),
message = Explanation,
)
return
}
}
}
}
private fun ASTNode.isValueArgumentName() =
this.elementType == VALUE_ARGUMENT_NAME
}
Detector code. In the example above, we were able to check whether success1 invokes Composable, but I can't think of a way to check whether MyComposable called by success1 is a Composable function.
I would appreciate it if you let me know if there is a way to check.
I have an app that launches the majority of the time, but every 7 or so launches it crashes with the error:
kotlin.UninitializedPropertyAccessException: lateinit property weekdayList has not been initialized
This is a clear error, I'm just not sure how to ensure the variable is initialized early enough in the context of my app.
Things I have tried
I tried moving variables around, making "inner" and "outer"
variables, one within onCreate and an underscore led variable as
the class variable.
Changing the viewmodel so that it will wait until the call to the db has finished (I
couldn't make this work, but mostly because I wasn't sure how to do it).
I think the problem is in the onCreate function, and that the weekday observe isn't setting the value of the variable faster than the task observe (where the weekdayList variable is needed) is called?
Edit 1
I referenced this but I end up with a similar error
java.lang.IndexOutOfBoundsException: Empty list doesn't contain element at index 1.
Edit 2
I understand how lateinit variables and nullables work at this point, I want to try and clarify this a little better.
The variable weekdayList needs to be initialized to the correct list before I hit the observe for the taskList, otherwise the app will crash.
I have tried setting the variable to be nullable, and Iend up with:
skipping parts of the program when it's null (not an option)
crashing with a null pointer exception (if set to non-nullable)
no tasks get assigned to any day, which means no recyclerviews get updated, thus making the app appear to contain no tasks when it does.
weekday buttons that don't work because there is no weekdayList for them to compare against to launch the next activity
My problem doesn't stand in figuring out if it's null or not, it's trying to guarantee that it won't be null.
Sorry for the confusion
Main Activity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val plannerViewModel: PlannerViewModel by viewModels {
PlannerViewModelFactory((application as PlannerApplication).repository)
}
private var weekdayList: List<Weekday> = listOf()
private var taskList: List<Task> = listOf()
private var taskDayList = mutableListOf<Task>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val clearButtonText = binding.clearCardText
val sundayButtonText = binding.sundayCardText
val mondayButtonText = binding.mondayCardText
val tuesdayButtonText = binding.tuesdayCardText
val wednesdayButtonText = binding.wednesdayCardText
val thursdayButtonText = binding.thursdayCardText
val fridayButtonText = binding.fridayCardText
val saturdayButtonText = binding.saturdayCardText
val sundayRv: RecyclerView = binding.sundayRv
val sundayAdapter = TaskRvAdapter(null)
sundayRv.adapter = sundayAdapter
sundayRv.layoutManager = LinearLayoutManager(this)
val mondayRv: RecyclerView = binding.mondayRv
val mondayAdapter = TaskRvAdapter(null)
mondayRv.adapter = mondayAdapter
mondayRv.layoutManager = LinearLayoutManager(this)
val tuesdayRv: RecyclerView = binding.tuesdayRv
val tuesdayAdapter = TaskRvAdapter(null)
tuesdayRv.adapter = tuesdayAdapter
tuesdayRv.layoutManager = LinearLayoutManager(this)
val wednesdayRv: RecyclerView = binding.wednesdayRv
val wednesdayAdapter = TaskRvAdapter(null)
wednesdayRv.adapter = wednesdayAdapter
wednesdayRv.layoutManager = LinearLayoutManager(this)
val thursdayRv: RecyclerView = binding.thursdayRv
val thursdayAdapter = TaskRvAdapter(null)
thursdayRv.adapter = thursdayAdapter
thursdayRv.layoutManager = LinearLayoutManager(this)
val fridayRv: RecyclerView = binding.fridayRv
val fridayAdapter = TaskRvAdapter(null)
fridayRv.adapter = fridayAdapter
fridayRv.layoutManager = LinearLayoutManager(this)
val saturdayRv: RecyclerView = binding.saturdayRv
val saturdayAdapter = TaskRvAdapter(null)
saturdayRv.adapter = saturdayAdapter
saturdayRv.layoutManager = LinearLayoutManager(this)
// Setting day card names
clearButtonText.text = "Clear"
sundayButtonText.text = "Sun"
mondayButtonText.text = "Mon"
tuesdayButtonText.text = "Tue"
wednesdayButtonText.text = "Wed"
thursdayButtonText.text = "Thu"
fridayButtonText.text = "Fri"
saturdayButtonText.text = "Sat"
sundayButtonText.text = "Sun"
plannerViewModel.allWeekdays.observe(this, {
weekdayList = it
})
plannerViewModel.allTasks.observe(this, { tasks ->
taskList = tasks
taskDayList = mutableListOf()
for (i in 1..7) {
taskDayList = sortTasks(weekdayList[i], taskList)
when (i) {
1 -> {
sundayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.sundayInner,
binding.sundayCardText, sundayRv, binding.sundayNoTasks)
}
2 -> {
mondayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.mondayInner,
binding.mondayCardText, mondayRv, binding.mondayNoTasks)
}
3 -> {
tuesdayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.tuesdayInner,
binding.tuesdayCardText, tuesdayRv, binding.tuesdayNoTasks)
}
4 -> {
wednesdayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.wednesdayInner,
binding.wednesdayCardText, wednesdayRv, binding.wednesdayNoTasks)
}
5 -> {
thursdayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.thursdayInner,
binding.thursdayCardText, thursdayRv, binding.thursdayNoTasks)
}
6 -> {
fridayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.fridayInner,
binding.fridayCardText, fridayRv, binding.fridayNoTasks)
}
7 -> {
saturdayAdapter.submitList(taskDayList)
toggleVisibility(taskDayList, binding.saturdayInner,
binding.saturdayCardText, saturdayRv, binding.saturdayNoTasks)
}
}
}
})
}
private fun toggleVisibility(taskDayList: List<Task>, inner: ConstraintLayout,
cardText: View, rv: RecyclerView, noTask: View) {
if (taskDayList.count() == 0 ) {
val newConstraintSet = ConstraintSet()
newConstraintSet.clone(inner)
newConstraintSet.connect(noTask.id, ConstraintSet.TOP,
cardText.id, ConstraintSet.BOTTOM)
newConstraintSet.applyTo(inner)
newConstraintSet.connect(cardText.id, ConstraintSet.BOTTOM,
noTask.id, ConstraintSet.TOP)
newConstraintSet.applyTo(inner)
rv.visibility = View.GONE
noTask.visibility = View.VISIBLE
Log.i("this", "ran zero")
} else {
val newConstraintSet = ConstraintSet()
newConstraintSet.clone(inner)
newConstraintSet.connect(rv.id, ConstraintSet.TOP,
cardText.id, ConstraintSet.BOTTOM)
newConstraintSet.applyTo(inner)
newConstraintSet.connect(cardText.id, ConstraintSet.BOTTOM,
rv.id, ConstraintSet.TOP)
newConstraintSet.applyTo(inner)
rv.visibility = View.VISIBLE
noTask.visibility = View.GONE
Log.i("this", "ran else")
}
}
private fun sortTasks(day: Weekday, tasks: List<Task>): MutableList<Task> {
val newAdapterList = mutableListOf<Task>()
tasks.forEach {
if (it.weekdayId == day.id) {
newAdapterList.add(it)
}
}
return newAdapterList
}
private fun startWeekdayActivity(day: Weekday) {
val intent = Intent(this, WeekdayActivity::class.java)
intent.putExtra("dayId", day.id)
this.startActivity(intent)
}
private fun clearDb(taskList: List<Task>) {
val alertDialog: AlertDialog = this.let { outerIt ->
val builder = AlertDialog.Builder(outerIt)
builder.apply {
setPositiveButton("Clear",
DialogInterface.OnClickListener { dialog, id ->
if (taskList.count() == 0) {
Toast.makeText(context, "No tasks to clear", Toast.LENGTH_SHORT).show()
} else {
plannerViewModel.deleteAllTasks()
Toast.makeText(context, "Tasks cleared", Toast.LENGTH_SHORT).show()
}
})
setNegativeButton("Cancel",
DialogInterface.OnClickListener { dialog, id ->
// User cancelled the dialog
})
}
.setTitle("Clear tasks?")
.setMessage("Are you sure you want to clear the weeks tasks?")
builder.create()
}
alertDialog.show()
}
private fun checkDay(dayIn: String, weekdayList: List<Weekday>) {
weekdayList.forEach {
if (dayIn == "clear_card" && it.day == "Clear") {
clearDb(taskList)
} else {
val dayInAbr = dayIn.substring(0, 3).toLowerCase(Locale.ROOT)
val dayOutAbr = it.day.substring(0, 3).toLowerCase(Locale.ROOT)
if (dayInAbr == dayOutAbr) {
startWeekdayActivity(it)
}
}
}
}
fun buttonClick(view: View) {
when (view.id) {
R.id.clear_card -> checkDay(view.context.resources.getResourceEntryName(R.id.clear_card).toString(), weekdayList)
R.id.sunday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.sunday_card).toString(), weekdayList)
R.id.monday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.monday_card).toString(), weekdayList)
R.id.tuesday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.tuesday_card).toString(), weekdayList)
R.id.wednesday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.wednesday_card).toString(), weekdayList)
R.id.thursday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.thursday_card).toString(), weekdayList)
R.id.friday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.friday_card).toString(), weekdayList)
R.id.saturday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.saturday_card).toString(), weekdayList)
}
}
}
Viewmodel
class PlannerViewModel(private val repository: DbRepository) : ViewModel() {
val allWeekdays: LiveData<List<Weekday>> = repository.allWeekdays.asLiveData()
val allTasks: LiveData<List<Task>> = repository.allTasks.asLiveData()
fun insertWeekday(weekday: Weekday) = viewModelScope.launch {
repository.insertWeekday(weekday)
}
fun insertTask(task: Task) = viewModelScope.launch {
repository.insertTask(task)
}
fun deleteTask(task: Task) = viewModelScope.launch {
repository.deleteTask(task)
}
fun deleteAllTasks() = viewModelScope.launch {
repository.deleteAllTasks()
}
}
class PlannerViewModelFactory(private val repository: DbRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(PlannerViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return PlannerViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
A variable declared as lateinit just means that you are sure that when the object is dereferenced it will not be null. In your case you are calling a method from weekdayList object before it is assigned a value. It is important to understand the concept clearly and why your code works.
Happy Coding!
You can use the "isInitialized" method, for checking "lateinit" variable is initialized or not.
Please refer the below article for the this-
https://blog.mindorks.com/how-to-check-if-a-lateinit-variable-has-been-initialized
lateinit is a way for you to have a var without an initial value when you declare it. It's a nice way to avoid taking something that will never be null, and making it nullable (and having to null check it forever) just so you can temporarily set it to null as a placeholder that nothing will ever see.
What you're doing is promising the compiler "ok, I'm not going to provide a value when the class is constructed, but I promise I'll set it to something before anything tries to read it". You're telling the compiler to trust you, that you know how your code works, and you can guarantee it'll all be ok.
Your problem is that it seems you can't guarantee that things won't try to read that property before you write to it. Your state can either be "has a value", or "doesn't have a value", and the rest of your code could encounter either state.
The "no value" state is basically null, so you should probably make the variable nullable instead, and initialise it as null. Kotlin has all that nice null-safety stuff to help your code handle it, until you do get a value. lateinit seems like the wrong tool for the job, even if you check ::isInitialized it's just making your life a lot harder when the null-checking stuff is right there!
Use lazy properties , refer to this doc for more informations:
assuming weekDayList is the property you want to successfully initialize ->
private var weekDayList: List<WeekDay> by lazy {
//return your first value
listOf<WeekDay>()
}
Here is a useful link about LifeCycleAware Lazy properties: blog Although, it is not required.
A solution with help from cactustictacs in the comments.
I moved a lot of the list dependency to a new function called setAdapterList. this allows both observes to run the function, and only the one with both lists initialized will run the code contained. I kept the variables lateinit and it seems to be working so far!
The Main Change in Main Activity
...
private fun setAdapterLists(adapterList: List<TaskRvAdapter>, rvList: List<RecyclerView>) {
if (this::weekdayList.isInitialized && this::taskList.isInitialized) {
adapterList.forEach {
taskDayList = mutableListOf()
val i = adapterList.indexOf(it)
taskDayList = sortTasks(weekdayList[i + 1], taskList)
Log.i("rvli", rvList[i].toString())
when (i) {
0 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.sundayInner,
binding.sundayCardText, rvList[i], binding.sundayNoTasks)
}
1 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.mondayInner,
binding.mondayCardText, rvList[i], binding.mondayNoTasks)
}
2 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.tuesdayInner,
binding.tuesdayCardText, rvList[i], binding.tuesdayNoTasks)
}
3 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.wednesdayInner,
binding.wednesdayCardText, rvList[i], binding.wednesdayNoTasks)
}
4 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.thursdayInner,
binding.thursdayCardText, rvList[i], binding.thursdayNoTasks)
}
5 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.fridayInner,
binding.fridayCardText, rvList[i], binding.fridayNoTasks)
}
6 -> {
adapterList[i].submitList(taskDayList)
toggleVisibility(taskDayList, binding.saturdayInner,
binding.saturdayCardText, rvList[i], binding.saturdayNoTasks)
}
}
}
}
}
...
I'm trying to make random password generator based on user input and everything is fine until i use .toCharArray().shuffle() function, but without shuffling it's too predictable beacuse it puts letters in pre-determined positions. Is there any way this code would work? Any workaround? I already tried stringbuilder but it bypasses user input so I don't know what to do now.
val chars= "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~##$%^&*()!"
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser)
{
when(seekBar)
{
sbNumberOfLetters ->
{
tvLetterCount.text = progress.toString()
smallLetters = progress
}
sbNumberOfCapitalLetters ->
{
tvCapitalsCount.text = progress.toString()
capitalLetterNumber = progress
}
sbNumberOfNumerals ->
{
tvNumeralsCount.text = progress.toString()
numeralsNumber = progress
}
sbNumberOfSpecialChars ->
{
tvSpecialCharsCount.text = progress.toString()
specialCharNumber = progress
}
}
}
}
private fun generatePassword() {
for (y in 1..numeralsNumber)
{
var randomLetter = Random.nextInt(0, 9)
listOfLetters.add(chars[randomLetter].toString())
}
for (w in 1..smallLetters)
{
var randomLetter = Random.nextInt(10, 36)
listOfLetters.add(chars[randomLetter].toString())
}
for (x in 1..capitalLetterNumber)
{
var randomLetter = Random.nextInt(36, 62)
listOfLetters.add(chars[randomLetter].toString())
}
for (z in 1..specialCharNumber)
{
var randomLetter = Random.nextInt(63, 73)
listOfLetters.add(chars[randomLetter].toString())
}
password = (listOfLetters.joinToString(separator = "",)).toCharArray().shuffle().toString()
tvGeneratedPassword.text = password
listOfLetters.clear()
}
shuffle returns Unit, so calling toString() on Unit will return kotlin.Unit which is defined in Unit. Try this:
listOfLetters.shuffle()
val password = listOfLetters.joinToString(separator = "")
tvGeneratedPassword.text = password
Might as well use the tools that Kotlin provides to write expressive and maintainable code ( are the indexes for Random.nextInt in the code you provided correct? ) . Here is one alternative (here lists and not arrays are used and shuffled() returns a new list ) :
abstract sealed class RandomChars {
open val chars: CharArray = charArrayOf()
fun get(count: Int) = (1..count).map { chars[Random.nextInt(0, chars.size)] }
}
object RandomDigits : RandomChars() {
override val chars = "0123456789".toCharArray()
}
object RandomLowerCase : RandomChars() {
override val chars = "abcdefghijklmnopqrstuvwxyz".toCharArray()
}
object RandomUpperCase : RandomChars() {
override val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray()
}
object RandomSpecial : RandomChars() {
override val chars = "~##$%^&*()!".toCharArray()
}
fun main() {
val password =
(RandomDigits.get(1) + RandomLowerCase.get(2) + RandomUpperCase.get(2) + RandomSpecial.get(1))
.shuffled()
.joinToString(separator = "")
println(password) // e.g. %Oa7Mt
}
Here is another, more functional approach:
val charGenerator =
{ alphabet: CharArray -> { count: Int -> (1..count).map { alphabet[Random.nextInt(0, alphabet.size)] } } }
val randomDigits = charGenerator("0123456789".toCharArray())
val randomLowecase = charGenerator("abcdefghijklmnopqrstuvwxyz".toCharArray())
val randomUppercase = charGenerator("ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray())
val randomSpecial = charGenerator("~##$%^&*()!".toCharArray())
fun main() {
val password =
(randomDigits(1) + randomLowecase(2) + randomUppercase(2) + randomSpecial(1))
.shuffled()
.joinToString(separator = "")
println(password) // e.g. %Oa7Mt
}
I am trying to return a list from inside firestore function based on if a condition is true.I want to return different lists when different categories are selected.
I tried:
putting the return statement out of firestore function which did not work and returned empty list due to firestore async behaviour.
creating my own callback to wait for Firestore to return the data using interface as I saw in some other questions but in that case how am i supposed to access it as my function has a Int value(i.e.private fun getRandomPeople(num: Int): List<String>)?
What could be the way of returning different lists for different categories based on firestore conditions?
My code(Non Activity class):
class Board// Create new game
(private val context: Context, private val board: GridLayout) {
fun newBoard(size: Int) {
val squares = size * size
val people = getRandomPeople(squares)
createBoard(context, board, size, people)
}
fun createBoard(context: Context, board: GridLayout, size: Int, people: List<String>) {
destroyBoard()
board.columnCount = size
board.rowCount = size
var iterator = 0
for(col in 1..size) {
for (row in 1..size) {
cell = RelativeLayout(context)
val cellSpec = { GridLayout.spec(GridLayout.UNDEFINED, GridLayout.FILL, 1f) }
val params = GridLayout.LayoutParams(cellSpec(), cellSpec())
params.width = 0
cell.layoutParams = params
cell.setBackgroundResource(R.drawable.bordered_rectangle)
cell.gravity = Gravity.CENTER
cell.setPadding(5, 0, 5, 0)
text = TextView(context)
text.text = people[iterator++]
words.add(text.text as String)
text.maxLines = 5
text.setSingleLine(false)
text.gravity = Gravity.CENTER
text.setTextColor(0xFF000000.toInt())
cell.addView(text)
board.addView(cell)
cells.add(GameCell(cell, text, false, row, col) { })
}
}
}
private fun getRandomPeople(num: Int): List<String> {
val mFirestore: FirebaseFirestore=FirebaseFirestore.getInstance()
val mAuth: FirebaseAuth=FirebaseAuth.getInstance()
val currentUser: FirebaseUser=mAuth.currentUser!!
var validIndexes :MutableList<Int>
var chosenIndexes = mutableListOf<Int>()
var randomPeople = mutableListOf<String>()
mFirestore.collection("Names").document(gName).get().addOnSuccessListener(OnSuccessListener<DocumentSnapshot>(){ queryDocumentSnapshot->
var categorySelected:String=""
if (queryDocumentSnapshot.exists()) {
categorySelected= queryDocumentSnapshot.getString("selectedCategory")!!
print("categoryselected is:$categorySelected")
Toast.makeText(context, "Got sel category from gameroom:$categorySelected", Toast.LENGTH_LONG).show()
when(categorySelected){
"CardWords"->{
for (i in 1..num) {
validIndexes=(0..CardWords.squares.lastIndex).toMutableList()
val validIndexIndex = (0..validIndexes.lastIndex).random()
val peopleIndex = validIndexes[validIndexIndex]
chosenIndexes.add(peopleIndex)
val person = CardWords.squares[peopleIndex]
randomPeople.add(person)
validIndexes.remove(peopleIndex)
peopleIndexes = chosenIndexes.toList()
}
}
else->{}
}
}
else {
Toast.makeText(context, "Sel category does not exist", Toast.LENGTH_LONG).show()
}
}).addOnFailureListener(OnFailureListener { e->
val error=e.message
Toast.makeText(context,"Error:"+error, Toast.LENGTH_LONG).show()
})
return randomPeople.toList()
}
}
Activity A:
Board(this, gridLay!!)
I wanna sort some strings that contain numbers but after a sort, it becomes like this ["s1", "s10", "s11", ... ,"s2", "s21", "s22"]. after i search i fount this question with same problem. but in my example, I have mutableList<myModel>, and I must put all string in myModel.title for example into a mutable list and place into under code:
val sortData = reversedData.sortedBy {
//pattern.matcher(it.title).matches()
Collections.sort(it.title, object : Comparator<String> {
override fun compare(o1: String, o2: String): Int {
return extractInt(o1) - extractInt(o2)
}
fun extractInt(s: String): Int {
val num = s.replace("\\D".toRegex(), "")
// return 0 if no digits found
return if (num.isEmpty()) 0 else Integer.parseInt(num)
}
})
}
I have an error in .sortedBy and Collections.sort(it.title), may please help me to fix this.
you can use sortWith instead of sortBy
for example:
class Test(val title:String) {
override fun toString(): String {
return "$title"
}
}
val list = listOf<Test>(Test("s1"), Test("s101"),
Test("s131"), Test("s321"), Test("s23"), Test("s21"), Test("s22"))
val sortData = list.sortedWith( object : Comparator<Test> {
override fun compare(o1: Test, o2: Test): Int {
return extractInt(o1) - extractInt(o2)
}
fun extractInt(s: Test): Int {
val num = s.title.replace("\\D".toRegex(), "")
// return 0 if no digits found
return if (num.isEmpty()) 0 else Integer.parseInt(num)
}
})
will give output:
[s1, s21, s22, s23, s101, s131, s321]
A possible solution based on the data you posted:
sortedBy { "s(\\d+)".toRegex().matchEntire(it)?.groups?.get(1)?.value?.toInt() }
Of course I would move the regex out of the lambda, but it is a more concise answer this way.
A possible solution can be this:
reversedData.toObservable()
.sorted { o1, o2 ->
val pattern = Pattern.compile("\\d+")
val matcher = pattern.matcher(o1.title)
val matcher2 = pattern.matcher(o2.title)
if (matcher.find()) {
matcher2.find()
val o1Num = matcher.group(0).toInt()
val o2Num = matcher2.group(0).toInt()
return#sorted o1Num - o2Num
} else {
return#sorted o1.title?.compareTo(o2.title ?: "") ?: 0
}
}
.toList()
.subscribeBy(
onError = {
it
},
onSuccess = {
reversedData = it
}
)
As you state that you need a MutableList, but don't have one yet, you should use sortedBy or sortedWith (in case you want to work with a comparator) instead and you get just a (new) list out of your current one, e.g.:
val yourMutableSortedList = reversedData.sortedBy {
pattern.find(it)?.value?.toInt() ?: 0
}.toMutableList() // now calling toMutableList only because you said you require one... so why don't just sorting it into a new list and returning a mutable list afterwards?
You may want to take advantage of compareBy (or Javas Comparator.comparing) for sortedWith.
If you just want to sort an existing mutable list use sortWith (or Collections.sort):
reversedData.sortWith(compareBy {
pattern.find(it)?.value?.toInt() ?: 0
})
// or using Java imports:
Collections.sort(reversedData, Compatarator.comparingInt {
pattern.find(it)?.value?.toInt() ?: 0 // what would be the default for non-matching ones?
})
Of course you can also play around with other comparator helpers (e.g. mixing nulls last, or similar), e.g.:
reversedData.sortWith(nullsLast(compareBy {
pattern.find(it)?.value
}))
For the samples above I used the following Regex:
val pattern = """\d+""".toRegex()
I wrote a custom comparator for my JSON sorting. It can be adapted from bare String/Number/Null
fun getComparator(sortBy: String, desc: Boolean = false): Comparator<SearchResource.SearchResult> {
return Comparator { o1, o2 ->
val v1 = getCompValue(o1, sortBy)
val v2 = getCompValue(o2, sortBy)
(if (v1 is Float && v2 is Float) {
v1 - v2
} else if (v1 is String && v2 is String) {
v1.compareTo(v2).toFloat()
} else {
getCompDefault(v1) - getCompDefault(v2)
}).sign.toInt() * (if (desc) -1 else 1)
}
}
private fun getCompValue(o: SearchResource.SearchResult, sortBy: String): Any? {
val sorter = gson.fromJson<JsonObject>(gson.toJson(o))[sortBy]
try {
return sorter.asFloat
} catch (e: ClassCastException) {
try {
return sorter.asString
} catch (e: ClassCastException) {
return null
}
}
}
private fun getCompDefault(v: Any?): Float {
return if (v is Float) v else if (v is String) Float.POSITIVE_INFINITY else Float.NEGATIVE_INFINITY
}