So I want to show a suggestion in a searchView which is now inside a toolbar. So I created this adapter and it doesn't seem to work and the app is also crashing with this error StringIndexOutOfBoundsException
Adapter
class SearchHitchAdapter(context: Context, cursor: Cursor) : CursorAdapter(context, cursor, false) {
private val dataSet = arrayListOf<String>(*context.resources.getStringArray(R.array.city_states))
override fun newView(context: Context?, cursor: Cursor?, parent: ViewGroup?): View {
val inflater = context!!.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
return inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false)
}
override fun bindView(view: View?, context: Context?, cursor: Cursor?) {
val position = cursor!!.position
val textView = view!!.findViewById(android.R.id.text1) as TextView
textView.text = dataSet[position]
}
}
This function is being called inside onQueryTextChange
private fun setUpSearchSuggestions(query: String) {
val dataSet = getCityList()
val columns = arrayOf("_id", "text")
val temp = arrayOf(0, "default")
val cursor = MatrixCursor(columns)
for (i in 0 until dataSet.size) {
val city = dataSet[i]
if (city.toLowerCase(Locale.US).contains(query.toLowerCase(Locale.US))) {
temp[0] = i
temp[1] = city[i]
cursor.addRow(temp)
}
}
searchVIew.suggestionsAdapter = SearchAdapter(context!!, cursor)
}
This is not working can somebody help me or suggest me something.
This line in your code looks suspicious:
temp[1] = city[i]
This is the same as writing temp[i] = city.get(i): you are trying to get the character from city at position i.
Since i is the loop variable, and you're looping over dataset, this is very likely a mistake. There's no guarantee that every string in the data set is as long as the data set itself. Imagine that you have a list of a thousand cities; chances are very good that each city's name is less than one thousand characters long.
The exception "StringIndexOutOfBoundsException" says that you are accessing the data which doesn't exists. Check if your dataset has the right list.
Related
I'm updating the font size of all the texts on the app, what I want to achieve is, when I select the font size, i should be able to update the font sizes of all the texts on that activity.
My only problem is i can't find the size property on the Spinner Object.
This is what I did for Text Views, is it possible to apply a code similar to this one for Spinners ?
const val HEADER_TEXT = 24
const val NORMAL_TEXT = 14
private fun updateAssetSize(textView: TextView, additionalSize: Int, type: Int) {
val size = additionalSize + type
textView.setTextSize(COMPLEX_UNIT_SP, size.toFloat());
}
//calling the method:
updateAssetSize(screenText, additionalFontSize, HEADER_TEXT)
Note: This should be done from code, since this will be updated on run time.
Based on #Zain Suggestion, I resolved this by using an adapterlist object. Instead of using String I created a custom class with fontSize and text properties in it.
class SpinnerItem(
val text: String,
var fontSize: Int
) {
// this is necessary, in order for the text to display the texts in the dropdown list
override fun toString(): String {
return text
}
}
Here's the AdapterList that I created:
class SpinnerItemListAdapter(
context: Context,
val resourceId: Int,
var list: ArrayList<SpinnerItem>
) : ArrayAdapter<SpinnerItem>(context, resourceId, list) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val text = this.list[position].text
val size = this.list[position].fontSize
val inflater = LayoutInflater.from(context)
val convertView = inflater.inflate(resourceId, parent, false)
val simpleTextView = convertView.findViewById(R.id.simpleTextView) as TextView
simpleTextView.text = text
simpleTextView.setTextSize(size.toFloat())
return convertView
}
// We'll call this whenever there's an update in the fontSize
fun swapList(list: ArrayList<SpinnerItem>) {
clear()
addAll(list)
notifyDataSetChanged()
}
}
Here's the custom XML File spinner_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/simpleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left"
android:padding="12dp"
android:textSize="16sp" />
The Spinner to be updated:
var fontSizes = arrayListOf(
SpinnerItem("Small", NORMAL_TEXT, "Default"),
SpinnerItem("Normal", NORMAL_TEXT, "Default"),
SpinnerItem("Large", NORMAL_TEXT, "Default"),
SpinnerItem("Largest", NORMAL_TEXT, "Default")
)
var fontSizeAdapterItem = SpinnerItemListAdapter(
this,
R.layout.spinner_item,
toSpinnerItemList(fontSizes, newSize)
)
Here's What will happen when we update it:
private fun updateSpinnerSize(additional: Int) {
val newSize = additional + NORMAL_TEXT
fontSizes = toSpinnerItemList(fontSizes, newSize)
fontSizeAdapterItem?.let {
it.swapList(fontSizes)
}
}
private fun toSpinnerItemList(
list: ArrayList<SpinnerItem>,
newSize: Int
): ArrayList<SpinnerItem> {
val itemList = ArrayList<SpinnerItem>()
for (item in list) {
item.fontSize = newSize
itemList.add(item)
}
return itemList
}
Here is my Adapter class code:
class SearchPeopleAdapter(user: FirestoreRecyclerOptions<User>) :
FirestoreRecyclerAdapter<User, SearchPeopleAdapter.ViewHolder>(user) {
private var mUser : FirestoreRecyclerOptions<User>? = null
private var mOptions: FirestoreRecyclerOptions<User>? = null
private var mSnapshots: ObservableSnapshotArray<User>? = null
init {
mUser = user
}
fun firestoreRecyclerAdapter(user: FirestoreRecyclerOptions<User>?) {
mOptions = user
mSnapshots = user!!.snapshots
if (mOptions!!.owner != null) {
mOptions!!.owner!!.lifecycle.addObserver(this)
}
}
override fun startListening() {
if (!mSnapshots!!.isListening(this)) {
mSnapshots!!.addChangeEventListener(this);
}
}
override fun stopListening() {
mSnapshots!!.removeChangeEventListener(this)
notifyDataSetChanged()
}
//Inflate the xml
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.search_people_list_item, parent, false))
}
//Bind every dataView to the xml based on the Int value
override fun onBindViewHolder(viewHolder: ViewHolder, holderNumber: Int, user: User) {
viewHolder.apply {
itemView.search_people_person_list_name.text = user.Name
itemView.search_people_person_username.text = user.UserName
}
viewHolder.bind(user)
}
override fun getItemCount(): Int {
return mSnapshots!!.size
}
//Adds functionality to each View (aka ViewHolder) which is every person downloaded
inner class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView!!), View.OnClickListener {
//We are downloading User Objects so this Variable will be assigned to the UserObject downloaded
var currentUser : User? = null
//When the class is initiated this function is called which sets an OnClickListener to each View
init {
itemView!!.setOnClickListener(this)
}
//The Data from the Object is used to Populate the TextViews
fun bind(model: User) {
currentUser = model
itemView.search_people_person_list_name.text = model.Name
itemView.search_people_person_username.text = model.UserName
//If the user has set a profilePhoto then download & populate it with Glide
if ( model.ProfilePhotoChosen ) {
CompanionObjects.getPersonProfilePhotoStorageRef(model.Uid).downloadUrl.addOnSuccessListener {
val downloadUrl = it.toString()
Glide.with(itemView)
.load(downloadUrl)
.into(itemView.search_people_list_profile_image)
}.addOnFailureListener {
Timber.i("unable to retrieve your profile photo")
}
}
//Clear the View with the Glide.with(View).clear() method as the view will be reused and the photo
//might also get reused unnecessarily
else {
Glide.with(itemView).clear(itemView.search_people_list_profile_image)
}
}
//The onClick function is called when the View is clicked, in this case we are starting
// an Intent with the Intent Extra of userId to the PersonProfile Activity
// which will check for the IntentExtras and Populate the elements
override fun onClick(v: View?) {
val userId = currentUser!!.Uid
Timber.i("The click is $userId")
val intent = Intent(v!!.context, PersonProfileActivity::class.java)
intent.putExtra(CompanionObjects.USER_ID_INTENT_EXTRA, userId)
v.context.startActivity(intent)
}
}
}
Here are 2 of the methods in the Activity class, Inspite of calling the onStart and onStop method, the itemCount method always returns 0
override fun onStop() {
adapter!!.stopListening()
super.onStop()
}
//Retrieves data from Firestore and assigns the retrieved data to the search People Adapter
private fun retrieveDataFromFirestore(searchQuery : String) {
mFirestore = FirebaseFirestore.getInstance()
//Assigns the Collection Name from which needs to be queried
//the where conditions ensure to query a userDocument whose userName starts with the query entered in the searchField
val userNameQuery = mFirestore.collection(CompanionObjects.USERS_COLLECTION_NAME)
.whereGreaterThanOrEqualTo("userName", searchQuery)
.whereLessThanOrEqualTo("userName", "$searchQuery\uF7FF")
//Assigns the query to the User Objects that are related to Firestore
users = FirestoreRecyclerOptions.Builder<User>()
.setQuery(userNameQuery, User::class.java)
.build()
//Assigns the Firestore queried data to the search People Adapter
adapter = SearchPeopleAdapter(users!!)
registerAdapterObserver()
adapter!!.firestoreRecyclerAdapter(users!!)
adapter!!.startListening()
search_people_list.setHasFixedSize(true)
search_people_list.hasFixedSize()
search_people_list.layoutManager = LinearLayoutManager(this)
search_people_list.adapter = adapter
persistSearchQueryString(searchQuery)
}
When I call getItemCount method in the Activity class. It always returns 0 even thou the adapter does hold Views. How do I retrieve the exact count in the Adapter
Please make sure mSnapshots = user!!.snapshots produces items.
In fun retrieveDataFromFirestore(searchQuery : String) after adding items in adapter!!.firestoreRecyclerAdapter(users!!), you are not notifying RecyclerView to update.
So, don't forget to call
adapter!!.notifyDataSetChanged();
Your code might looks like this:
private fun retrieveDataFromFirestore(searchQuery : String) {
mFirestore = FirebaseFirestore.getInstance()
//Assigns the Collection Name from which needs to be queried
//the where conditions ensure to query a userDocument whose userName starts with the query entered in the searchField
val userNameQuery = mFirestore.collection(CompanionObjects.USERS_COLLECTION_NAME)
.whereGreaterThanOrEqualTo("userName", searchQuery)
.whereLessThanOrEqualTo("userName", "$searchQuery\uF7FF")
//Assigns the query to the User Objects that are related to Firestore
users = FirestoreRecyclerOptions.Builder<User>()
.setQuery(userNameQuery, User::class.java)
.build()
//Assigns the Firestore queried data to the search People Adapter
adapter = SearchPeopleAdapter(users!!)
registerAdapterObserver()
adapter!!.firestoreRecyclerAdapter(users!!)
adapter!!.startListening()
adapter!!.notifyDataSetChanged(); //<------- Add this line---------
search_people_list.setHasFixedSize(true)
search_people_list.hasFixedSize()
search_people_list.layoutManager = LinearLayoutManager(this)
search_people_list.adapter = adapter
persistSearchQueryString(searchQuery)
}
Ok I solved this, Here is the code:
private fun retrieveDataFromFirestore(searchQuery : String) {
mFirestore = FirebaseFirestore.getInstance()
//Assigns the Collection Name from which needs to be queried
//the where conditions ensure to query a userDocument whose userName
starts with the query entered in the searchField
val userNameQuery = mFirestore.collection(CompanionObjects.USERS_COLLECTION_NAME)
.whereGreaterThanOrEqualTo(userName, searchQuery.toLowerCase())
.whereLessThanOrEqualTo(userName, "${searchQuery.toLowerCase()}\uF7FF")
//Assign the Query to the user variable
users = FirestoreRecyclerOptions.Builder<User>()
.setQuery(userNameQuery, User::class.java)
.build()
//Assigns the Firestore queried data to the search People Adapter
adapter = object : SearchPeopleAdapter(users!!) {
//Need to create a class Body because it is open and this gives
//option to override its onDataChanged method in the Activity rather than in its adapter class
override fun onDataChanged() {
if ( itemCount == 0 ) {
search_people_list.visibility = View.INVISIBLE
retrieving_progress.visibility = View.INVISIBLE
empty_search_users_text.visibility = View.VISIBLE
//If was not searching with name field then prompt search with name field
if ( !searchingWithNameField ) {
//Code to add formatting options to the text like underline it
val content = SpannableString(getString(R.string.search_with_name_instead))
content.setSpan(UnderlineSpan(), 0, content.length, 0)
change_search_field_text.text = content
} else
//If was not searching with username field then prompt search with username field
{
//Code to add formatting options to the text like underline it
val content = SpannableString(getString(R.string.search_with_username_instead))
content.setSpan(UnderlineSpan(), 0, content.length, 0)
change_search_field_text.text = content
}
change_search_field_text.visibility = View.VISIBLE
} else {
//The adapter count is not 0 so show the recyclerView and hide the progress bar, emptyText, Change Search field Text etc.
search_people_list.visibility = View.VISIBLE
retrieving_progress.visibility = View.INVISIBLE
empty_search_users_text.visibility = View.INVISIBLE
change_search_field_text.visibility = View.INVISIBLE
//Should persist only if there is a result from the query ofCourse
persistSearchQueryStringAndSearchField(searchQuery)
}
}
}
adapterCreated = true
adapter!!.startListening()
adapter!!.notifyDataSetChanged()
//Make the progress bar visible and invisible soon as a document is added to the adapter
registerAdapterObserver()
search_people_list.setHasFixedSize(true)
search_people_list.hasFixedSize()
search_people_list.layoutManager = LinearLayoutManager(this)
search_people_list.adapter = adapter
}
So, What I did was made the adapter class open, then created an instance of the Adapter in the Activity and called the OnDataChanged() method in the Activity which watches for the itemCount in the Adapter. This way I am able to retrieve the correct the adapterCount value.
I have a listView that has a custom layout that contains a fragment. The problem I am having is when I add the fragment to the listView row I get a never ending loop that keeps going through the getView code.
I have only been doing Android coding for a couple months so any help would be great. Please let me know if there is any more of my code I need to post
This is my adapter code:
class AdapterReply(
private val context: Context,
private val ph: Phan,
private val replies: ArrayList<Reply> ) : BaseAdapter() {
override fun getCount(): Int {
return replies.size
}
override fun getItem(position: Int): Reply {
return replies[position]
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getView(position: Int, convertView: View?, viewGroup: ViewGroup?): View? {
val rowMain: View?
val rowHolder: ListRowHolder
val contacts = ph.contacts
val reply = replies[position]
Log.d("AdapterReply", "Binding reply: Position $position ${reply.id} Detail: ${reply.detail}")
if (convertView == null) {
val layoutInflater = LayoutInflater.from(viewGroup!!.context)
rowMain = layoutInflater.inflate(R.layout.reply_item_row, viewGroup, false)
rowHolder = ListRowHolder(rowMain)
rowMain.tag = rowHolder
Log.d("AdapterReply", "New Item")
} else {
rowMain = convertView
rowHolder = rowMain.tag as ListRowHolder
Log.d("AdapterReply", "Old item from memory")
}
rowHolder.itemDetail.text = Helpers.anonymizeContent(reply.detail, ph.anonymousMetadata, ph.isViewerMember())
rowHolder.itemLastActive.text = Helpers.getFormattedDate(reply.lastActive())
if(reply.attachments.size > 0){
val imageAttachment = reply.attachments.filter { attachment: Attachment -> attachment.type == 0 }[0]
var imageUrl = Constants.BASE_URL + imageAttachment.url
if(imageAttachment.anonymous()){
imageUrl = Constants.BASE_URL + imageAttachment.anonUrl
}
Picasso.with(rowMain!!.context).load(imageUrl).into(rowHolder.itemImage)
}
var poster: Contact? = reply.contact
if(contacts.size > 0) {
val posterList = contacts.filter { contact: Contact -> contact.id == reply.rlContactID }
if (posterList.isNotEmpty()) {
poster = posterList[0]
}
}
if(poster != null) {
if(poster.anonymizedName.isNotEmpty()) {
rowHolder.itemPoster.text = poster.anonymizedName
} else {
val posterName = "${poster.firstName} ${poster.lastName}"
rowHolder.itemPoster.text = posterName
}
//Code the causes the problem
val manager = (rowMain!!.context as AppCompatActivity).supportFragmentManager
val posterAvatarFragment =
AvatarFragment.newInstance(poster)
manager.beginTransaction()
.add(R.id.reply_avatar_fragment, posterAvatarFragment, "ReplyAvatarFragment")
.commit()
//End Code the causes the problem
}
bindReplies(rowMain, ph, reply.replies)
return rowMain
}
internal class ListRowHolder(row: View?) {
var itemDetail : TextView = row!!.reply_view_detail
var itemImage : ImageView = row!!.reply_view_image
var itemPoster : TextView = row!!.reply_view_posterName
var itemLastActive : TextView= row!!.reply_view_lastActive
var itemReplyReplies: ListView = row!!.reply_view_replyList
}
private fun bindReplies(viewRow: View?, ph: Phan, replies : ArrayList<Reply>){
if(replies.isNotEmpty()) {
val myObject = AdapterReply(context, ph, replies)
val replyListView = viewRow!!.reply_view_replyList
Log.d("AdapterReply", "Binding Replies: ${ph.encodedId} ${replies.size}")
replyListView.adapter = myObject
}
}
}
manager.beginTransaction()
.add(R.id.reply_avatar_fragment, posterAvatarFragment, "ReplyAvatarFragment")
.commit()
Man, I'm not sure do you know, what function performs adapter class in listview. Adapter class is used to fill listview by rows passed in array inside class constructor. getView method is called for every row in array, so for example, if you have an array with 10 rows, this code will fire ten times. If you do automatically change fragment to another, and during filling an old view you will change layout to a another layout with the same data, your code will make an infinity loop, because you will repeat all time the same cases. You should avoid actions, which will dynamically change layout during load him. In my sugestion, if you want to use a multiple layouts inside one adapter, there are two special methods to set layout for row, based on some conditions: getItemViewType and getViewTypeCount. In first one you can use your conditions to check, which layout should be used for row. Second one is to set number of layouts, which will be used in your adapter class. Let search some examples of usage.
I need to pick a new item from a list that hasn't been picked already until there are no more.
Here is my code:
private var quizQuestionList: ArrayList<Quiz>
private var pickedItems: ArrayList<Int>
private var random: Random = Random()
private fun pickItem(): Quiz {
var index = random?.nextInt(quizQuestionList!!.size)
if (pickedItems.contains(index)) {
index = random?.nextInt(quizQuestionList!!.size)
pickedItems.add(index)
} else {
pickedItems.add(index)
}
val item = quizQuestionList!!.get(index!!)
return item
}
Please suggest any solution that gives me a new item every time. I used an int list for all previously picked items and check every time when picked new item but I didn't get success.
It isn't obvious what you are looking for, but it looks like you want to show different Quiz question from ArrayList. In condition of, when that question is shown, no more same question will be shown. Here is how you should do, I will give you just the logic you could try it yourself:
import java.util.Random
fun main(args: Array<String>) {
val random = Random()
var randomInt: Int
var pickedInt: MutableSet<Int> = mutableSetOf()
fun rand(from: Int, to: Int): Int{
do{
randomInt = random.nextInt(to - from) + from
}while(pickedInt.contains(randomInt))
pickedInt.add(randomInt)
return randomInt
}
while(pickedInt.size < 9){
var differentNumber = rand(1,11)
println(differentNumber)
}
}
This will print nine different Number. The way I choosing MutableSet is because it will only have unique values, no duplicated value. Hope it helps!
here is code for same::
val arrayList = ArrayList<String>()
arrayList.add("a")
arrayList.add("b")
arrayList.add("c")
arrayList.add("d")
arrayList.add("e")
arrayList.add("f")
arrayList.add("g")
arrayList.add("h")
arrayList.add("i")
arrayList.add("j")
arrayList.add("k")
random = Random()
Low = 0
High = arrayList.size
val generateRandom = findViewById<View>(R.id.generateRandom) as Button
generateRandom.setOnClickListener {
val Result = random.nextInt(High - Low) + Low
Log.v("check", arrayList[Result])
}
Please let me know if need more!!
Try this way
class MainActivity : AppCompatActivity() {
private var arrayList = ArrayList<String>()
private var lastItem = -1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
for (i in 0..9) {
arrayList.add(i.toString())
}
btnData.setOnClickListener {
Log.e("RANDOM_NUMBER", "" + getRandomItemFromList())
}
}
private fun getRandomItemFromList(): Int {
val randomValue = Random().nextInt(arrayList.size)
return if (randomValue != lastItem) {
lastItem = randomValue
randomValue
} else {
getRandomItemFromList()
}
}
}
I made this extension for the ArrayList class in Kotlin you can use it multiple times in only one line.
fun <T> ArrayList<T>.generateRandomItems(numberOfItems: Int): ArrayList<T> {
val range = if(numberOfItems > this.size){this.size}else{numberOfItems}
val randomItems = ArrayList<T>()
for (i in 0..range) {
var randomItem: T
do {
randomItem = this.random()
} while (randomItems.contains(randomItem))
randomItems.add(randomItem)
}
return randomItems
}
Usage:
val randomUsersList = usersList.generateRandomItems(20)
Note: the usersList is the list that has items to generate random items from.
I tried many ways but couldn't get RecyclerView display new rows.
This is my adapter:
private inner class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {
override fun getItemCount(): Int {
LogDog.i(TAG, "getItemCount=" + _to.size)
return _to.size
}
override fun onBindViewHolder(holder: MyViewHolder?, position: Int) {
LogDog.i(TAG, "Binding row $position of size=" + _to.size)
val contactId = _to[position]
holder?._tv_name?.text = DB.getString(TblContact, TblContact._display, contactId)
holder?._tv_address?.text = DB.getString(TblContact, TblContact._address, contactId)
holder?._position = position
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): MyViewHolder {
LogDog.i(TAG, "onCreateViewHolder, size=" + _to.size)
return MyViewHolder(activity.layoutInflater.inflate(R.layout.r_to, parent, false))
}
}
When contact button clicked, contact dialog displays to select and calls _adListener to add row:
private val _to = ArrayList<Long>()
private var _adapter = MyAdapter()
private val _addListener : (Long) -> Unit = { id ->
_to.add(id)
Log.i(TAG, "_addListener size=" + _to.size)
//_adapter.notifyItemInserted(_to.size - 1)
_adapter.notifyDataSetChanged()
}
private val _addClicker = View.OnClickListener { _ ->
DlgContactEdit(activity, null, null, _addListener).show()
}
private val _lookupClicker = View.OnClickListener { _ ->
DlgContactSelector(activity, _addListener).show()
}
From log, I do see _addListener is called, but new rows not displaying! Only when layout refreshes (e.g., keyboard displays and hides), new rows will display to the real content in _to list. What could be the reason?
I also tried to use Handler to call notifyItemInserted later, or change to notifyDataSetChanged, but no luck.
Contrary to many tutorials, notifyChange does not work by itself, most importantly you need to refresh the recycler view .invalidate().
Here is an example of a function i have in one of my classes that can call from different places to refresh my RV.
/**
* Sets up the recycler view and refreshes it after data changes
*/
private void setUpView() {
Schedule_AddTimes_Adapter adapter = new Schedule_AddTimes_Adapter(mTimesList);
RecyclerView.ItemDecoration divider = new DividerItemDecoration(ContextCompat.getDrawable(mContext, R.drawable.divider));
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mContext);
mDoseTimesList.setLayoutManager(linearLayoutManager);
mDoseTimesList.setAdapter(adapter);
mDoseTimesList.addItemDecoration(divider);
mDoseTimesList.invalidate();
}
Sorry for my stupid error. Just noticed I didn't use _adapter at all, I was writing this
rv_to.adapter = MyAdapter()
which is completely wrong.
And it works without calling invalidate.
Thanks all for the help!