Use kotlin enum to avoid else statement in when expressions - kotlin

I would like to use the enum class to create exhaustive lists for when statements.
I have created my enum class but in onCreateViewHolder() I am still getting error message "A 'return' expression required in a function with a block body ('{...}')". The error goes away when i add else statement. What's strange is that onBindViewHolder works fine even though it implements the same enum elements.
How can I implement the enum here to avoid using else block in onCreateViewHolder()?
enum class ViewHolderType(val ID: Int) {
FOOTER(0),
ITEM(1)
}
override fun getItemViewType(position: Int): Int {
return if (position == currentList.size) {
ViewHolderType.FOOTER.ID
} else {
ViewHolderType.ITEM.ID
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,
): MultiViewViewHolder {
when (viewType) {
ViewHolderType.FOOTER.ID -> {
val view = LayoutInflater
.from(parent.context)
.inflate(R.layout.recycler_view_fragment_plus_button_new, parent, false)
return ViewHolder2(view)
}
ViewHolderType.ITEM.ID -> {
val view = LayoutInflater
.from(parent.context)
.inflate(R.layout.recycler_view_fragment, parent, false)
return ViewHolder1(view)
}
}
}
override fun onBindViewHolder(holder: MultiViewViewHolder, position: Int) {
when (getItemViewType(position)) {
ViewHolderType.ITEM.ID -> {
val item = getItem(position)
holder.onBindViewHolderItem(position, item)
}
ViewHolderType.FOOTER.ID -> {
holder.onBindViewHolderFooter()
}
}
}

Do it like this so the subject of when is an enum rather than an Int:
enum class ViewHolderType {
FOOTER,
ITEM
}
override fun getItemViewType(position: Int): Int {
return when (position == currentList.size) {
true -> ViewHolderType.FOOTER
false -> ViewHolderType.ITEM
}.ordinal
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,
): MultiViewViewHolder {
return when (ViewHolderType.values()[viewType]) {
ViewHolderType.FOOTER -> {
val view = LayoutInflater
.from(parent.context)
.inflate(R.layout.recycler_view_fragment_plus_button_new, parent, false)
ViewHolder2(view)
}
ViewHolderType.ITEM -> {
val view = LayoutInflater
.from(parent.context)
.inflate(R.layout.recycler_view_fragment, parent, false)
ViewHolder1(view)
}
}
}
override fun onBindViewHolder(holder: MultiViewViewHolder, position: Int) {
when (ViewHolderType.values()[getItemViewType(position)]) {
ViewHolderType.ITEM -> {
val item = getItem(position)
holder.onBindViewHolderItem(position, item)
}
ViewHolderType.FOOTER -> {
holder.onBindViewHolderFooter()
}
}
}
And you don’t need the ID property in your enum class. Often it is recommended not to use the ordinals of enums like this because it prevents changes from being made to the number and order of enum elements without breaking code elsewhere. But this reason doesn’t matter here since it is only used internally to this class, so it’s perfectly fine.

Related

Filter searchView from RecycleView with Adapter

Adapter class
class AppListAdapter(private val context: Context, initialChecked: ArrayList<String> = arrayListOf()) : RecyclerView.Adapter<AppListAdapter.AppViewHolder>() {
public val appList = arrayListOf<ApplicationInfo>()
private val checkedAppList = arrayListOf<Boolean>()
private val packageManager: PackageManager = context.packageManager
init {
context.packageManager.getInstalledApplications(PackageManager.GET_META_DATA).sortedBy { it.loadLabel(packageManager).toString() }.forEach { info ->
if (info.packageName != context.packageName) {
if (info.flags and ApplicationInfo.FLAG_SYSTEM == 0) {
appList.add(info)
checkedAppList.add(initialChecked.contains(info.packageName))
}
}
}
}
inner class AppViewHolder(private val item: ItemAppBinding) : RecyclerView.ViewHolder(item.root) {
fun bind(data: ApplicationInfo, position: Int) {
item.txApp.text = data.loadLabel(packageManager)
item.imgIcon.setImageDrawable(data.loadIcon(packageManager))
item.cbApp.isChecked = checkedAppList[position]
item.cbApp.setOnCheckedChangeListener { _, checked ->
checkedAppList[position] = checked
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder {
return AppViewHolder(ItemAppBinding.inflate(LayoutInflater.from(context), parent, false))
}
override fun onBindViewHolder(holder: AppViewHolder, position: Int) {
holder.bind(appList[position], position)
}
override fun getItemCount(): Int {
return appList.size
}
on MainActivity
binding.searchView2.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
binding.searchView2.clearFocus()
// how to write code filtered by query?
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
// how to write code filtered by newText?
return false
}
})
I'm newbie in kotlin..anyone can help?
I believe you want to filter items with "ApplicationInfo.txApp", so i will write the code for it.
First you need your adapter class to extend Filterable like below, and add one more list to hold all items:
class AppListAdapter(private val context: Context, initialChecked: ArrayList<String> = arrayListOf()) : RecyclerView.Adapter<AppListAdapter.AppViewHolder>(), Filterable {
public val appList = arrayListOf<ApplicationInfo>()
public val appListFull = ArrayList<ApplicationInfo>(appList)
// This full list because of when you delete all the typing to searchView
// so it will get back to that full form.
Then override it's function and write your own filter to work, paste this code to your adapter:
override fun getFilter(): Filter {
return exampleFilter
}
private val exampleFilter = object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults? {
val filteredList: ArrayList<ApplicationInfo> = ArrayList()
if (constraint == null || constraint.isEmpty()) {
// when searchview is empty
filteredList.addAll(appListFull)
} else {
// when you type something
// it also uses Locale in case capital letters different.
val filterPattern = constraint.toString().lowercase(Locale.getDefault()).trim()
for (item in appListFull) {
val txApp = item.txApp
if (txApp.lowercase(Locale.getDefault()).contains(filterPattern)) {
filteredList.add(item)
}
}
}
val results = FilterResults()
results.values = filteredList
return results
}
#SuppressLint("NotifyDataSetChanged") #Suppress("UNCHECKED_CAST")
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
appList.clear()
appList.addAll(results!!.values as ArrayList<ApplicationInfo>)
notifyDataSetChanged()
}
}
And finally call this filter method in your searchview:
binding.searchView2.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
yourAdapter.filter.filter(query)
yourAdapter.notifyDataSetChanged()
binding.searchView2.clearFocus()
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
yourAdapter.filter.filter(newText)
yourAdapter.notifyDataSetChanged()
return false
}
})
These should work i'm using something similar to that, if not let me know with the problem.

