I have a kotlin app of ListView of items from firebase.
The app can insert items to the firebase and everything works great with simple array adapter.
BUT what if I need to do that without using simple array adapter?
In the data class I have toString() and I need to use it without the simple adapter
data class Product(var name:String, var quantity: Int, var size: String, var comment:String){
private var _ky:String = ""
constructor() : this("", 0, "", "")
override fun toString(): String {
return "%s, %d, %s\n%s".format(this.name,this.quantity,this.size,this.comment)
}
fun fillKey(ky:String) {
this._ky = ky
}
fun giveKey():String {
return this._ky
}
}
In the activity I have this
val products = ArrayList<Product>()
val adptr: ArrayAdapter<Product> by lazy {ArrayAdapter<Product>(this, android.R.layout.simple_list_item_checked, products)}
But I don't know what to do with this if I don't use an adapter
lvItems.adapter = adptr
lvItems.choiceMode = ListView.CHOICE_MODE_MULTIPLE
myRef.child("products").addChildEventListener(object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
val p: Product = snapshot.getValue(Product::class.java) as Product
p.fillKey(snapshot.key!!)
products.add(p)
adptr.notifyDataSetChanged()
lvItems.smoothScrollToPosition(products.size - 1)
I need to do make it work without an adapter because of my mentor decision.
Related
IS it possible for a recyclerview to not update when the activity is opened, but will update if reopened?
db = FirebaseFirestore.getInstance()
db.collection("Helpers")
.whereEqualTo("helperReady", true)
.whereEqualTo("state", "Online")
.addSnapshotListener(object : EventListener<QuerySnapshot> {
override fun onEvent(value: QuerySnapshot?, error: FirebaseFirestoreException?) {
if (error != null) {
Log.d("Firestore Error: ", error.message.toString())
return
}
newArrayList.clear()
for (dc: DocumentChange in value?.documentChanges!!) {
val currentUser = dc.document.toObject(HelperList::class.java)
if (dc.type == DocumentChange.Type.ADDED) {
if (auth.currentUser?.uid != currentUser?.userID) {
newArrayList.add(dc.document.toObject(HelperList::class.java))
newArrayList.sortByDescending {
it.rating
}
}
}
}
helperAdapter.notifyDataSetChanged()
}
})
Adapter:
class HelperAdapter(private val context: Context, private val helperList: ArrayList<HelperList>) : RecyclerView.Adapter<HelperAdapter.MyViewHolder>(){
private lateinit var mListener: onItemClickListener
interface onItemClickListener{
fun onItemClick(position: Int)
}
fun setOnItemClickListener(listener:onItemClickListener){
mListener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val helperView = LayoutInflater.from(parent.context).inflate(R.layout.helpers_list_view, parent, false)
return MyViewHolder(helperView, mListener)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentHelper = helperList[position]
var delimiter = " "
var firstName = currentHelper.helperName!!.substringBefore(delimiter)
holder.helperName.text = firstName
holder.helperBio.text = currentHelper.helperBio
holder.helperRating.text = currentHelper.rating
Glide.with(context).load(currentHelper.profileImage).into(holder.helperImage)
}
override fun getItemCount(): Int {
return helperList.size
}
class MyViewHolder(itemView: View, listener: onItemClickListener) : RecyclerView.ViewHolder(itemView){
val helperImage: CircleImageView = itemView.findViewById(R.id.helper_image)
val helperName: TextView = itemView.findViewById(R.id.helper_name)
val helperBio: TextView = itemView.findViewById(R.id.helper_bio)
val helperRating: TextView = itemView.findViewById(R.id.helper_rating)
init{
itemView.setOnClickListener {
listener.onItemClick(adapterPosition)
}
}
}
}
Array List:
data class HelperList(var helperName: String ?= null, var helperBio: String?=null, var helperPay: String ?=null, var helperSkills: String ?=null,
var helperNumber: String ?=null, var profileImage: String ?= null, var rating: String ?= null,
val userID: String, var tokenID: String ?= null){
constructor():this ("","", "", "", "", "", "", "", "")
}
This code updates the list but everytime a data changes, the list goes completely blank and will only show the updated item, when reopened it shows the item with the rest of the list. I want it to stay the way it is and the update to only show if the activity is reopened. Any help is appreciated.
Edit: This is all on the assumption that newArrayList is the list used by the RecyclerView to display data.
Let's check your code.
You are updating the list as it is being changed.
If we look at your code, you are listening to data changes in firebase
.addSnapshotListener
This means that whenever data changes in the database, your code will trigger.
In your code, you are then doing this
newArrayList.clear()
which basically clears the data in the list.
After that, you check if this event is true if (dc.type == DocumentChange.Type.ADDED), so you only add a newly added item in the now empty list. After all this, you then triger helperAdapter.notifyDataSetChanged() so the recyclerView updates with only the now one item size list.
Not sure if it will be the perfect approach, but you can remove the call to newArrayList.clear(), and then your list will update in real time without clearing everything currently in it.
If you want to make it even better, you should separate the data updating from the recyclerView update. Ideally, you should create a function in your class to set the new list of items.
This way, you can even check before calling the method to set the new items if the list is empty, and only set new data if it is empty by calling rvAdapter.itemCount.
Code for the recyclerViewAdapter
private var dataList: MutableList<MyData> = mutableListOf()
//for setting all the data when list is empty.
fun setItems(newItemsList : List<MyData>){
dataList.clear()
dataList.addAll(newItemsList)
notifyDataSetChanged()
}
//for only adding items to an existing list.
fun addItems(newItemsList : List<MyData){
//here maybe do some logic if your data is sorted and you don't want to break it
val lastIndex = dataList.lastIndex
dataList.addAll(lastIndex,newItemsList)
notifyItemRangeInserted(lastIndex, newItemsList.size)
}
Then listen in the activity/fragment to data changes, and call the corresponding function.
I'm making a function that looks like an image.
Although not shown in the image, these items can be added or deleted through the button.
Item is composed of Header and Detail. The adapter also has HeaderAdapter and DetailAdapter respectively.
I'm using ConcatAdapter to make sure there are HeaderAdapter and DetailAdatper per group rather than the whole list.
Because I thought it would be more manageable than using multiview types in one adapter (purely my opinion).
But I have a question. HeaderAdapter.
As you can see from the image, there is one header per group. So, there must be only one HeaderItem in the HeaderAdapter of each group.
In this case, I don't think there is much reason to use the Adapter.
In my case, is it better to use a multiview type for one Adapter?
RoutineItem
sealed class RoutineItem(
val layoutId: Int
) {
data class Header(
val id: String = "1",
val workout: String = "2",
val unit: String = "3",
) : RoutineItem(VIEW_TYPE) {
companion object {
const val VIEW_TYPE = R.layout.routine_item
}
}
data class Detail(
val id: String = UUID.randomUUID().toString(), // UUID
val set: Int = 1,
var weight: String ="",
val reps: String = "1"
) : RoutineItem(VIEW_TYPE) {
companion object {
const val VIEW_TYPE = R.layout.item_routine_detail
}
}
}
HeaderAdapter
class HeaderAdapter(item: RoutineItem.Header) : BaseAdapter<RoutineItem.Header>(initialItem = listOf(item)) {
override fun createViewHolder(itemView: View): GenericViewHolder<RoutineItem.Header> {
return HeaderViewHolder(itemView)
}
override fun getItemCount(): Int = 1
class HeaderViewHolder(itemView: View) : GenericViewHolder<RoutineItem.Header>(itemView)
}
DetailAdapter
class DetailAdapter(private val items: List<RoutineItem.Detail> = emptyList())
: BaseAdapter<RoutineItem.Detail>(initialItem = items) {
override fun createViewHolder(itemView: View): GenericViewHolder<RoutineItem.Detail> {
return DetailViewHolder(itemView)
}
override fun getItemCount(): Int = items.size
class DetailViewHolder(itemView: View) : GenericViewHolder<RoutineItem.Detail>(itemView)
}
Activity
class MainActivity : AppCompatActivity() {
var concatAdpater: ConcatAdapter = ConcatAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rv: RecyclerView = findViewById(R.id.rv)
val adapterItems: ArrayList<Pair<RoutineItem.Header, List<RoutineItem.Detail>>> = arrayListOf()
val childItems : List<RoutineItem.Detail> = listOf(
RoutineItem.Detail(),
RoutineItem.Detail(),
RoutineItem.Detail(),
RoutineItem.Detail(),
RoutineItem.Detail()
)
adapterItems.add(Pair(RoutineItem.Header(), childItems))
adapterItems.add(Pair(RoutineItem.Header(), childItems))
adapterItems.add(Pair(RoutineItem.Header(), childItems))
for ((header, list) in adapterItems) { // 1 adapter per group
concatAdpater.addAdapter(HeaderAdapter(header))
concatAdpater.addAdapter(DetailAdapter(list))
}
rv.adapter = concatAdpater
}
}
Because it is a test code, there are parts that are not functionally implemented! (Ex. Dynamically adding and deleting items..)
It's always better to use a single adapter because item animation and state changes are way more managable with DiffUtil. Also it's easier to maintain and way more efficient (in terms of speed and resource managment).
More detailed answers:
https://stackoverflow.com/a/53117359/6694770
https://proandroiddev.com/writing-better-adapters-1b09758407d2
Data Class
data class productAddOnVariations (var name : String? =null, var id :String? =null, var price :String? =null)
Adapter
class CartAdapter(val product: List<productAddOnVariations>) : RecyclerView.Adapter<CartAdapter.customizationViewHolder>(){
inner class customizationViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val radioButton = itemView.radioButton
val tvcustomPrice = itemView.tvCustomPrice
}
//its show the item count in recycleView
override fun getItemCount(): Int {
return product.size
}
override fun onBindViewHolder(holder: customizationViewHolder, position: Int) {
holder.radioButton.text = product[position].name
holder.tvcustomPrice.text =product[position].price
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): customizationViewHolder {
return customizationViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.customization_layout,parent,false))
}
}
BootSheet
var list1 = document.get("product_add_ons") as List<productAddOnVariations>
val userModel: productAddOnVariations = document.toObject(productAddOnVariations::class.java)!!
var hashMap = HashMap<String,String>()
hashMap.put(list1.toString(),userModel.toString()
for (list1 in userModel.toString()){
println("$key = $value")
}
cartAdapter = CartAdapter(list1)
rvChooseCustomiztion.layoutManager = LinearLayoutManager(requireContext())
rvChooseCustomiztion.adapter = cartAdapter
We have to read data from firebase and the structure is of array list as show in screenShot so how to print that time of data in recycleView where one side of it is key ="Food Name" and value =30 like this we have to print in recycleview.
It should be better to restructure your Firestore document by adding a third field called food name in order to specify "Double cheese", "Extra veggies", etc. there.
It will make it easier to fetch data from your DB.
I want to create a search function for my user to quick access to my items .
Well , the first thing is that i have my product in a room table(List) and store them in database and show them with a recyclerview in the my main activity(Home activity ) .
So i want code a Query to search between them after user click on button search .
I code my query and after use it in my home activity nothing happend .i'm using mvvm model. pls help me with this .
Code :
My Table (List of Product ) :
#Entity(tableName = "cart")
data class RoomTables(
#PrimaryKey(autoGenerate = true) val id: Int?,
#ColumnInfo val title: String,
#ColumnInfo val price: Int,
#ColumnInfo val image: Int,
#ColumnInfo var amount: Int
)
My dao :
#Query ("SELECT * FROM cart WHERE title LIKE :search")
fun searchItem (search : String?):List<RoomTables>
My Repository :
fun searchItem(search :String) = db.GetDao().searchItem(search)
My Viewmodel :
fun searchItem(search : String) = CoroutineScope(Dispatchers.Default).launch {
repository.searchItem(search)
}
And HomeActivity :
class HomeActivity : AppCompatActivity() {
lateinit var viewModelRoom: ViewModelRoom
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.home_activity)
val list = ArrayList<RoomTables>()
for (i in 0..20) {
list.add(
RoomTables(
null, "$i banana", 12,
R.drawable.bannana, 0
)
)
}
recycler_main.apply {
layoutManager = GridLayoutManager(this#HomeActivity, 2)
adapter = RecyclerAdapterMain(list, context)
}
val database = DataBaseRoom(this)
val repositoryCart = RepositoryCart (database)
val factoryRoom = FactoryRoom(repositoryCart)
viewModelRoom = ViewModelRoom(repositoryCart)
viewModelRoom = ViewModelProvider(this , factoryRoom ).get(ViewModelRoom::class.java)
val editText : EditText = findViewById(R.id.edittextSearch)
val searchbtn : ImageView = findViewById(R.id.search_main)
searchbtn.setOnClickListener{
viewModelRoom.searchItem(editText.text.toString())
}
Let's try this approach.
First get list items from the table.
#Query ("SELECT * FROM cart")
fun searchItem():List<RoomTables>
Now from your repository.
fun searchItem() : List<RoomTables> = db.GetDao().searchItem()
In ViewModel.
fun searchItem(search : String): <List<RoomTables> {
filterWithQuery(query)
return filteredList
}
private fun filterWithQuery(query: String, repository: YourRepository) {
val filterList = ArrayList<RoomTables>()
for (currentItem: RoomTables in repository.searchItem()) {
val formatTitle: String = currentItem.title.toLowerCase(Locale.getDefault())
if (formatTitle.contains(query)) {
filterList.add(currentItem)
}
}
filteredList.value = filterList
}
Make sure you add Coroutines above.
Now you have all elements filtered and returns new list items based on search query user entered.
In your fragment or activity observe data.
searchbtn.setOnClickListener{
viewModel.searchItem(query).observe(viewLifecycleOwner, Observer { items -> {
// Add data to your recyclerview
}
}
The flow and approach is correct and it is working, it is hard to follow your code since i'm not sure if the return types match because you are not using LiveData, in this case you must.
If you found confusing or hard to follow, i have a working example in github, compare and make changes.
https://github.com/RajashekarRaju/ProjectSubmission-GoogleDevelopers
I am fetching JSON from URL using OKhttp...
I am Using Kotlin.
{
"CountryName": [{
"Country": "India",
"CountryCode": "+91",
"Population": "545121546846"
},
{
"country ": "Pakistan",
"countryCode": "+92",
"Population": "23546546546"
},
{
"Country": "UK",
"CountryCode": "+42",
"Population": "545121546846"
},
{
"Country": "US",
"CountryCode": "+1",
"Population": "54512154545846"
}
]
}
When successfully fetched data its show only when user search Country and add to a list and show its related data like countryside and population. I am able to fetch JSON data, But unable to control it like when user search and add to List. This is my main Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView.layoutManager = LinearLayoutManager(this)
fetchjson()
}
//This is How I fetch JSON
fun fetchjson() {
val url = "https://firebasestorage.googleapis.com/v0/b/unity-b69ff.appspot.com/o/new.json?alt=media&token=dc24acb2-13aa-40da-b0c5-7bb3ccd59af8"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
println("Fail to load json")
}
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
println(body)
val gson = GsonBuilder().create()
val homefeed = gson.fromJson(body, HomeFeed::class.java)
runOnUiThread{
recyclerView.adapter = MainAdapter(homefeed)
}
}
})
}
}
This Is my MainAdapter
class adapter(val homefeed: jjson) : RecyclerView.Adapter
<adapter.CustomViewHolder>(), Filterable {
override fun getItemCount(): Int {
return homefeed.CountryName.count()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cellforRow = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
return CustomViewHolder(cellforRow)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
val v = homefeed.CountryName.get(position)
holder.itemView.textView2.text = v.Country
holder.itemView.textView.text = v.CountryCode.toString()
}
class CustomViewHolder(view: View) : RecyclerView.ViewHolder(view) {}
// this filter method
override fun getFilter(): Filter {
return (object: Filter(){
override fun performFiltering(constraint: CharSequence?): FilterResults {
// TODO("Not yet implemented")
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
// TODO("Not yet implemented")
}
})
}
}
//json data object
class jjson(val CountryName: List<name>)
class name{
val Country: String? = null
val CountryCode: Int? = null
val Popuplation: Long? = null
}
CAN I EXECUTE SOME CODE WRITTEN IN JSON WHILE AFTER FETCHING
Now that I understand a little better your problem, I'm writing an answer.
Remember that your adapter only takes care of displaying a set of data, in this case the list of countries you want the user to see. Currently, you're just initializing the adapter with the whole list of countries once you receive the response from the server.
That you could do is initialize the adapter with an empty list, update the adapter dataset when the countries are fetched from the server and then update it again everytime the user search query changes.
You may need to use a different type of adapter (an ArrayAdapter for example), to make it easier to work with your list data. You can read more about it here: https://developer.android.com/guide/topics/ui/declaring-layout#AdapterViews
Once you have your adapter set up, what you need to do is:
1. Initialize it with an empty list, it can be done on its constructor.
2. Update its dataset when the countries come from the server:
adapter.clear()
adapter.addAll(countries)
adapter.notifyDataSetChanged()
Update its dataset when the user makes a search:
adapter.clear()
adapter.addAll(yourFilteredCountries) // you can keep the whole list of countries in a variable in the activity and just pass a filtered list here
adapter.notifyDataSetChanged()
Note that this is a super simple approach and you can improve it in many ways, but it will help you understand how adapters work.
Let me know how it goes