I have a problem. In my xml file, I have multiple textViews with the following id's:
txtRow1Column1
txtRow1Column2
txtRow2Column1
txtRow2Column2
txtRow3Column1
txtRow3Column2
txtRow4Column1
txtRow4Column2
Now I filled an array with random numbers between 1..2 and using the 1 or 2 I want to add a text in the textview from the resources. For that I already have the following function:
private fun startNewRound() {
// Define the array for (row, column, value)
val rows = arrayOf<Array<Int>>();
// Fill the array for each row and for each column
for (row in 1..4) {
rows.set(row, emptyArray())
for (column in 1..2) {
rows[row].plus((1..2).random())
}
}
// Show the correct text using the filled array
for (columns in rows) {
for (value in columns) {
val id = "txtRow" + rows.indexOf(columns) + "Column" + (columns.indexOf(value) + 1)
when (value) {
1 -> binding."ID HERE".text = getString(R.string.strTrue)
2 -> binding."ID HERE".text = getString(R.string.strFalse)
}
}
}
}
Currently I have added "ID HERE" instead of the id of the view, because I need to use a string as id to bind to the view. My question is: How can I use that string to find the correct id and be able to bind to that specific view?
Side note, the way you're building that 2D array looks like O(n^2), even if it wasn't going to throw IndexOutOfBoundsExceptions.
The way you're getting the column index won't work. Suppose you have the value 1 twice in a column. indexOf is going to return 0 both times because it returns the first valid match. Also, using indexOf inside your 2D loop results in O(n^3) complexity when it could just be O(n).
As to your problem, you can use Resources.getIdentifier() to find the Int ID of a view so you can find the view using findViewById. The alternative would be to use reflection on the binding class to find a field with that name, but that's uglier.
fun <T> View.getChildWithName(name: String): T {
val id = context.resources.getIdentifier("id", name, context.packageName)
return findViewById(id)
}
private fun startNewRound() {
val rows = Array(4) { Array(2) { (1..2).random() } }
rows.forEachIndexed { rowIndex, row ->
row.forEachIndexed { colIndex, value ->
val id = "txtRow${rowIndex + 1}Column${colIndex + 1}"
val view = binding.root.getChildWithName<TextView>(id)
view.text = when (value) {
1 -> getString(R.string.strTrue)
2 -> getString(R.string.strFalse)
}
}
}
}
Related
How to make list of random numbers and use it to set image from array?
val randomValues = List(15) { Random.nextInt(0, 5) }
var array = intArrayOf(R.drawable.cat1,R.drawable.cat2,R.drawable.cat3,R.drawable.cat4,R.drawable.cat5)
imageView.setImageResource(array[randomValues])
I'm getting Type mismatch on randomValues in imageView.setImageResource(array[randomValues]). Required: Int and Found: List <int>.
Edited
val randomValues = List(15) { Random.nextInt(0, 5) }
var array = intArrayOf(R.drawable.cat1,R.drawable.cat2,R.drawable.cat3,R.drawable.cat4,R.drawable.cat5)
imageView.setOnClickListener {
randomValues
.map { array[it] }
.forEach { imageView.setImageResource(it) }
}
If you want to select a new random image at each click you just need to do:
val array = intArrayOf(R.drawable.cat1, R.drawable.cat2, R.drawable.cat3, R.drawable.cat4, R.drawable.cat5)
imageView.setOnClickListener {
imageView.setImageResource(array.random())
}
If you absolutely need to use a predefined list of random values (what's the point?), then you need to track the last index you used. Something like:
// in your class
var lastIndex = 0
val randomValues = List(15) { Random.nextInt(0, 5) }
// setting the listener
val array = intArrayOf(R.drawable.cat1, R.drawable.cat2, R.drawable.cat3, R.drawable.cat4, R.drawable.cat5)
imageView.setOnClickListener {
imageView.setImageResource(array[randomValues[lastIndex]])
lastIndex = (lastIndex + 1) % randomValues.size
}
If you'd like to select simply a random element from the array, you can use the Array.random() method which just returns a random element from the array:
var array = intArrayOf(R.drawable.cat1,R.drawable.cat2,R.drawable.cat3,R.drawable.cat4,R.drawable.cat5)
imageView.setImageResource(array.random())
Edit
If you want to select a list of resources based on a randomly generated list of indices, you can achieve this by transforming every index into the right resource. Then you can perform your action on every selected item using the forEach method:
var array = intArrayOf(R.drawable.cat1,R.drawable.cat2,R.drawable.cat3,R.drawable.cat4,R.drawable.cat5)
val randomValues = List(15) { Random.nextInt(0, 5) }
randomValues
.map { array[it] }
.forEach { imageView.setImageResource(it) }
Basically your approach failed because you tried to use the entire randomValues list as a single index value. Instead you should iterate over the list in one way or another and select the resource for each randomly generated number.
How can I implement text completion,Like Gmail's smart compose?
I've an edit text where the user enters server address and I want to detect when they start typing the domain suffix and suggest completion.
Thanks.
First you need an algorithm to get suggestion from a given dictionary.
I've created a simple class named SuggestionManager to get suggestion from a given dictionary for a string input. Instead of returning the full match, it'll only return the remaining part of the given input. Below given a simple unit test along with full source code of the class. You can also go here to run the test online.
SuggestionManager.kt
class SuggestionManager(private val dictionary: Array<String>) {
companion object {
private val WORD_SPLIT_REGEX = Regex("[^A-Za-z0-9'\\-]")
/**
* To get reversed list
*/
private fun getReversedList(list: List<String>): MutableSet<String> {
val reversed = mutableSetOf<String>()
for (item in list.withIndex()) {
if (item.index != 0) {
val rev = list.subList(list.size - item.index, list.size).joinToString(" ")
reversed.add(rev)
}
}
// finally, add the full string
reversed.add(list.joinToString(" "))
return reversed
}
}
fun getSuggestionFor(_text: String?): String? {
var text = _text
// empty text
if (text.isNullOrBlank()) {
return null
}
// Getting last line only
if (text.contains("\n")) {
text = text.split("\n").last()
if (text.trim().isEmpty()) {
return null
}
}
// Splitting words by space
val words = text.split(WORD_SPLIT_REGEX).filter { it.isNotBlank() }
// Getting last word
val lastWord = if (text.endsWith(" ")) "${words.last()} " else words.last()
// Matching if last word exist in any dictionary
val suggestions = mutableSetOf<String>()
for (dicItem in dictionary) {
if (dicItem.contains(lastWord, true)) {
// Storing founded matches
suggestions.add(dicItem)
}
}
// Reverse ordering split-ed words
val pyramidWords = getReversedList(words)
val matchList = mutableListOf<String>()
for (pw in pyramidWords) {
for (sug in suggestions) {
// Storing suggestions starts with reversed word
if (sug.startsWith(pw, true)) {
matchList.add("$pw:$sug")
}
}
}
// Checking if second level match is not empty
if (matchList.isNotEmpty()) {
// Ordering by matched reversed word - (largest key first)
matchList.sortBy { -it.split(":")[0].length }
// Looping through in ascending order
for (m in matchList) {
val match = m.split(":")
val selPw: String = match[0]
var selSug: String = match.subList(1, match.size).joinToString(":")
// trimming to
selSug = selSug.replace(selPw, "", true)
if (text.endsWith(" ")) {
selSug = selSug.trim()
}
return selSug
}
}
return null
}
}
Unit Test
class SuggestionManagerUrlTest {
private val suggestionManager by lazy {
val dictionary = arrayOf(
"google.com",
"facebook.com",
"gmail.com",
"yahoo.com",
"192.168.354.45"
)
SuggestionManager(dictionary)
}
#Test
fun test() {
// null of empty and null input
assertNull(suggestionManager.getSuggestionFor(null))
assertNull(suggestionManager.getSuggestionFor(""))
// matching
assertEquals("ogle.com", suggestionManager.getSuggestionFor("go"))
assertEquals("book.com", suggestionManager.getSuggestionFor("face"))
assertEquals(".168.354.45", suggestionManager.getSuggestionFor("192"))
// no match
assertNull(suggestionManager.getSuggestionFor("somesite"))
}
}
Then, you'll have to set text in EditText with two colors. One for input and other for the suggestion. You may use the Html.fromHtml method to do this.
val text = "<font color=#cc0029>$input</font> <font color=#ffcc00>$suggestion</font>";
yourEditText.setText(Html.fromHtml(text));
Combining these two aspects, you can create a custom EditText.
I got this mutablelist:
[Videos(id=4, yt_id=yRPUkDjwr1A, title=test4, likes=0, kat=pranks, ilike=false), Videos(id=3, yt_id=WkyUU9ZDUto, title=test3, likes=0, kat=pranks, ilike=false), Videos(id=2, yt_id=B_X9OQqtduE, title=test2, likes=0, kat=animals, ilike=false), Videos(id=1, yt_id=ywaKlGNiv80, title=test1, likes=0, kat=animals, ilike=false)]
How can I change ilike to true where id is 2
This is what I've tried:
for (i in 0 until vids!!.size) {
Log.d("lets", vids!!.get(i).title)
if(vids!!.get(i).id == 2){
vids!!.get(i).ilike = true
}
}
You can use find function to find the element with id = 2 and change its property:
vids?.find { it.id == 2 }?.iLike = true
Note: it is a good practice to use question mark if the property is nullable and you unsure whether it is null or not.
If you expect few items (maybe 1 or 2?) to be affected,
you can filter the list and then change iLike of the filtered items:
vids!!.filter { it.id == 2 }.forEach { it.iLike = true }
Try this, I'm assuming your Videos structure is a data class defined somewhat like so. data class Videos(val id: Int, val yt_id: String, val title: String, val likes: Int, val kat: String, val ilike: Boolean)
list.forEachIndexed { index, video ->
video.takeIf { it.id == 2}?.let {
list[index] = it.copy(ilike = true)
}
}
I had to change several properties and I had a need to hold the changed object. Therefore following approach worked better for me:
//First, find the position of the video in the list
val videoPosition= list.indexOfFirst {
it.id == 2
}
//Now get your video by position and make changes
val updatedVideo = list[videoPosition].apply {
//Make all changes you need here
ilike = true
//...
}
//Finally, replace updated video into your list.
list[videoPosition] = updatedVideo
Use set to replace the object if you don't want to use predicates or iteration
Eg.
val video = (...,read = true) //or however you are getting the current model
val updatedVideo = video
updatedVideo.read = true
vids[vids.indexOf(video)] = updatedVideo
After a lot of searching, I know its possible with regular adapter, but I have no idea how to do it using Paging Library. I don`t need code just a clue.
Example
To add separators, you essentially have 2 options:
View-based, you explicitly include separators as an 'item' in the list and define a new viewtype for those separators. Allows the list to re-use the separator views but means you need to take the separators into account when defining the data.
Data-based, each item actually has a separator view, but it only shows on specific items. Based on some criteria you show or hide it whilst binding the view-holder.
For the paging library only option 2 is viable since it only partially loads the data and inserting the separators becomes much more complicated. You will simply need to figure out a way to check if item x is a different day than item x-1 and show/hide the date section in the view depending on the result.
You can achieve the same result using insertSeparators in Paging 3 library.
Make sure your items are sorted by date.
Inside or viewmodel retrieve a Pager something like that
private val communicationResult: Flow<PagingData<CommunicationHistoryItem>> = Pager(
PagingConfig(
pageSize = 50,
enablePlaceholders = false,
maxSize = 400,
initialLoadSize = 50
)
) {
CommunicationPagingSource(repository)
}.flow.cachedIn(viewModelScope)
After all insert separators like a header
val groupedCommunicationResult = communicationResult
.map { pagingData -> pagingData.map { CommunicationHistoryModel.Body(it) } }
.map {
it.insertSeparators{ after, before ->
if (before == null) {
//the end of the list
return#insertSeparators null
}
val afterDateStr = after?.createdDate
val beforeDateStr = before.createdDate
if (afterDateStr == null || beforeDateStr == null)
return#insertSeparators null
val afterDate = DateUtil.parseAsCalendar(afterDateStr)?.cleanTime()?.time ?: 0
val beforeDate = DateUtil.parseAsCalendar(beforeDateStr)?.cleanTime()?.time ?: 0
if (afterDate > beforeDate) {
CommunicationHistoryModel.Header( DateUtil.format(Date(beforeDate))) // dd.MM.yyyy
} else {
// no separator
null
}
}
}
cleanTime is required for grouping by dd.MM.yyyy ignoring time
fun Calendar.cleanTime(): Date {
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
return this.time
}
I was in the same spot as you and I came up with this solution.
One important note though, in order to implement this I had to change my date converter to the database, from long to string to store a timestamp
these are my converters
class DateConverter {
companion object {
#JvmStatic
val formatter = SimpleDateFormat("yyyyMMddHHmmss", Locale.ENGLISH)
#TypeConverter
#JvmStatic
fun toDate(text: String): Date = formatter.parse(text)
#TypeConverter
#JvmStatic
fun toText(date: Date): String = formatter.format(date)
}
}
Some starting info though, I have a list of report headers that I wish to show , and page through and be able to filter
They are represented by this object:
data class ReportHeaderEntity(
#ColumnInfo(name = "id") override val id: UUID
, #ColumnInfo(name = "name") override val name: String
, #ColumnInfo(name = "description") override val description: String
, #ColumnInfo(name = "created") override val date: Date)
I also wanted to add separators between the items in the list to show them by date
I achieved this by doing the following:
I created a new query in room like this
#Query(
"SELECT id, name, description,created " +
"FROM (SELECT id, name, description, created, created AS sort " +
" FROM reports " +
" WHERE :filter = '' " +
" OR name LIKE '%' || :filter || '%' " +
" OR description LIKE '%' || :filter || '%' " +
" UNION " +
" SELECT '00000000-0000-0000-0000-000000000000' as id, Substr(created, 0, 9) as name, '' as description, Substr(created, 0, 9) || '000000' AS created, Substr(created, 0, 9) || '256060' AS sort " +
" FROM reports " +
" WHERE :filter = '' " +
" OR name LIKE '%' || :filter || '%' " +
" OR description LIKE '%' || :filter || '%' " +
" GROUP BY Substr(created, 0, 9)) " +
"ORDER BY sort DESC ")
fun loadReportHeaders(filter: String = ""): DataSource.Factory<Int, ReportHeaderEntity>
This basically creates a separator line for all the items I have filtered through
it also creates a dummy date for sorting (with the time of 25:60:60 so that it will always appear in front of the other reports)
I then combine this with my list using union and sort them by the dummy date
The reason I had to change from long to string is because it is much easier to create dummy dates with string in sql and seperate the date part from the whole date time
The above creates a list like this:
00000000-0000-0000-0000-000000000000 20190522 20190522000000
e3b8fbe5-b8ce-4353-b85d-8a1160f51bac name 16769 description 93396 20190522141926
6779fbea-f840-4859-a9a1-b34b7e6520be name 86082 description 21138 20190522141925
00000000-0000-0000-0000-000000000000 20190521 20190521000000
6efa201f-d618-4819-bae1-5a0e907ddcfb name 9702 description 84139 20190521103247
In my PagedListAdapter I changed it to be an implementation of PagedListAdapter<ReportHeader, RecyclerView.ViewHolder> (not a specific viewholder)
Added to the companion object:
companion object {
private val EMPTY_ID = UUID(0L,0L)
private const val LABEL = 0
private const val HEADER = 1
}
and overrode get view type like so:
override fun getItemViewType(position: Int): Int = if (getItem(position)?.id ?: EMPTY_ID == EMPTY_ID) LABEL else HEADER
I then created two seperate view holders :
class ReportHeaderViewHolder(val binding: ListItemReportBinding) : RecyclerView.ViewHolder(binding.root)
class ReportLabelViewHolder(val binding: ListItemReportLabelBinding) : RecyclerView.ViewHolder(binding.root)
and implemented the other overriden methods like so:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
HEADER -> ReportHeaderViewHolder(DataBindingUtil.inflate(inflater, R.layout.list_item_report, parent, false))
else -> ReportLabelViewHolder(DataBindingUtil.inflate(inflater, R.layout.list_item_report_label, parent, false))
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val reportItem = getItem(position)
when (getItemViewType(position)) {
HEADER -> {
(holder as ReportHeaderViewHolder).binding.apply {
report = reportItem
executePendingBindings()
}
}
LABEL -> {
(holder as ReportLabelViewHolder).binding.apply {
date = reportItem?.name
executePendingBindings()
}
}
}
}
I hope this helps and inspires people to find even better solutions
When binding the data pass in the previous item as well
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
val previousItem = if (position == 0) null else getItem(position - 1)
holder.bind(item, previousItem)
}
Every view then sets a header, which is only made visible if the previous item doesn't have the same header.
val previousHeader = previousItem?.name?.capitalize().first()
val header = item?.name?.capitalize()?.first()
view.cachedContactHeader.text = header
view.cachedContactHeader.isVisible = previousHeader != header
Kiskae's answer is excellent and for your case option 2 probably works well.
In my case I wanted to have one additional item that wasn't in the database, like this:
Show all
Item 1
Item 2
It needed to be clickable as well. There's the usual way of overriding getItemCount to return +1 and offsetting positions for the other methods.
But I stumbled on another way that I haven't seen documented yet that might be useful for some cases. You might be able to incorporate additional elements into your query using union:
#Query("select '' as name, 0 as id " +
"union " +
"select name, id from user " +
"order by 1 asc")
DataSource.Factory<Integer, User> getAllDataSource();
That means the data source actually returns another item in the beginning, and there's no need to adjust positions. In your adapter, you can check for that item and handle it differently.
In your case the query would have to be different but I think it would be possible.
fun forLoopListItems() {
val items = listOf("apple", "banana", "kiwifruit")
for (i in items) {
if (i.equals("banana")) {
println("position is ${i.indices}")
}
}
}
This is My Kotlin code used with For Loop.
I tried to Print Index Curresponding to "banana"
But Result is "System.out: position is 0..5"
How is it Become 0..5 ?
The indices method gives you a range, which is 0..5 in this case because it's called on the String: banana with a length of 6.
You can instead iterate with indices as follows:
items.forEachIndexed { i, element ->
if (element == "banana") {
println("position is $i")
}
}
Alternative ways of iterating with indices are listed in this post.
I'm not sure if you really want to iterate explicitly though. Maybe it's fine for you to use built-in functions for finding the index of your element:
println(items.indexOf("banana"))
There is indexOf:
Returns first index of element, or -1 if the collection does not
contain element.
and lastIndexOf:
Returns last index of element, or -1 if the collection does not contain element.
val items = listOf("apple", "banana", "kiwifruit")
val appleIndex = items.indexOf("apple") // 0
val lastAppleIndex = items.lastIndexOf("apple") // 0
val bananaIndex = items.indexOf("banana") // 1
val orangeIndex = items.indexOf("orange") // -1
You can iterate with Index using forEachIndexed method of Kotlin
Iterate with Index
itemList.forEachIndexed{index, item ->
println("index = $index, item = $item ")
}
Check if item is Banana and print Index
itemList.forEachIndexed{ index, item -> {
if (item.equals("banana")) {println("position is ${index}")}
}