Unexpected closure resolution

Consider this snipped:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.test, container, false)
class MyViewHolder(val view: View): RecyclerView.ViewHolder(view) {
init {
view.setOnClickListener {
Log.d("hey", "there")
}
}
}
view.findViewById<RecyclerView>(R.id.files).adapter = object: RecyclerView.Adapter<MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val item = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
return MyViewHolder(item)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.view.findViewById<TextView>(R.id.text).text = files[position].name
}
override fun getItemCount() = files.size
}
return view
}
onCreateView creates a local variable view. The nested class MyViewHolder also declares a variable called view. What was unexpected is that the variable view accessed inside the init block of MyViewHolder (where the OnClickListener is set) is not the one declared in MyViewHolder, but the outer one. Why?
I would expect the innermost variable declaration would be used.
The class is not declared as an inner class. Outside variables should not be accesible.
What am I missing?
This is not the case of a nested class, but of a function that returns a class.
If you try to define your class as inner, you'll actually get an error message:
Modifier 'inner' is not applicable to 'local class'
I'll simplify this example a bit, so the Android part won't interfere:
// This is what you're doing
fun a(): Any {
val a = "a"
class B(val a: String = "b") {
init {
println(a)
}
}
return B()
}
// This is what you think you're doing
class A(val a: String = "a") {
class B(val a: String = "b") {
init {
println(a)
}
}
}
fun main() {
// This refers to function called a
val func = a()
// This refers to a nested class called B
val nestedClass = A.B()
}
If you actually want to refer to the local class properties, use this

