I have a recyclerView with 10 names and in this activity is possible search for name. With a click in a name the activity moves to another(with help Intents to move for DetailsActivity). It possible make this because in adapter, i have a function that give the position that the user have clicked. But when put a word in menu search this functional gives the position 1 (the real position of name, in exactally moment).
I can send in function in adpter for example, the id of name? What is the correct way to make this?
MainActivity.kt
class MainActivity : AppCompatActivity(), PeopleInStarWarsAdapter.OnNoteListener {
private lateinit var mainViewModel: MainViewModel
private var listDataPeople = ArrayList<DataPeople>()
private lateinit var dataPeopleAdapter: PeopleInStarWarsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel = ViewModelProvider
.AndroidViewModelFactory
.getInstance(application)
.create(MainViewModel::class.java)
//iniciar ViewModel
mainViewModel.init(this)
mainViewModel.getList().observe(this, Observer{ it ->
if(it != null){
listDataPeople = it
dataPeopleAdapter.submitList(listDataPeople)
}
else{
Toast.makeText(this, "Something is wrong!", Toast.LENGTH_SHORT).show()
}
})
initRecyclerView(listDataPeople)
}
private fun initRecyclerView(listDataPeople : ArrayList<DataPeople>) {
recycler_view.layoutManager = LinearLayoutManager(this)
dataPeopleAdapter = PeopleInStarWarsAdapter(listDataPeople, this)
recycler_view.adapter = dataPeopleAdapter
}
override fun onNoteClick(position: Int){
val intent = Intent(this, DetailsActivity::class.java)
intent.putExtra(BuildConfig.POSITION, position)
startActivity(intent)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main_menu, menu)
val manager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
val searchItem = menu?.findItem(R.id.search)
val searchView = searchItem?.actionView as SearchView
searchView.setSearchableInfo(manager.getSearchableInfo(componentName))
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String): Boolean {
searchView.clearFocus()
searchView.setQuery("", false)
searchItem.collapseActionView()
Log.v("Information", "Looking for $query")
mainViewModel.checkMatch(query, this#MainActivity)
return true
}
override fun onQueryTextChange(newText: String): Boolean {
Log.v("Information", "$newText")
return false
}
})
return true
}
}
PeopleInStarWarsAdapter.kt
class PeopleInStarWarsAdapter(listDataPeople: ArrayList<DataPeople>, onNoteListener: OnNoteListener) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){
var listDataPeople = ArrayList<DataPeople>()
private var mOnNoteListener : OnNoteListener
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.layout_list_item, parent,
false)
)
}
override fun getItemCount(): Int {
return listDataPeople.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ViewHolder -> {
holder.ViewHolder(listDataPeople[position], mOnNoteListener)
}
}
}
fun submitList(dataPeopleList: ArrayList<DataPeople>) {
this.listDataPeople = dataPeopleList
notifyDataSetChanged()
}
class ViewHolder constructor(itemView: View): RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val textViewName : TextView = itemView.textView_name
var onNoteListener: OnNoteListener? = null
fun ViewHolder(dataPeople: DataPeople, onNoteListener: OnNoteListener) {
val name = dataPeople.name
this.textViewName.text = name
this.onNoteListener = onNoteListener
itemView.setOnClickListener(this)
}
override fun onClick(v: View?) {
onNoteListener?.onNoteClick(adapterPosition)
}
}
interface OnNoteListener {
fun onNoteClick(position: Int)
}
init {
this.listDataPeople = listDataPeople
mOnNoteListener = onNoteListener
}
}
You should send position to ViewHolder .
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ViewHolder -> {
holder.ViewHolder(listDataPeople[position], mOnNoteListener,position)
}
}
}
Then pass this value into onNoteClick()
fun ViewHolder(dataPeople: DataPeople, onNoteListener: OnNoteListener,position: Int) {
val name = dataPeople.name
this.textViewName.text = name
this.onNoteListener = onNoteListener
itemView.setOnClickListener(this)
}
override fun onClick(v: View?) {
onNoteListener?.onNoteClick(position)
}
Related
In my application I want use checkbox in recyclerview.
I want when users click on item check/unchecked checkbox and when click on Select All or clear, checked/unchecked all of items.
I write below codes, when click on select all checked all of checkboxes but after click on one of checkbox not unchecked!
I should click twice on checkbox after unchecked!
My UI is :
My Adapter codes :
class DataListAdapter #Inject constructor() : RecyclerView.Adapter<DataListAdapter.ViewHolder>() {
private lateinit var binding: ItemWithCheckboxBinding
private lateinit var context: Context
private var moviesList = emptyList<String>()
private var isSelectedAll = false
private var checkBoxState = SparseBooleanArray()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
binding = ItemWithCheckboxBinding.inflate(LayoutInflater.from(parent.context), parent, false)
context = parent.context
return ViewHolder()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(moviesList[position])
holder.checkBox.isChecked = checkBoxState.get(position, false)
var state: String
if (!isSelectedAll) {
holder.checkBox.isChecked = false
state = LIST_STATE_REMOVE
onItemClickListener?.let { it(moviesList[position], state) }
} else {
holder.checkBox.isChecked = true
state = LIST_STATE_ADD
onItemClickListener?.let { it(moviesList[position], state) }
}
}
override fun getItemCount() = moviesList.size
inner class ViewHolder : RecyclerView.ViewHolder(binding.root) {
val checkBox = binding.itemCheck
#SuppressLint("SetTextI18n")
fun bind(item: String) {
binding.apply {
//Views
itemTitle.text = item
//Click
var state: String
binding.root.setOnClickListener {
if (!checkBoxState.get(adapterPosition, false)) {
checkBox.isChecked = true
checkBoxState.put(adapterPosition, true)
state = LIST_STATE_ADD
} else {
checkBox.isChecked = false
checkBoxState.put(adapterPosition, false)
state = LIST_STATE_REMOVE
}
onItemClickListener?.let { it(item, state) }
}
}
}
}
#SuppressLint("NotifyDataSetChanged")
fun selectAll() {
isSelectedAll = true
notifyDataSetChanged()
}
#SuppressLint("NotifyDataSetChanged")
fun unSelectAll() {
isSelectedAll = false
notifyDataSetChanged()
}
private var onItemClickListener: ((String, String) -> Unit)? = null
fun setOnItemClickListener(listener: (String, String) -> Unit) {
onItemClickListener = listener
}
fun setData(data: List<String>) {
val moviesDiffUtil = NotesDiffUtils(moviesList, data)
val diffUtils = DiffUtil.calculateDiff(moviesDiffUtil)
moviesList = data
diffUtils.dispatchUpdatesTo(this)
}
class NotesDiffUtils(private val oldItem: List<String>, private val newItem: List<String>) : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldItem.size
}
override fun getNewListSize(): Int {
return newItem.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldItem[oldItemPosition] === newItem[newItemPosition]
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldItem[oldItemPosition] === newItem[newItemPosition]
}
}
}
For control select all and clear checkboxes I write codes in Fragment:
selectAllTxt.setOnClickListener { dataListAdapter.selectAll() }
clearTxt.setOnClickListener { dataListAdapter.unSelectAll() }
How can I fix it?
Try to use the !checkBox.isChecked instead of !checkBoxState.get(adapterPosition, false) when clicking:
binding.root.setOnClickListener {
if (!checkBox.isChecked) {
checkBox.isChecked = true
checkBoxState.put(adapterPosition, true)
state = LIST_STATE_ADD
} else {
checkBox.isChecked = false
checkBoxState.put(adapterPosition, false)
state = LIST_STATE_REMOVE
}
onItemClickListener?.let { it(item, state) }
}
In my scenario, I have two fragments. In my first Fragment I have a RecyclerView with SelectionTracker. After selecting an item in my first fragment I can navigate to the second fragment. But from my second fragment if I navigate back to the first fragment using back button all my previous selection is lost. How can I prevent this?
I am using Jetpack Navigation Component to handle Fragment navigation.
Here is my fragment
class SelectVideoForCourseFragment : Fragment(R.layout.fragment_select_video_for_course) {
.....
..
private val adapter by lazy { MediaListAdapter() }
private lateinit var selectionTracker: SelectionTracker<String>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initializeListeners()
initializeRecyclerView(savedInstanceState)
fetchMedias()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
selectionTracker.onSaveInstanceState(outState)
}
private fun fetchMedias() {
val mediaRepo = MediaRepo()
mediaRepo.getVideos(contentResolver).observe(viewLifecycleOwner) {
adapter.submitItems(it)
}
}
private fun initializeRecyclerView(savedInstanceState: Bundle?) {
binding.mediaList.layoutManager = GridLayoutManager(requireContext(), 3)
binding.mediaList.addItemDecoration(GridSpacingItemDecoration(3, 5.dp, false))
binding.mediaList.adapter = adapter
selectionTracker = SelectionTracker.Builder(
"tracker-select-video-for-course",
binding.mediaList,
MediaItemKeyProvider(adapter),
MediaItemDetailsLookup(binding.mediaList),
StorageStrategy.createStringStorage()
).withSelectionPredicate(
SelectionPredicates.createSelectSingleAnything()
).build()
if (savedInstanceState != null) {
selectionTracker.onRestoreInstanceState(savedInstanceState)
}
adapter.tracker = selectionTracker
}
......
...
}
My RecyclerView Adapter:
class MediaListAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val differ = AsyncListDiffer(this, DIFF_CALLBACK_MEDIA)
var tracker: SelectionTracker<String>? = null
fun submitItems(updatedItems: List<Any>) {
differ.submitList(updatedItems)
}
fun getSelections(): List<MediaEntity> {
....
..
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == 1) {
val binding = ListItemImageBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
ViewHolderImage(binding)
} else {
val binding = ListItemVideoBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
ViewHolderVideo(binding)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ViewHolderImage) {
tracker?.let {
val item = differ.currentList[position] as ImageEntity
holder.bind(item , it.isSelected(item.uri.toString()))
}
} else {
holder as ViewHolderVideo
val item = differ.currentList[position] as VideoEntity
tracker?.let {
holder.bind(item, it.isSelected(item.uri.toString()))
}
}
}
override fun getItemCount(): Int = differ.currentList.size
override fun getItemViewType(position: Int): Int {
return if (differ.currentList[position] is ImageEntity) {
1
} else {
2
}
}
inner class ViewHolderImage(
private val binding: ListItemImageBinding): RecyclerView.ViewHolder(binding.root
) {
fun bind(item: ImageEntity, isSelected: Boolean) {
.....
...
}
fun getItemDetails(): ItemDetailsLookup.ItemDetails<String> =
object : ItemDetailsLookup.ItemDetails<String>() {
override fun getPosition(): Int = absoluteAdapterPosition
override fun getSelectionKey(): String {
val item = differ.currentList[absoluteAdapterPosition] as ImageEntity
return item.uri.toString()
}
override fun inSelectionHotspot(e: MotionEvent): Boolean = true
}
}
inner class ViewHolderVideo(
private val binding: ListItemVideoBinding,
): RecyclerView.ViewHolder(binding.root) {
fun bind(item: VideoEntity, isSelected: Boolean) {
.....
...
}
fun getItemDetails(): ItemDetailsLookup.ItemDetails<String> =
object : ItemDetailsLookup.ItemDetails<String>() {
override fun getPosition(): Int = absoluteAdapterPosition
override fun getSelectionKey(): String {
val item = differ.currentList[absoluteAdapterPosition] as VideoEntity
return item.uri.toString()
}
override fun inSelectionHotspot(e: MotionEvent): Boolean = true
}
}
companion object {
val DIFF_CALLBACK_MEDIA = object: DiffUtil.ItemCallback<Any>() {
.....
...
}
}
}
I'm trying to update the below recycler view to show the new data that input from the form in the second activity. However, its only showing the original list that I had in there. What am I doing wrong here?
Here is the main activity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
var workoutList = mutableListOf(
Workout("a","d","d","d"),
Workout("a","d","d","d"),
Workout("a","d","d","d")
)
val adaptor = WorkoutAdaptor(workoutList)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnNext.setOnClickListener {
Intent(this, SecondActivity::class.java).also {
startActivity(it)
}
}
binding.recyclerView.adapter = adaptor
binding.recyclerView.layoutManager = LinearLayoutManager(this)
}
override fun onRestart() {
super.onRestart()
var workout = intent.getSerializableExtra("EXTRA_WORKOUT") as Workout
workoutList.add(workout)
adaptor.notifyDataSetChanged()
}
}
Here is the adapter:
class WorkoutAdaptor (
var workouts: List<Workout>
) : RecyclerView.Adapter<WorkoutAdaptor.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val workoutCardBinding = WorkoutCardBinding.inflate(
LayoutInflater.from(parent.context), parent, false)
return ViewHolder(workoutCardBinding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(workouts[position])
}
override fun getItemCount(): Int {
return workouts.size
}
inner class ViewHolder(private val workoutCardBinding: WorkoutCardBinding) :
RecyclerView.ViewHolder(workoutCardBinding.root) {
fun bind(workout: Workout) {
workoutCardBinding.tvWorkoutCard.text = workout.toString()
}
}
}
And here is the activity where I get the workout object from:
class SecondActivity : AppCompatActivity() {
private lateinit var binding: ActivitySecondBinding
lateinit var spWorkoutsPosition: String
lateinit var spIncrementsPosition: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySecondBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.spWorkoutTypes.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
spWorkoutsPosition = p0?.getItemAtPosition(p2) as String
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
binding.spIncrements.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>?, view: View?, position: Int, id: Long) {
spIncrementsPosition = adapterView?.getItemAtPosition(position) as String
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
binding.btnSave.setOnClickListener {
val workoutName = binding.tvWorkoutName.text.toString()
val workoutType = spWorkoutsPosition.toString()
val workoutIncrement = spIncrementsPosition.toString()
val workoutRestTime = binding.etRestTime.text.toString()
val workout = Workout(workoutName, workoutType, workoutIncrement, workoutRestTime)
Intent(this, MainActivity::class.java).also {
it.putExtra("EXTRA_WORKOUT", workout)
startActivity(it)
}
}
}
}
As #John Doe commented you should probably use ActivityResultLauncher. refer here.
and as for the update issue the recommended way of implementing recyclerView's adapter is androidx.recyclerview.widget.ListAdapter. The ListAdapter handles addition and removal without the need to redraw the entire view, and even animate those changes.
here's the implementation for your adapter:
class WorkoutAdaptor :
ListAdapter<Workout, WorkoutAdaptor.ItemViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(
ItemAutocompleteSearchResultBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(position)
}
inner class ItemViewHolder(val binding: ItemAutocompleteSearchResultBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int) {
binding.apply {
workoutCardBinding.tvWorkoutCard.text = getItem(position).toString()
}
}
}
private class DiffCallback : DiffUtil.ItemCallback<Workout>() {
override fun areContentsTheSame(oldItem: Workout, newItem: Workout): Boolean {
return oldItem == newItem
}
override fun areItemsTheSame(oldItem: Workout, newItem: Workout): Boolean {
return oldItem.param1 == newItem.param1 &&
oldItem.param2 == newItem.param2 &&
oldItem.param3 == newItem.param3 &&
oldItem.param4 == newItem.param4
}
}
}
then you only have to call,
adapter.submitList(workoutList)
after you update the workoutList and the ListAdapter handles the rest.
and also call the adapter.submitList() in onStart() or onResume() methods.
My problem is that when I write MyListAdapter.currentList.isEmpty() the value is true even tho the list is NOT EMPTY! (I can see it in my view and in my database). What am I doing wrong, why is the value true even tho it should be false?
Fragment
#AndroidEntryPoint
class ShoppingCartFragment(
private val cacheMapper: ShopCacheMapper
) : Fragment(R.layout.fragment_shopping_cart), ShoppingCartListAdapter.OnItemClickListener {
private val shoppingCartViewModel: ShoppingCartViewModel by viewModels()
private val shoppingCartBinding: FragmentShoppingCartBinding by viewBinding()
#Inject lateinit var shoppingCartAdapter: ShoppingCartListAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindObjects()
observeList()
hideBtnIfListEmpty()
toast("LISTE IST EMPTY: ${shoppingCartAdapter.currentList.isEmpty()}")
}
private fun observeList() {
shoppingCartViewModel.productList.observe(viewLifecycleOwner) {
shoppingCartAdapter.submitList(it)
}
}
private fun bindObjects() = with(shoppingCartBinding) {
adapter = shoppingCartAdapter.apply { clickHandler(this#ShoppingCartFragment) }
viewModel = shoppingCartViewModel
lifecycleOwner = viewLifecycleOwner
}
override fun forwardCardClick(productCacheEntity: ProductCacheEntity) {
val product = cacheMapper.mapFromEntity(productCacheEntity)
val action = ShoppingCartFragmentDirections.actionShoppingCartFragmentToShopItemFragment(product)
findNavController().navigate(action)
}
override fun fordwardBtnIncreaseClick(id: Int) {
shoppingCartViewModel.increaseProductQuantityById(id)
}
override fun fordwardBtnDecreaseClick(id: Int) {
shoppingCartViewModel.decreaseProductQuantityById(id)
}
override fun forwardBtnDeleteClick(id: Int) {
shoppingCartViewModel.deleteProductById(id)
}
override fun onDestroyView() {
requireView().findViewById<RecyclerView>(R.id.rv_shopping_cart).adapter = null
super.onDestroyView()
}
// here lies the problem
private fun hideBtnIfListEmpty() {
if (shoppingCartAdapter.currentList.isEmpty()) shoppingCartBinding.shoppingCartBtn.root.visibility = View.INVISIBLE
else if (shoppingCartAdapter.currentList.isNotEmpty()) shoppingCartBinding.shoppingCartBtn.root.visibility = View.VISIBLE
}
}
ListAdapter
#FragmentScoped
class ShoppingCartListAdapter #Inject constructor() : ListAdapter<ProductCacheEntity, ShoppingCartListAdapter.ProductViewHolder>(Companion) {
private lateinit var clickListener: OnItemClickListener
companion object: DiffUtil.ItemCallback<ProductCacheEntity>() {
override fun areItemsTheSame(oldItem: ProductCacheEntity, newItem: ProductCacheEntity): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: ProductCacheEntity, newItem: ProductCacheEntity): Boolean = oldItem == newItem
}
inner class ProductViewHolder(val binding: ShoppingCartListItemBinding): RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ShoppingCartListItemBinding.inflate(layoutInflater, parent, false)
return ProductViewHolder(binding).also {
with(binding) {
ivProduct.setOnClickListener { clickListener.forwardCardClick(binding.product!!) }
tvTitle.setOnClickListener { clickListener.forwardCardClick(binding.product!!) }
btnIncrease.setOnClickListener { clickListener.fordwardBtnIncreaseClick(binding.product!!.id) }
btnDecrease.setOnClickListener { clickListener.fordwardBtnDecreaseClick(binding.product!!.id) }
btnDelete.setOnClickListener { clickListener.forwardBtnDeleteClick(binding.product!!.id) }
}
}
}
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
val currentProduct = getItem(position) ?: return
holder.binding.product = currentProduct
holder.binding.btnDecrease.isEnabled = currentProduct.quantity > 1
holder.binding.executePendingBindings()
}
interface OnItemClickListener {
fun forwardCardClick(productCacheEntity: ProductCacheEntity)
fun fordwardBtnIncreaseClick(id: Int)
fun fordwardBtnDecreaseClick(id: Int)
fun forwardBtnDeleteClick(id: Int)
}
fun clickHandler(clickEventHandler: OnItemClickListener) {
this.clickListener = clickEventHandler
}
}
Layout
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_shopping_cart"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="#id/shopping_cart_btn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/headline"
app:recyclerview_adapter_stableID="#{adapter}"
tools:listitem="#layout/shopping_cart_list_item" />
Bindingadapter (app:recyclerview_adapter_stableID)
#BindingAdapter("recyclerview_adapter_stableID")
fun setRecyclerViewAdapterWithStableId(view: RecyclerView, adapter: RecyclerView.Adapter<*>) {
view.run {
this.setHasFixedSize(true)
this.adapter = adapter
}
}
You are calling ListAdapter.Currentlist.isEmpty() within hideBtnIfListEmpty() too early before making sure that the list is ready and submitted by observeList()
You can call it after you submit the list to the adapter:
private fun observeList() {
shoppingCartViewModel.productList.observe(viewLifecycleOwner) {
shoppingCartAdapter.submitList(it)
hideBtnIfListEmpty()
}
}
I am making database app using room, mvvm, livedata . I have Prepopulated it with some data. Now i have two options either i am going to show that prepoulated data when app turns on via recyclerview or show it when i search it using SearchView inside recyclerView.
The Problem is when i search for particular item, itw shows that item when i complete my word and when i try to go back either my resView back empty or it always stays on that item. i wanna try sometime realtime like: when i enter only one alphabet it show all the suggestions belongs to alphabet, i only want to update with livedata
What i have try?
1-> I have already tried switchMap which worked with liveData but i have to refresh my activity in order to get back my list.
2-> I have tried resView filter which didn't worked because i am using livedata to update my UI and also give it regular try without livedata it still didn't work either.
3-> I Have tried regular editText just to try but i didn't find it useful as facing same problem, my main focus was on searchView
RecyclerView code with filterable or just ignore filterable part it wasn't good try at all
class MyAdapter(
private var context: Context,
private var dataList: List<SearchPojo>
) : RecyclerView.Adapter<MyAdapter.BaseViewHolder<*>>(), Filterable {
private var exampleListFull: List<SearchPojo>? = null
init {
exampleListFull = ArrayList(dataList)
}
companion object {
const val SEARCH_TYPE = 1
}
abstract class BaseViewHolder<T>(view: View) : RecyclerView.ViewHolder(view) {
abstract fun bind(t: T)
}
inner class SearchViewHolder(view: View) : BaseViewHolder<SearchPojo>(view) {
private val userID: TextView = view.findViewById(R.id.idSearch)
private val userName: TextView = view.findViewById(R.id.nameSearch)
private val userPos: TextView = view.findViewById(R.id.positionSearch)
override fun bind(t: SearchPojo) {
userID.text = t.id
userName.text = t.userName
userPos.text = t.position
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> {
return when (viewType) {
SEARCH_TYPE -> {
val view =
LayoutInflater.from(context).inflate(R.layout.layout_show_data, parent, false)
SearchViewHolder(view)
}
else -> {
throw IllegalAccessException("In valid View Type")
}
}
}
override fun getItemCount(): Int {
return dataList.size
}
override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
val element = dataList[position]
when (holder) {
is SearchViewHolder -> {
holder.bind(element)
}
}
}
override fun getItemViewType(position: Int): Int {
return when (dataList[position]) {
is SearchPojo -> SEARCH_TYPE
else -> throw IllegalAccessException()
}
}
override fun getFilter(): Filter {
return exampleFilter
}
private var exampleFilter = object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filteredList = ArrayList<SearchPojo>()
if (constraint == null || constraint.isEmpty()) {
filteredList.addAll(exampleListFull as Iterable<SearchPojo>)
} else {
val filterPattern = constraint.toString().toLowerCase().trim { it <= ' ' }
for (item in exampleListFull!!) {
if (item.userName.toLowerCase().contains(filterPattern)) {
filteredList.add(item)
}
}
}
val results = FilterResults()
results.values = filteredList
return results
}
override fun publishResults(constraint: CharSequence, results: FilterResults) {
dataList.clear()
dataList.addAll(results.values as List<SearchPojo>)
notifyDataSetChanged()
}
}
}
Main Activity
class MainActivity : AppCompatActivity() {
lateinit var searchViewModel: SearchViewModel
lateinit var myAdapter: MyAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (!::searchViewModel.isInitialized) {
searchViewModel = ViewModelProviders.of(this)[SearchViewModel::class.java]
searchViewModel.getAll().observe(this, Observer {
myAdapter(it)
})
}
searchItems.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
nameFromDb(s.toString())
}
})
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.search_item, menu)
val searchItem = menu!!.findItem(R.id.search_menu)
val searchView = searchItem.actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
myAdapter.getFilter().filter(newText)
return true
}
})
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) {
R.id.refresh -> {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}
return true
}
private fun nameFromDb(searchTxt: String) {
searchViewModel = ViewModelProviders.of(this)[SearchViewModel::class.java]
searchViewModel.items.observe(this, object : Observer<List<SearchPojo>> {
override fun onChanged(t: List<SearchPojo>?) {
if (t == null) {
return
}
myAdapter(t)
}
})
searchViewModel.searchIt(searchTxt)
}
private fun myAdapter(t: List<SearchPojo>) {
searchResultResView.apply {
layoutManager = LinearLayoutManager(context)
myAdapter = MyAdapter(this#MainActivity, t)
adapter = myAdapter
}
}
}
ViewModel
lass SearchViewModel(application: Application) :
AndroidViewModel(application) {
private val repo: SearchRepo = SearchRepo(application)
private val _searchItem : MutableLiveData<String> = MutableLiveData()
val items : LiveData<List<SearchPojo>> = Transformations.switchMap(_searchItem) { myItems ->
repo.searchItem(myItems)
}
init {
_searchItem.value = ""
}
fun searchIt(items: String) {
_searchItem.value = items
}
fun getAll() = repo.allSearch()
}
Repository
class SearchRepo(application: Application) {
private val searchDao: SearchDao
init {
val db = SearchDb.instance(application)
searchDao = db.searchDao()
}
fun searchItem(id: String): LiveData<List<SearchPojo>> {
return searchDao.searchViaID(id)
}
fun allSearch() : LiveData<List<SearchPojo>> {
return searchDao.allSearch()
}
}
Dao
#Dao
abstract class SearchDao : BaseDao<SearchPojo> {
#Query("Select * from SearchPojo")
abstract fun allSearch(): LiveData<List<SearchPojo>>
#Query("Select * from SearchPojo where userName Like :userNameSearch or LOWER(userName) like LOWER(:userNameSearch)")
abstract fun searchViaID(userNameSearch: String) : LiveData<List<SearchPojo>>
#Insert
abstract override fun insert(searchPojo: SearchPojo)
}
Please change your #Dao class like this
#Dao
abstract class SearchDao : BaseDao<SearchPojo> {
#Query("Select * from SearchPojo")
abstract fun allSearch(): LiveData<List<SearchPojo>>
#Query("Select * from SearchPojo where userName GLOB '*' || :userNameSearch|| '*'")
abstract fun searchViaID(userNameSearch: String) : LiveData<List<SearchPojo>>
#Insert
abstract override fun insert(searchPojo: SearchPojo)
}