I'm trying to make my Android App (I'm only experienced in iOS).
I created a RecyclerView that gets the data from a web. I tried everything to implement endless scrolling to load more items, but when I call the function to get the items, the entire RecyclerView loads again and no attach the new results on the bottom.
This is my code:
ConversationUser.kt
data class ConversationUser(
val message_nickname: String,
val message_image_thumb: String,
val message_large_thumb: String,
val message_modified: String,
val message_status: String,
val message_unread: Int,
val conv_id: String,
val message_dest: String) {
}
ConversacionesActivity.kt
class ConversacionesActivity : AppCompatActivity() {
// MARK: Variables
var user_token = ""
var user_id = ""
override fun onCreate(savedInstanceState: Bundle?) {
// User Defaults
val sharedPreferences = getSharedPreferences("Preferences", Context.MODE_PRIVATE)
user_token = sharedPreferences.getString("user_token", "")!!
user_id = sharedPreferences.getString("user_id", "")!!
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_conversaciones)
recyclerConv.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
getConversationsData()
recyclerConv.setLoadingListener(object : LoadingListener {
override fun onRefresh() {
//refresh data here
}
override fun onLoadMore() {
// load more data here
getConversationsData()
}
})
}
fun getConversationsData() {
val httpAsync = "https://mywebsite.com/conversations/${user_token}"
.httpPost()
.responseString { request, response, result ->
when (result) {
is Result.Failure -> {
val ex = result.getException()
println(ex)
}
is Result.Success -> {
val data = result.get()
runOnUiThread {
val conversaciones = processJson(data)
show(conversaciones)
return#runOnUiThread
}
}
}
}
httpAsync.join()
}
fun processJson(json: String): List<ConversationUser> {
val gson: Gson = GsonBuilder().create()
val conversaciones: List<ConversationUser> = gson.fromJson(
json,
Array<ConversationUser>::class.java
).toList()
return conversaciones
}
fun show(conversaciones: List<ConversationUser>) {
recyclerConv.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recyclerConv.adapter = AdaptadorConv(conversaciones, this, user_token, user_id)
}
AdaptadorConv.kt
class AdaptadorConv(
val conversaciones: List<ConversationUser> = ArrayList(),
val context: Context,
val user_token: String,
val user_id: String) : RecyclerView.Adapter<AdaptadorConv.ConvViewHolder>() {
override fun onBindViewHolder(holder: ConvViewHolder, position: Int) {
holder.convName.text = conversaciones[position].message_nickname
holder.convTime.text = conversaciones[position].message_modified
}
override fun getItemCount(): Int {
return conversaciones.size - 1
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ConvViewHolder {
val view: View = LayoutInflater.from(parent.context).inflate(
R.layout.conversaciones,
parent,
false
)
return ConvViewHolder(view)
}
class ConvViewHolder(vista: View): RecyclerView.ViewHolder(vista) {
val convImg: ImageView = itemView.findViewById(R.id.convImg)
val convStatus: ImageView = itemView.findViewById(R.id.convStatus)
val convName: TextView = itemView.findViewById(R.id.convName)
val convUnread: TextView = itemView.findViewById(R.id.convUnread)
val convTime: TextView = itemView.findViewById(R.id.convTime)
}
Thanks for any help or hint.
Please check your show () method, you are creating new Adapter every time with the new dataset. You have to append the new items to the adapter's list and adapter should be set to list once. Helpful tutorial can be found at here.
Related
I have one RecyclerView, and if I click one item of it, I want make Data of RecyclerView change.
companion object {
var regionData: MutableLiveData<List<String>> = MutableLiveData()
var smallRegionScreen : Boolean = false
}
So I use MutableLiveData to make Data mutable and keep being observed.
adapter = regionData.value?.let { RegionAdapter(this, it, smallRegionScreen) }!!
I pass regionData.value as Data of Adapter, whose type will be List. And smallRegionScreen is Boolean value.
Since first click of item and second click of item in RecyclerView's taken action will be different, so I differentiate it by this value.
regionDB.get()
.addOnSuccessListener { documents ->
for (document in documents) {
var newArray = ArrayList<String>()
Log.d("리지온1", "$document")
for ((k, v) in document.data) {
regionData.value.add(v.String)
Log.d("리지온", "${regionData.value}")
}
}
adapter.notifyDataSetChanged()
}
binding.regionRecycler.adapter=adapter
binding.regionRecycler.layoutManager= LinearLayoutManager(this)
}
As here, I add item to regionData.value.
But it shows empty Array.
What is problem here?
And My Adapter is below, my process is okay?
class RegionAdapter(private var context: Context, private var regionData: List<String>, private var smallRegionScreen: Boolean): RecyclerView.Adapter<RegionAdapter.RegionViewHolder>() {
var userDB = Firebase.firestore.collection("users")
var userId = Firebase.auth.currentUser?.uid
companion object {
var REGION_RECYCLER_CLICKED = "com.chungchunon.chunchunon_android.REGION_RECYCLER_CLICKED"
}
inner class RegionViewHolder(ItemView: View) : RecyclerView.ViewHolder(ItemView) {
val regionView: TextView = itemView.findViewById(R.id.regionSelectText)
fun bind (position: Int) {
regionView.text = regionData[position]
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RegionViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_region, parent, false)
return RegionViewHolder(view)
}
override fun onBindViewHolder(holder: RegionViewHolder, position: Int) {
holder.bind(position)
holder.itemView.setOnClickListener { view ->
if(!smallRegionScreen) {
var selectedRegion = regionData[position]
var regionSet = hashMapOf(
"region" to selectedRegion
)
userDB.document("$userId").set(regionSet)
var regionDB = Firebase.firestore.collection("region")
regionDB
.document("4ggk4cR82mz46CjrLg60")
.collection(selectedRegion.toString())
.get()
.addOnSuccessListener { documents ->
for (document in documents) {
for ((k, v) in document.data) {
regionData.plus(v.toString())
}
}
smallRegionScreen = true
}
} else {
var selectedSmallRegion = regionData[position]
var regionSet = hashMapOf(
"smallRegion" to selectedSmallRegion
)
userDB.document("$userId").set(regionSet)
}
}
}
override fun getItemCount(): Int {
return regionData.size
}
}
If you want to add data to your MutableLiveData:
val regionDataList = regionData.value
val templateList = mutableListOf<String>()
regionDataList?.forEach { data ->
templateList.add(data)
}
templateList.add(v.String)
regionData.value = templateList
you can add data in the list like this :-
regionData.value.add(v.toString())
I have a RecyclerView where an item can be edited via a DialogFragment, so when an item is clicked a Dialog is shown, then I can change some properties of that item, the issue is that RecyclerView is not updated with the updated properties and I have to force a notifyItemChanged when the Dialog is closed.
When an item in RecyclerView is clicked I set a MutableLiveData in my ViewModel so then it can be manipulated in the Dialog.
My ViewModel looks like this:
#HiltViewModel
class DocumentProductsViewModel #Inject constructor(private val repository: DocumentProductsRepository) :
ViewModel() {
val barcode = MutableLiveData<String>()
private val _selectedProduct = MutableLiveData<DocumentProduct>()
val selectedProduct: LiveData<DocumentProduct> = _selectedProduct
private val _selectedDocumentId = MutableLiveData<Long>()
val selectedDocumentId: LiveData<Long> = _selectedDocumentId
val products: LiveData<List<DocumentProduct>> = _selectedDocumentId.switchMap { documentId ->
repository.getDocumentProducts(documentId).asLiveData()
}
fun insert(documentProduct: DocumentProduct) = viewModelScope.launch {
repository.insert(documentProduct)
}
fun setProductQuantity(quantity: Float) {
_selectedProduct.value = _selectedProduct.value.also {
it?.timestamp = System.currentTimeMillis()
it?.quantity = quantity
}
update()
}
fun start(documentId: Long?) = viewModelScope.launch{
if (documentId == null) {
_selectedDocumentId.value = repository.getHeaderByType("Etichette")?.id
}
documentId?.let { documentId ->
_selectedDocumentId.value = documentId
}
}
fun select(product: DocumentProduct) {
_selectedProduct.value = product
}
fun delete() = viewModelScope.launch {
_selectedProduct.value?.let { repository.delete(it) }
}
private fun update() = viewModelScope.launch {
_selectedProduct.value?.let { repository.update(it) }
}
}
And in my fragment I'm subscribed to products as this:
private fun initRecyclerView() {
binding.rvProducts.adapter = adapter
viewModel.products.observe(viewLifecycleOwner) { products ->
val productsCount = products.count()
binding.tvProductsCount.text =
resources.getQuantityString(R.plurals.articoli, productsCount, productsCount)
// TODO: create amount string and set it with resources
binding.tvProductsAmount.text = productsCount.toEuro()
adapter.submitList(products)
binding.rvProducts.smoothScrollToPosition(adapter.itemCount - 1)
}
initSwipe(adapter)
}
When setProductQuantity is called the RecyclerView remains unchanged until notify is called while delete works fine without the necessity of calling any notify on RecyclerView.
UPDATE:
The item position is actually changed in RecyclerView as it's sorted by it's last changed timestamp BUT not the quantity field.
Here is my Adapter:
class DocumentProductsListAdapter : ListAdapter<DocumentProduct, DocumentProductsListAdapter.ViewHolder>(ProductDiffCallback) {
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
val view: View = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.layout_item, viewGroup, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val product = getItem(position)
holder.bind(product)
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val barcode: TextView = itemView.findViewById(R.id.barcode)
val quantity: TextView = itemView.findViewById(R.id.quantity)
val description: TextView = itemView.findViewById(R.id.description)
val unitOfMeasure: TextView = itemView.findViewById(R.id.unitOfMeasure)
fun bind(product: DocumentProduct) {
barcode.text = product.barcode
quantity.text = product.quantity.formatForQta().replace(".", ",")
if (product.labelType != null && product.labelType != "") {
unitOfMeasure.text = product.labelType
} else {
unitOfMeasure.text = product.unitOfMeasure?.lowercase(Locale.ITALIAN)
}
description.text = product.description ?: "-"
}
}
}
object ProductDiffCallback : DiffUtil.ItemCallback<DocumentProduct>() {
override fun areItemsTheSame(oldItem: DocumentProduct, newItem: DocumentProduct): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: DocumentProduct, newItem: DocumentProduct): Boolean {
return oldItem == newItem
}
}
data class DocumentProduct(
#PrimaryKey(autoGenerate = true)
var id: Long,
var barcode: String,
#Json(name = "desc")
var description: String?,
#ColumnInfo(defaultValue = "PZ")
#Json(name = "um")
var unitOfMeasure: String?,
#Json(name = "qta")
var quantity: Float,
#Json(name = "id_testata")
var documentId: Long,
#Json(name = "tipo_frontalino")
var labelType: String?,
var timestamp: Long?
) {
constructor(barcode: String, documentId: Long, labelType: String?) : this(
0,
barcode,
null,
"PZ",
1f,
documentId,
labelType,
null
)
override fun equals(other: Any?): Boolean {
return super.equals(other)
}
override fun hashCode(): Int {
return super.hashCode()
}
}
You have the implementations of areContentsTheSame() and areItemsTheSame() swapped.
areContentsTheSame() is asking if everything in the two items being compared is the same. Therefore, if the class has a proper equals()/hashcode() for all properties used by the ViewHolder, you can use oldItem == newItem. If you use a data class with all relevant properties in the primary constructor, then you don't need to manually override equals()/hashcode().
areItemsTheSame() is asking if the two items represent the same conceptual row item, with possible differences in their details. So it should be oldItem.id == newItem.id.
The problem with your data class is that you are overriding equals()/hashcode() without providing any implementation at all. This is effectively disabling the proper implementations that are provided by the data modifier by calling through to the super implementation in the Any class. You should not override them at all when you use data class.
I have two activity and each activity has recyclerview, I have nested json.I want when user tap first activity recyclerview then they goes to next activity and show the recyclerview.Better understanding please follow the json link and code.
Json Link
: https://run.mocky.io/v3/8c3f6be2-a53f-47da-838c-72e603af844d
CropData.kt
data class CropData(
#SerializedName("cropImage")
val cropImage: String,
#SerializedName("cropName")
val cropName: String,
#SerializedName("cropTime")
val cropTime: String,
#SerializedName("cropDetails")
val cropDetails: String,
#SerializedName("cropProcess")
val cropProcess: String,
#SerializedName("cropType")
val cropType: String,
#SerializedName("cropFertilizer")
val cropFertilizer: List<CropFertilizer>
)
CropFertilizer.kt
data class CropFertilizer(
#SerializedName("fertilizerName")
val fertilizerName: String,
#SerializedName("fertilizerFirst")
val fertilizerFirst: String,
#SerializedName("fertilizerSecond")
val fertilizerSecond: String,
#SerializedName("fertilizerThird")
val fertilizerThird: String
)
CropActivity.kt
class CropActivity : AppCompatActivity(), CropOnItemClickListener {
private lateinit var viewModel: CropActivityViewModel
private val cropAdapter by lazy { CropAdapter(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_crop)
val repository = Repository()
val viewModelFactory = CropActivityViewModelFactory(repository)
viewModel = ViewModelProvider(this, viewModelFactory).get(CropActivityViewModel::class.java)
viewModel.getCropData()
viewModel.cropResponse.observe(this, Observer { response ->
cropAdapter.setData(response)
Log.d("dCrop", response.toString())
})
setUpCrops()
}
private fun setUpCrops() {
crop_recyclerview.layoutManager = LinearLayoutManager(
this,
LinearLayoutManager.VERTICAL, false
)
crop_recyclerview.setHasFixedSize(true)
crop_recyclerview.adapter = cropAdapter
}
override fun onClick(item: CropData, position: Int) {
val intent = Intent(this, CropDetailsActivity::class.java)
intent.putExtra("name", item.cropName)
intent.putExtra("image", item.cropImage)
intent.putExtra("intro", item.cropDetails)
intent.putExtra("process", item.cropProcess)
intent.putExtra("type", item.cropType)
intent.putExtra("time", item.cropTime)
startActivity(intent)
}
}
CropAdapter.kt
class CropAdapter(private val cropOnItemClickListener: CropOnItemClickListener) :
RecyclerView.Adapter<CropAdapter.ViewHolder>() {
private var cropList = emptyList<CropData>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.crop_row,parent,false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.crop_name.text = cropList[position].cropName
holder.itemView.crop_details.text = cropList[position].cropDetails
holder.itemView.crop_time.text = cropList[position].cropTime
holder.itemView.setOnClickListener{
cropOnItemClickListener.onClick(cropList[position],position)
}
}
override fun getItemCount(): Int {
return cropList.size
}
fun setData(newList: List<CropData>){
notifyDataSetChanged()
cropList = newList
notifyDataSetChanged()
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
}
CropDetailsActivity.kt
class CropDetailsActivity : AppCompatActivity() {
private lateinit var viewModel: CropDetailsActivityViewModel
private val fertlizerAdapter by lazy { FertilizerAdapter() }
private val cropFertilizerAdapter by lazy { FertilizerAdapter() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_crop_details)
val bundle:Bundle? = intent.extras
crop_details_name.text = bundle!!.getString("name")
val i: String = bundle!!.getString("intro")
crop_details_intro.text = i
Glide.with(this).load(bundle.getString("image")).into(crop_details_image);
val j: String = bundle!!.getString("process")
crop_details_process.text = j
crop_details_time.text = bundle!!.getString("time")
crop_details_type.text = bundle!!.getString("type")
val repository = Repository()
val viewModelFactory = CropDetailsActivityViewModelFactory(repository)
viewModel = ViewModelProvider(this, viewModelFactory).get(CropDetailsActivityViewModel::class.java)
viewModel.getFertilizer()
viewModel.fResponse.observe(this, Observer {
cropFertilizerAdapter.setData(it)
})
crop_details_recyclerview.layoutManager = LinearLayoutManager(
this,
LinearLayoutManager.VERTICAL, false
)
crop_details_recyclerview.setHasFixedSize(true)
crop_details_recyclerview.adapter = cropFertilizerAdapter
}
}
Better understanding see the image
[1]: https://i.stack.imgur.com/BtPqe.jpg
I want to show CropFertilizer list in CropDetailsActivity.How can i do this?
I just want to say sorry to my English skill
I've studied the Android Studio and Kotlin these days.
but I'd got a problem on RecyclerViewer and Adapter, for Intent method
work flow chart
this image, this is what i want to do
so i coded the three classes
ShoppingAppActivity.kt, MyAdapter.kt, CartActivity.kt
At ShoppingAppActivity, If I click the itemId ( in the Red box texts)
I make it move to other context(CartActivity)
ShoppingAppActivity working
if i clicked the red box then
cartStatus
go to cart Activity
it worked but already I said, I just want to send only send itemID
covert to String (i will use toString())
SO i tried to use Intent method in ShoppingAppActivity.kt
///PROBLEM PART
adapter?.setOnItemClickListener{
val nextIntent = Intent(this, CartActivity::class.java)
//nextIntent.putExtra("itemID", )
startActivity(nextIntent)
}
///PROBLEM PART
like this but the problem is I don't know what am i have to put the parameter in
nextIntent.putExtra("itemID", )
what should i do?
I think, I should fix MyAdaptor.kt or ShopingAppActivity.kt for this problem.
But in my knowledge, this is my limit. :-(
below
Full Codes of ShoppingAppActivity.kt, MyAdapter.kt, CartActivity.kt
ShoppingAppActivity.kt
class ShoppingAppActivity : AppCompatActivity() {
lateinit var binding: ActivityShoppingAppBinding
private var adapter: MyAdapter? = null
private val db : FirebaseFirestore = Firebase.firestore
private val itemsCollectionRef = db.collection("items")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityShoppingAppBinding.inflate(layoutInflater)
setContentView(binding.root)
updateList()
binding.recyclerViewItems.layoutManager = LinearLayoutManager(this)
adapter = MyAdapter(this, emptyList())
binding.recyclerViewItems.adapter = adapter
///PROBLEM PART
adapter?.setOnItemClickListener{
val nextIntent = Intent(this, CartActivity::class.java)
//nextIntent.putExtra("itemID", )
startActivity(nextIntent)
}
///PROBLEM PART
}
private fun updateList() {
itemsCollectionRef.get().addOnSuccessListener {
val items = mutableListOf<Item>()
for (doc in it) {
items.add(Item(doc))
}
adapter?.updateList(items)
}
}
}
MyAdapter.kt
data class Item(val id: String, val name: String, val price: Int, val cart: Boolean) {
constructor(doc: QueryDocumentSnapshot) :
this(doc.id, doc["name"].toString(), doc["price"].toString().toIntOrNull() ?: 0, doc["cart"].toString().toBoolean() ?: false)
constructor(key: String, map: Map<*, *>) :
this(key, map["name"].toString(), map["price"].toString().toIntOrNull() ?: 0, map["cart"].toString().toBoolean() ?: false)
}
class MyViewHolder(val binding: ItemBinding) : RecyclerView.ViewHolder(binding.root)
class MyAdapter(private val context: Context, private var items: List<Item>)
: RecyclerView.Adapter<MyViewHolder>() {
fun interface OnItemClickListener {
fun onItemClick(student_id: String)
}
private var itemClickListener: OnItemClickListener? = null
fun setOnItemClickListener(listener: OnItemClickListener) {
itemClickListener = listener
}
fun updateList(newList: List<Item>) {
items = newList
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding: ItemBinding = ItemBinding.inflate(inflater, parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = items[position]
val itemID : String
holder.binding.textID.text = item.id
holder.binding.textName.text = item.name
if(item.cart)
{
holder.binding.textCart.text = "in Cart"
}
else
{
holder.binding.textCart.text = ""
}
holder.binding.textID.setOnClickListener {
AlertDialog.Builder(context).setMessage("You clicked ${item.id}.").show()
itemClickListener?.onItemClick(item.id)
}
holder.binding.textName.setOnClickListener {
//AlertDialog.Builder(context).setMessage("You clicked ${student.name}.").show()
itemClickListener?.onItemClick(item.id)
}
//return item.id.toString()
}
override fun getItemCount() = items.size
}
CartActivity.kt
class CartActivity : AppCompatActivity() {
lateinit var binding: ActivityCartBinding
private val db: FirebaseFirestore = Firebase.firestore
private val itemsCollectionRef = db.collection("items")
private var adapter: MyAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCartBinding.inflate(layoutInflater)
setContentView(binding.root)
updateList()
//binding.recyclerViewItems.layoutManager = LinearLayoutManager(this)
//adapter = MyAdapter(this, emptyList())
//binding.recyclerViewItems.adapter = adapter
binding.changeCartStatus.setOnClickListener{
//change the button's text if the itemID is corrected
//if(){
// binding.changeCartStatus.text = ""
//}
}
}
private fun updateList() {
itemsCollectionRef.get().addOnSuccessListener {
val items = mutableListOf<Item>()
for (doc in it) {
items.add(Item(doc))
}
adapter?.updateList(items)
}
}
}
You just need to implement listener to your activity
class ShoppingAppActivity : AppCompatActivity() ,MyAdapter.OnItemClickListener {
In oncreate add below line after adapter
adapter?.setOnItemClickListener(this)
Then override its method
override fun onItemClick(id: String){
val nextIntent = Intent(this, CartActivity::class.java)
nextIntent.putExtra("itemID",id )
startActivity(nextIntent)
}
i have a problem, when i add a new element which is a data class to arraylist, it's always replace the current element with the new elements. Can you give me a insight what's the problem and what's the solution. Thanks in advance
Model
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.android.parcel.Parcelize
#Parcelize
data class ScheduleModel(
#field:SerializedName("user_available_id")
var userAvailableId: String? = null,
#field:SerializedName("teacher_id")
var teacherId: String? = null,
#field:SerializedName("schedule")
var schedule: ArrayList<schedule?>? = null
) : Parcelable
#Parcelize
data class schedule(
#field:SerializedName("event_id")
var eventId: String? = null,
#field:SerializedName("schedule_time")
var scheduleTime: String? = null,
#field:SerializedName("status")
var status: String? = null
) : Parcelable
I'm using recyclerview to add element to arraylist. the element is data class model. And here's the flow first i get the data from API then display the data with recyclerview. and then after display i want take the display data and save it to array list. after save data to arraylist, the arraylist will send the data to api like this :
{
"user_available_id": 702,
"teacher_id" : 3207,
"schedule" : [{
"event_id" : 47533,
"schedule_time" : "2020-11-30 07:00:00",
"status" :1
},
{
"event_id" : 47532,
"schedule_time" : "2020-11-30 06:30:00",
"status" :1
}]
}
i'm struggling with the data that save to arraylist always overwrite the current data with new data when i add new data to arraylist.
The Function
private suspend fun getMultiSlotJadwal(id: String, date: String) {
jamList.clear()
val networkConfig =
NetworkConfig().getTeacher().getTeacherScheduleAvailability(token, id, date)
if (networkConfig.isSuccessful) {
if (networkConfig.body()!!.availability!!.isEmpty()) {
binding.rvSlot.visibility = View.GONE
Handler(Looper.getMainLooper()).post {
Toast.makeText(
this,
"Jam tidak tersedia",
Toast.LENGTH_SHORT
).show()
}
} else {
for (slot in networkConfig.body()!!.availability!!) {
//convert tanggal start ke millis
val tanggalSlot = slot!!.start!!.toDate().formatTo("yyyy-MM-dd HH:mm")
val tanggalInMillis = convertToMillis(tanggalSlot)
//ambil tanggal sekarang
val myFormat = "yyyy-MM-dd HH:mm" // format tanggal
val calendar = Calendar.getInstance()
val time = calendar.time
val sdf = SimpleDateFormat(myFormat, Locale.getDefault())
val curdate = sdf.format(time) //diconvert ke tanggal local
val curDateinMillis = convertToMillis(curdate) // convert ke millis
val hasilDate = tanggalInMillis - curDateinMillis
val tanggalJam = hasilDate / 3600000 //diubah dari millis ke jam
if (tanggalJam >= 6) {
jamList.add(slot)
val sortJamList = jamList.sortedBy { jamList -> jamList.start }
binding.rvSlot.visibility = View.VISIBLE
val adapter = SlotJamMultiAdapter(sortJamList) {
teacher_id = it.teacherId.toString()
scheduleModel.userAvailableId = user_avalaible_id //model
scheduleModel.teacherId = teacher_id
scheduleItem.scheduleTime = it.start.toString()
scheduleItem.status = "1"
scheduleItem.eventId = it.id.toString()
scheduleList.add(scheduleItem) // array list
scheduleModel.schedule = scheduleList
Log.d(TAG, "getMultiSlotJadwal: $scheduleList")
itemClicked = true
changeBackgroundButtonSesi2()
}
adapter.submitList(sortJamList)
binding.rvSlot.adapter = adapter
}
}
}
} else {
Handler(Looper.getMainLooper()).post {
Toast.makeText(
this,
"Jam tidak tersedia",
Toast.LENGTH_SHORT
).show()
}
}
}
Edit
here's my adapter after using diffutils
Adapter
class SlotJamMultiAdapter(
private var data: List<AvailabilitySlotItem>,
private val listener: (AvailabilitySlotItem) -> Unit
) : RecyclerView.Adapter<SlotJamMultiAdapter.LeagueViewHolder>() {
private lateinit var ContextAdapter: Context
class LeagueViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvJam = view.findViewById<TextView>(R.id.tv_slot_jam_list)
val cvSlot = view.findViewById<CardView>(R.id.cv_slot_list)
val llSlot = view.findViewById<LinearLayout>(R.id.ll_cv_slot)
fun bidnItem(
data: AvailabilitySlotItem,
listener: (AvailabilitySlotItem) -> Unit,
context: Context,
position: Int
) {
val jam = data.start!!.toDate().formatTo("HH:mm")
tvJam.text = jam
itemView.setOnClickListener {
listener(data)
}
}
private fun String.toDate(
dateFormat: String = "yyyy-MM-dd HH:mm:ss",
timeZone: TimeZone = TimeZone.getTimeZone("UTC")
): Date {
val parser = SimpleDateFormat(dateFormat, Locale.getDefault())
parser.timeZone = timeZone
return parser.parse(this)
}
private fun Date.formatTo(
dateFormat: String,
timeZone: TimeZone = TimeZone.getDefault()
): String {
val formatter = SimpleDateFormat(dateFormat, Locale.getDefault())
formatter.timeZone = timeZone
return formatter.format(this)
}
}
class slotItemDiffCallback(
var oldSlotList: List<AvailabilitySlotItem>,
var newSlotList: List<AvailabilitySlotItem>
) : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldSlotList.size
}
override fun getNewListSize(): Int {
return newSlotList.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return (oldSlotList.get(oldItemPosition).id == newSlotList.get(newItemPosition).id)
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldSlotList.get(oldItemPosition).equals(newSlotList.get(newItemPosition))
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): SlotJamMultiAdapter.LeagueViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
ContextAdapter = parent.context
val inflatedView: View = layoutInflater.inflate(R.layout.slot_list, parent, false)
return SlotJamMultiAdapter.LeagueViewHolder(inflatedView)
}
override fun onBindViewHolder(holder: SlotJamMultiAdapter.LeagueViewHolder, position: Int) {
holder.bidnItem(data[position], listener, ContextAdapter, position)
}
override fun getItemCount(): Int = data.size
fun submitList(availabilitySlotItemList: List<AvailabilitySlotItem>) {
val oldList = data
val diffResult:DiffUtil.DiffResult=DiffUtil.calculateDiff(
slotItemDiffCallback(
oldList,availabilitySlotItemList
)
)
data = availabilitySlotItemList
diffResult.dispatchUpdatesTo(this)
}
}
Because every time you get the list with getMultiSlotJadwal(), You create a new adapter instance and assign it to the recyclerview.
binding.rvSlot.adapter = SlotJamMultiAdapter(sortJamList) { }
Keep referencing the assigned adapter, and then when you get the new list from the server, provide the list to the adapter you were previously referencing.
You can search ListAdatper / DiffUtil.Callback, they check if the items in the new list are the same items in the old list on behalf of you. All you have to do is submit the new list to the ListAdapter like below
val newList = getNewListFromServer()
myAdapter.submitList(list)
Updated
Ok, for the ScheduleModel
class ScheduleModel(
val userAvailableId : String?,
val teacherId : String?,
val schedule : List<String>?
) {
class ScheduleDiffUtilCallback : DiffUtil.ItemCallback<ScheduleModel>(){
override fun areItemsTheSame(oldItem: ScheduleModel, newItem: ScheduleModel): Boolean {
return true
// set the way how to identify if the newItem is the same as the oldItem
}
override fun areContentsTheSame(oldItem: ScheduleModel, newItem: ScheduleModel): Boolean {
return true
// set the way how to identify if the newItem's contents is the same as the oldItem's contents
}
}
}
for the Adapter
class SlotJamMultiAdapter : ListAdapter<ScheduleModel, SlotJamMultiAdapter.ViewHolder>(
ScheduleModel.ScheduleDiffUtilCallback()
){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_schedule, parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item)
}
class ViewHolder(root : View) : RecyclerView.ViewHolder(root) {
fun bind(item : ScheduleModel) {
//bind data to view here
}
}
}
Node that you don't actually reference data list yourself in the adapter class. ListAdapter will do reference it and do all the thing you need for updating data.
All you have to do is, in fragment or activity
val adapter = SlotJamMultiAdapter()
rc.adapter = adapter
And when your data changes(if you get the new list)
adapter.submitList(newList)
Note that I didn't create a new Adapter instance, I am referencing the old adapter that was attached to the recyclerview, and I am reusing it