How to create custom Adapter with for 3 recycleViews

Hello All,
i want to ask if i can add to my 3 recycle Views each recycle view hase interface to optimise my code i tried to add only 1 adapter for the 3 recycle View, as you can see my code below but i find my self stuck with this adapter, any 1 have idea how add custom adapter to adapt 3 recycle View? Thanx.
class CustomAdapter(private val contexte: Context) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val context: Context = contexte
inner class FolderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
inner class PagesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
inner class CorpusViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (viewType == VIEW_TYPE_CORPUS)
return CorpusViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.corpus_item_layout, parent, false)
)
if (viewType == VIEW_TYPE_FOLDER)
return FolderViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.folder_item_layout, parent, false)
)
return PagesViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.page_item_layout, parent, false
)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
TODO("Not yet implemented")
}
override fun getItemCount(): Int {
return 20
}
companion object {
internal val VIEW_TYPE_CORPUS = 1
internal val VIEW_TYPE_FOLDER = 2
internal val VIEW_TYPE_PAGES = 2
}
Instead of doing this, I suggest you to use base class that takes layout id and initialize your common adapter with that.
open class AdapterItem(val layoutId: Int)
data class Corpus(val id: Int): AdapterItem(id)
then init your adapter like
CustomAdapter<AdapterItem>(...)
in your adapter, override getView
#Override
fun getView(position: Int, convertView: View, parent: ViewGroup): View {
val item = list[position]
return if(converView != null){
convertView
} else {
LayoutInflater.from(parent.context).inflate(item.layoutId, parent, false)
}
}

How to search a diffutil filter out results from existing list

I'm using DiffUtil in my RecyclerView to displays a list from a database using the Room component. I would like to add a search function in the Appbar, that will filter out the existing items in the list as the user is typing.
My app currently has a search icon in the action bar, when you click the search icon it will expand across the Appbar and allow the user to search the database and return a new list. This mehtod involves querying the database each time.
Search Menu, This is where the parameters for the search widget are set.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="#+id/my_search"
android:title="Search"
android:icon="#drawable/ic_search"
app:showAsAction="collapseActionView|ifRoom"
app:actionViewClass="androidx.appcompat.widget.SearchView" />
</menu>
RecyclerViewFragment
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.recycler_view_menu, menu)
val searchItem = menu.findItem(R.id.my_search)
val searchView: SearchView = searchItem.actionView as SearchView
searchView.imeOptions = EditorInfo.IME_ACTION_DONE
searchView.setIconifiedByDefault(false)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
// This approach queries my database for a new list.
viewModel.searchTopic("%$query%")
submitList()
return false
}
override fun onQueryTextChange(newText: String): Boolean {
// I would like to use the onTextChange() to filter out results from the list instead of querying a new list from the database.
return true
}
})
}
private fun submitList() {
viewModel.listDevTopics.observe(viewLifecycleOwner, Observer {
it?.let {
rvAdapter.submitList(it)
}
})
}
My RecyclerViewAdapter
class RecyclerViewAdapter() : androidx.recyclerview.widget.ListAdapter<Dev,
RecyclerViewAdapter.ItemViewHolder>(MyDiffCallback()) {
lateinit var searchList: List<Dev>
class MyDiffCallback : DiffUtil.ItemCallback<Dev>() {
override fun areItemsTheSame(oldItem: Dev, newItem: Dev): Boolean {
return oldItem.topic == newItem.topic
}
override fun areContentsTheSame(oldItem: Dev, newItem: Dev): Boolean {
return oldItem == newItem
}
}
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
...
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
...
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
...
}
I would like to avoid querying the database every time for a search result, I want to use onQueryTextChange so it goes through the existing list and updates the list as the user is entering their query.
just implement Filterable and override getFilter Method
and make your filter object then return this object at getFilter Method
class JobOrderAdapter(val clickListener: JobOrderListener) : ListAdapter<CJO,
ViewHolder>(JobOrderDiffCallback()), Filterable {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder.from(parent)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item, clickListener)
}
private var list = listOf<CJO>()
fun setData(list: List<CJO>){
this.list = list
submitList(list)
}
override fun getFilter(): Filter = customFilter
private val customFilter = object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filteredList = mutableListOf<CJO>()
if (constraint == null || constraint.isEmpty()) {
filteredList.addAll(list)
} else {
val filterPattern = constraint.toString().toLowerCase().trim()
for (item in list) {
// here i am searching at custom obj by managerName
if (item.managerName.toLowerCase().contains(filterPattern)) {
filteredList.add(item)
}
}
}
val results = FilterResults()
results.values = filteredList
return results
}
override fun publishResults(constraint: CharSequence?, filterResults: FilterResults?) {
submitList(filterResults?.values as MutableList<CJO>?)
}
}}
and from your fragmnet or activity just call adapter.filter.filter(yourQueryText)
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.home_menu, menu)
val searchByContract = menu.findItem(R.id.search_by_name)
val searchContractView = searchByContract.actionView as SearchView
searchContractView.queryHint = "البحث باسم مدير البيع"
searchContractView.inputType = InputType.TYPE_CLASS_TEXT
searchContractView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
adapter.filter.filter(newText)
return false
}
})
super.onCreateOptionsMenu(menu, inflater)
}

How can I convert this Firebase Recycler Adapter implementation into an Adapter.kt class?

I have written a Kotlin FirebaseRecyclerAdapter that works just fine as part of my MainActivity. However, I would like to have this code in a separate MainAdapter.kt file/class. How can I do this?
var query = FirebaseDatabase.getInstance()
.reference
.child("").child("categories")
.limitToLast(50)
val options = FirebaseRecyclerOptions.Builder<Category>()
.setQuery(query, Category::class.java)
.setLifecycleOwner(this)
.build()
val adapter = object : FirebaseRecyclerAdapter<Category, CategoryHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryHolder {
return CategoryHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.category_row, parent, false))
}
protected override fun onBindViewHolder(holder: CategoryHolder, position: Int, model: Category) {
holder.bind(model)
}
override fun onDataChanged() {
// Called each time there is a new data snapshot. You may want to use this method
// to hide a loading spinner or check for the "no documents" state and update your UI.
// ...
}
}
class CategoryHolder(val customView: View, var category: Category? = null) : RecyclerView.ViewHolder(customView) {
fun bind(category: Category) {
with(category) {
customView.textView_name?.text = category.name
customView.textView_description?.text = category.description
}
}
}
Given your code you could do something like this :
class MainAdapter(lifecycleOwner: LifecycleOwner) : FirebaseRecyclerAdapter<Category, CategoryHolder>(buildOptions(lifecycleOwner)) {
companion object {
private fun buildQuery() = FirebaseDatabase.getInstance()
.reference
.child("").child("categories")
.limitToLast(50)
private fun buildOptions(lifecycleOwner:LifecycleOwner) = FirebaseRecyclerOptions.Builder<Category>()
.setQuery(buildQuery(), Category::class.java)
.setLifecycleOwner(lifecycleOwner)
.build()
}
class CategoryHolder(val customView: View, var category: Category? = null) : RecyclerView.ViewHolder(customView) {
fun bind(category: Category) {
with(category) {
customView.textView_name?.text = category.name
customView.textView_description?.text = category.description
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryHolder {
return CategoryHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.category_row, parent, false))
}
protected override fun onBindViewHolder(holder: CategoryHolder, position: Int, model: Category) {
holder.bind(model)
}
override fun onDataChanged() {
// Called each time there is a new data snapshot. You may want to use this method
// to hide a loading spinner or check for the "no documents" state and update your UI.
// ...
}
}
There are many other ways to handle this problem, this is just an encapsulated version of yours.