I am fetching JSON data from API and passing it in recycler view but if I want to fetch new data and display it in recycler view then I have to clear the list and then add new data in that list and notify the adapter that the data is changed but it is not updated what should I do?
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var recipeViewModel: RecipeViewModel
private lateinit var mainBinding: ActivityMainBinding
private lateinit var recipeAdapter: RecipeAdapter
private lateinit var recipeItemList: ArrayList<Hit>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
recipeViewModel =
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory
.getInstance(application)
)[RecipeViewModel::class.java]
recipeItemList = arrayListOf()
mainBinding.recyclerView.layoutManager = LinearLayoutManager(this)
mainBinding.recyclerView.hasFixedSize()
recipeAdapter = RecipeAdapter(this)
mainBinding.recyclerView.adapter = recipeAdapter
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
recipeItemList.addAll(recipeItems.hits)
recipeAdapter.updateRecipes(recipeItemList)
Log.d("RESPONSE", recipeItems.toString())
Log.d("List size", recipeAdapter.itemCount.toString())
})
searchRecipeName()
}
private fun searchRecipeName() {
mainBinding.searchRecipeFabBtn.setOnClickListener {
val view = layoutInflater.inflate(R.layout.recipe_search_layout, null)
val searchRecipeET = view.findViewById<EditText>(R.id.searchRecipeET)
val searchRecipeBtn = view.findViewById<Button>(R.id.searchRecipeBtn)
val bottomSheetDialog = BottomSheetDialog(this)
bottomSheetDialog.apply {
this.setContentView(view)
this.show()
}
searchRecipeBtn.setOnClickListener {
val recipeName = searchRecipeET.text.toString()
searchRecipeName(recipeName, searchRecipeET, bottomSheetDialog)
}
}
}
private fun searchRecipeName(
recipeName: String,
searchRecipeET: EditText,
bottomSheetDialog: BottomSheetDialog
) {
if (recipeName.isEmpty()) {
searchRecipeET.error = "Please enter recipe name"
} else {
recipeViewModel.getRecipes(recipeName)
bottomSheetDialog.dismiss()
}
}
}
RecipeAdapter.kt
class RecipeAdapter(val context: Context) : RecyclerView.Adapter<RecipeAdapter.RecipeViewHolder>() {
private val recipesList: ArrayList<Hit> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater.inflate(R.layout.recipe_items_layout, null, false)
return RecipeViewHolder(view)
}
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
val currentItem = recipesList[position]
holder.recipeImageView.load(currentItem.recipe.image)
holder.recipeNameText.text = currentItem.recipe.label
}
override fun getItemCount(): Int {
return recipesList.size
}
class RecipeViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val recipeImageView: ImageView = itemView.findViewById(R.id.recipeImageView)
val recipeNameText: TextView = itemView.findViewById(R.id.recipeNameText)
}
fun updateRecipes(newRecipesList: ArrayList<Hit>){
recipesList.clear()
Log.d("RECIPE SIZE", "${recipesList.size}")
recipesList.addAll(newRecipesList)
notifyDataSetChanged()
}
}
This may be helpful.
Be careful of this :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
recipeViewModel =
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory
.getInstance(application)
)[RecipeViewModel::class.java]
recipeItemList = arrayListOf()
mainBinding.recyclerView.layoutManager = LinearLayoutManager(this)
mainBinding.recyclerView.hasFixedSize()
recipeAdapter = RecipeAdapter(this)
mainBinding.recyclerView.adapter = recipeAdapter
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
// You're adding items here but never clear the list
// list will be bigger every time you'll be notified
// recipeItemList.addAll(recipeItems.hits
// recipeAdapter.updateRecipes(recipeItemList)
// Do this instead
recipeItemList = recipeItems.hits
recipeAdapter.updateRecipes(recipeItemList)
Log.d("RESPONSE", recipeItems.toString())
Log.d("List size", recipeAdapter.itemCount.toString())
})
searchRecipeName()
}
Also, here: It's a little better to do this (https://stackoverflow.com/a/10298038/4221943)
fun updateRecipes(newRecipesList: ArrayList<Hit>){
recipesList = newRecipesList
Log.d("RECIPE SIZE", "${recipesList.size}")
notifyDataSetChanged()
}
BTW it will always be more efficient to use the more specific change events if you can. Rely on notifyDataSetChanged() as a last resort. It is also good practice to use notifyItemInserted(mItems.size() - 1) for "easier" solution.
You could convert the RecyclerView.Adapter into a ListAdapter:
class RecipeAdapter(val context: Context) : ListAdapter<Hit, RecipeAdapter.RecipeViewHolder>(RecipeDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater.inflate(R.layout.recipe_items_layout, null, false)
return RecipeViewHolder(view)
}
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
val currentItem = getItem(position)
holder.recipeImageView.load(currentItem.recipe.image)
holder.recipeNameText.text = currentItem.recipe.label
}
class RecipeViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val recipeImageView: ImageView = itemView.findViewById(R.id.recipeImageView)
val recipeNameText: TextView = itemView.findViewById(R.id.recipeNameText)
}
}
class RecipeDiffCallback : DiffUtil.ItemCallback<Hit>() {
// Change this condition based on the attribute of `Hit` that will change
override fun areItemsTheSame(oldItem: Hit, newItem: Hit): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Hit, newItem: Hit): Boolean = oldItem == newItem
}
Then update its content with the submitList method.
Every item not satisfying the RecipeDiffCallback conditions will be automatically updated:
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
recipeAdapter.submitList(recipeItems.hits)
})
Related
I have a recyclerview in my project, where a user will be able to drag and drop it to a different view(card view).
I am following mvvm pattern and using dataBinding. I use the longclick function from within the activity,i created an interface in adapter class for this.
inorder to use the drag and drop i need to get the adapter position of the item in activity, which i am unable to get.
here is the adapter class
class StatementAdapter(
private val context: Context,
private val statementList: ArrayList<Statement>
) :
RecyclerView.Adapter<StatementAdapter.StatementViewHolder>() {
private var mListener: OnItemLongClickListener? = null
interface OnItemLongClickListener {
fun onItemLongClick(statement: Statement)
}
fun setOnItemLongClickListener(listener: OnItemLongClickListener) {
mListener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatementViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val statementBinding: StatementBinding =
DataBindingUtil.inflate(layoutInflater, R.layout.statement_list, parent, false)
return StatementViewHolder(statementBinding, mListener)
}
override fun getItemCount(): Int {
return statementList.size
}
override fun onBindViewHolder(holder: StatementViewHolder, position: Int) {
val statementViewModel = statementList[position]
holder.bind(statementViewModel)
}
class StatementViewHolder(
private val statementBinding: StatementBinding,
private val listener: OnItemLongClickListener?
) : RecyclerView.ViewHolder(statementBinding.root) {
fun bind(statementViewModel: Statement) {
this.statementBinding.statementModel = statementViewModel
itemView.setOnLongClickListener {
listener?.onItemLongClick(
statementViewModel
)
true
}
statementBinding.executePendingBindings()
}
}
}
Here is the activity
class TrueOrFalseActivity : AppCompatActivity(), StatementAdapter.OnItemLongClickListener {
private lateinit var trueOrFalseBinding: ActivityTrueOrFalseBinding
private var rvStatement: RecyclerView? = null
private var statementAdapter: StatementAdapter? = null
private val dragMessage = "Added"
private val inBucket = "in bucket"
private val offBucket = "not in bucket"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
trueOrFalseBinding = ActivityTrueOrFalseBinding.inflate(layoutInflater)
setContentView(trueOrFalseBinding.root)
rvStatement = findViewById(R.id.rvStatement)
val statementViewModel = ViewModelProvider(this).get(StatementViewModel::class.java)
statementViewModel.generateStatement()
statementViewModel.newMStatementList.observe(this) {
statementAdapter = StatementAdapter(this#TrueOrFalseActivity, it)
rvStatement!!.layoutManager = LinearLayoutManager(this#TrueOrFalseActivity)
rvStatement!!.adapter = statementAdapter
statementAdapter?.setOnItemLongClickListener(this)
}
}
override fun onItemLongClick(statement: Statement) {
val item = ClipData.Item(dragMessage)
val dragData = ClipData(
dragMessage,
arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
item
)
val myShadow = MyDragShadowBuilder(this)
// Here where i need to get the item position
}
}
class MyDragShadowBuilder(trueOrFalseActivity: TrueOrFalseActivity) {
}
I am following the following documentation
https://developer.android.com/guide/topics/ui/drag-drop.html#AboutDragging
in this, the "view" is the parameter, but in my case it is arecyclerview item.
how do i solve this?
I am creating a GenericAdapter for handling single row|item layouts. Everything working fine only view-binding is not updating data..
I want to get RecyclerView.ViewHolder binding in callback ,I know I can bind it in adapter using BR.item and executePending
I want viewDataBinding context in a Callback
holder.binding.name.text = mutableList[pos]
Above line in TestActivity not working properly
GenericAdapter.kt
class GenericAdapter<T,VB:ViewDataBinding>(
var items:MutableList<T>,
#LayoutRes val resLayoutID:Int,
val onBind:(holder:GenericViewHolder<T,VB>,pos:Int) -> Unit
): RecyclerView.Adapter<GenericViewHolder<T,VB>>() {
lateinit var mItemBinding:VB
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GenericViewHolder<T,VB> {
val layoutInflater = LayoutInflater.from(parent.context)
mItemBinding = DataBindingUtil.inflate(layoutInflater, resLayoutID, parent, false)
return GenericViewHolder(mItemBinding)
}
override fun onBindViewHolder(holder: GenericViewHolder<T,VB>, position: Int) {
onBind(holder,position)
}
override fun getItemCount(): Int = items.size
}
GenericViewHolder.kt
class GenericViewHolder<T,VB: ViewDataBinding>(val binding: VB)
:RecyclerView.ViewHolder(binding.root){
val mItemBinding:VB = binding
}
TestActivity.kt
class TestActivity:AppCompatActivity() {
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
recyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(true)
populateList()
}
private fun populateList(){
val mutableList = mutableListOf<String>("apple","mango","tutti fruti","apricot",
"apple","mango","tutti fruti","apricot",
"apple","mango","tutti fruti","apricot",
"apple","mango","tutti fruti","apricot")
val mAdapter = GenericAdapter<String,ItemCountryBinding>(mutableList,R.layout.item_country){ holder,pos ->
//val nameTv = holder.itemView.findViewById<TextView>(R.id.name)
//nameTv.text = mutableList[pos]
holder.binding.name.text = mutableList[pos]
}
recyclerView.adapter = mAdapter
}
}
Where as below code working fine
val nameTv = holder.itemView.findViewById<TextView>(R.id.name)
nameTv.text = mutableList[pos]
I would recommend do onBind inside view holder.
I don't see reason why you need this callback.
You already passed mutableList to the adapter.
For example here is my ViewHolder
class NewsViewHolder(private val binding: ListItemNewsBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(news: RedditPost, itemClick: (RedditPost, View) -> Unit) {
with(binding) {
root.transitionName = news.thumbnail
root.setOnClickListener { itemClick.invoke(news, binding.root) }
title.text = news.title
image.setImageUrl(news.thumbnail)
author.text = news.author
xHoursAgo.text = news.created_utc
numComments.text = news.numComments
}
}
}
And
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (isLoadingMore && position == (itemCount - 1)) return
(holder as? NewsViewHolder)?.bind(news[position], itemClick)
}
I have a RecyclerView and my Fragment. I am getting data from Firebase Realtime Database into a RecyclerView.
I need to make it so that while the data is loading, I see some kind of loading effect. How can i do this?
Code from my Fragment:
private var _binding: FragmentDayDetailBinding? = null
private val binding get() = _binding!!
private var ref: DatabaseReference? = null
private lateinit var adapter: DayDetailAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentDayDetailBinding.inflate(inflater, container, false)
setupRecyclerView()
initDatabase()
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
readFromDatabase()
}
private fun setupRecyclerView() {
adapter = DayDetailAdapter()
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.adapter = adapter
}
private fun initDatabase() {
FirebaseApp.initializeApp(requireContext())
ref = FirebaseDatabase.getInstance()
.getReference("IMIT")
.child("groups")
}
private fun readFromDatabase() {
ref?.addValueEventListener(object: ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
val list = ArrayList<Day>()
for (daySnapshot in snapshot.children) {
val day = daySnapshot.getValue(Day::class.java)
list.add(day!!)
}
adapter.submitList(list)
} else {
binding.apply {
lrDbEmpty.visibility = View.VISIBLE
recyclerView.visibility = View.INVISIBLE
}
}
}
override fun onCancelled(error: DatabaseError) {
}
})
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
Code from RecyclerView Adapter:
class ViewHolder(private val binding: ItemSubjectDetailBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(day: Day) = with(binding) {
tvSubject.text = day.subject
tvInfo.text = "${day.teacher}, ${day.type}"
tvTime.text = day.time
tvAud.text = day.classroom
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemSubjectDetailBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
with(holder) {
bind(getItem(position))
}
}
class ItemComparator: DiffUtil.ItemCallback<Day>() {
override fun areItemsTheSame(oldItem: Day, newItem: Day): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Day, newItem: Day): Boolean {
return oldItem == newItem
}
}
You already in half the way
Create a progressBar in the center of the fragment
<ProgressBar
android:id="#+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
Keep it visible until the data fetched (onDataChange Called) then hide the progressBar
override fun onDataChange(snapshot: DataSnapshot) {
binding.progressBar.visibility = View.GONE
if (snapshot.exists()) {
// rest of your code
}
}
I prefer a dynamic add view.
I use the lottie player for the show wait animation. There are two approaches for adding the show wait. In this way, you can add animation or any type of view from the non-activity class. It helps you to better implement in the MVVM form.
This is both methods implementation.
1- Create and remove by Id
2- Create and remove by LiveData
class AddViewNonActivity(
private val viewGroup: ViewGroup
) {
fun addCustomWait(): Int {
val relativeLayout =
RelativeLayout(viewGroup.context)
val relativeParams =
RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
val lottieParams =
RelativeLayout.LayoutParams(600, 600)
relativeLayout.setBackgroundColor(
viewGroup.context.getColor(R.color.wait_transparent)
)
relativeLayout.gravity = CENTER
val lottieLoading = LottieAnimationView(viewGroup.context)
lottieLoading.setAnimation("lottie/space-runner.json")
lottieLoading.repeatCount = INFINITE
lottieLoading.speed = 1f
lottieLoading.playAnimation()
relativeLayout.addView(lottieLoading, lottieParams)
val viewId = View.generateViewId()
viewGroup.addView(relativeLayout, relativeParams)
relativeLayout.id = viewId
relativeLayout.isClickable = true
return viewId
}
fun removeCustomWait(
waitViewId: Int
) {
for (view in viewGroup) {
if (view.id == waitViewId)
viewGroup.removeView(view)
}
}
fun addLiveCustomWait(
lifecycleOwner: LifecycleOwner,
liveData: LiveData<Boolean>) {
var viewId = 0
liveData.observe(lifecycleOwner) {
if (it) {
removeCustomWait(viewId)
viewId = addCustomWait()
} else {
removeCustomWait(viewId)
}
}
}
}
Activity call method
class DynamicViewActivity : AppCompatActivity() {
private lateinit var binding : ActivityDynamicViewBinding
private val liveShowWait =MutableLiveData(false)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDynamicViewBinding.inflate(layoutInflater)
setContentView(binding.root)
var waitId = 0
initialWait()
binding.btnAddView.setOnClickListener {
waitId = AddViewNonActivity(binding.root).addCustomWait()
}
binding.btnRemoveView.setOnClickListener {
AddViewNonActivity(binding.root).removeCustomWait(waitId)
}
binding.btnLiveAddView.setOnClickListener {
liveShowWait.postValue(true)
}
binding.btnLiveRemoveView.setOnClickListener {
liveShowWait.postValue(false)
}
}
private fun initialWait() =
AddViewNonActivity(binding.root).addLiveCustomWait(this as
LifecycleOwner, liveShowWait)
}
Github link
The data in the RecyclerView is called the first time without issues. However when i refresh the data, for some reason all the items goes blank.
The MainActivity is this
class BusinessActivity : AppCompatActivity() {
private val businessViewModel: BusinessViewModel by viewModel()
private val imageLoader: ImageLoader by inject()
private lateinit var staggeredGridLayoutManager: StaggeredGridLayoutManager
private lateinit var skeleton: Skeleton
private val adapter: BusinessAdapter by lazy { BusinessAdapter(imageLoader, businessViewModel) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_business)
initToolbar()
skeleton = findViewById<SkeletonLayout>(R.id.skeletonLayout)
staggeredGridLayoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
staggeredGridLayoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
recycler_view.apply {
layoutManager = staggeredGridLayoutManager
adapter = this#BusinessActivity.adapter
setHasFixedSize(true)
}
setupSkeleton()
initializeObserverBusiness()
refreshBusiness.setOnRefreshListener {
refreshBusiness.isRefreshing = true
skeleton.showSkeleton()
businessViewModel.retrieveBusiness()
}
}
private fun initToolbar() {
setSupportActionBar(toolbar)
supportActionBar?.title = getString(R.string.app_name)
this.setSystemBarColor(this)
}
private fun setupSkeleton(){
skeleton = recycler_view.applySkeleton(R.layout.business_card, 6)
skeleton.showSkeleton()
}
private fun initializeObserverBusiness(){
businessViewModel.uiState.observe(this, Observer {
val dataState = it ?: return#Observer
if (!dataState.showProgress){
refreshBusiness.isRefreshing = false
skeleton.showOriginal()
}
if (dataState.business != null && !dataState.business.consumed){
dataState.business.consume()?.let { business ->
adapter.submitList(business)
}
}
if (dataState.error != null && !dataState.error.consumed){
dataState.error.consume()?.let { error ->
Toast.makeText(this, resources.getString(error), Toast.LENGTH_LONG).show()
}
}
})
}
}
and the Adapter for the RecyclerView, im currently using DiffCallback and ListAdapter due to a better performance.
class BusinessAdapter(var imageLoader: ImageLoader, var viewModel: BusinessViewModel) : ListAdapter<Business, BusinessViewHolder>(DIFF_CALLBACK){
companion object{
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Business>() {
override fun areItemsTheSame(oldItem: Business, newItem: Business) = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Business, newItem: Business) = oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = BusinessViewHolder.create(parent)
override fun onBindViewHolder(holder: BusinessViewHolder, position: Int) {
holder.bind(getItem(position), imageLoader, viewModel)
}
}
and the ViewHolder for the Adapter
class BusinessViewHolder constructor(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bind(business: Business, imageLoader: ImageLoader, viewModel: BusinessViewModel) {
businessImage?.let { imageLoader.load("${BuildConfig.MY_URL}/gallery/${business.images[0]}", it) }
ownerBusiness.text = business.owner
businessName.text = business.name
cardBusiness.setOnClickListener {
viewModel.callDetailBusiness(business.id)
}
}
companion object {
fun create(parent: ViewGroup): BusinessViewHolder {
return BusinessViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.business_card, parent, false))
}
}
}
and the ViewModel
class BusinessViewModel (private val businessRepository: BusinessRepository): ViewModel() {
private val _uiState = MutableLiveData<BusinessDataState>()
val uiState: LiveData<BusinessDataState> get() = _uiState
val _showDetailBusiness = MutableLiveData<Int?>()
val showDetailBusiness: LiveData<Int?> get() = _showDetailBusiness
init {
retrieveBusiness()
}
fun retrieveBusiness(){
viewModelScope.launch {
runCatching {
emitUiState(showProgress = true)
businessRepository.retrieveBusiness()
}.onSuccess {
emitUiState(business = Event(it))
}.onFailure {
emitUiState(error = Event(R.string.internet_failure_error))
}
}
}
fun callDetailBusiness(businessId: Int) {
_showDetailBusiness.value = businessId
}
private fun emitUiState(showProgress: Boolean = false, business: Event<List<Business>>? = null, error: Event<Int>? = null){
val dataState = BusinessDataState(showProgress, business, error)
_uiState.value = dataState
}
data class BusinessDataState(val showProgress: Boolean, val business: Event<List<Business>>?, val error: Event<Int>?)
}
When the data is loaded for the first time i see this.
however when i apply the SwipeRefresh. I receive the data.
D/OkHttp: [{"id":18,"name":"Whatsup","owner":"Mi
Soledad","category":"ToDo",
but the RecyclerView won't attach the new information...
I have an actitvity with Recyclerview which display data. Now I want to update my RecyclerView once got a new data.For now each time I close and reopen my app the new data will be displayed. but i want it without close to update my view.
I have tried this,
but nothing will work,
fun setupViewPager(viewPager: ViewPager, it: List<TransactionEntity>, incoming: TransactionAdapterDirection, mainActivity: MainActivity) {
val cc: Context = mainActivity.applicationContext
if(adapter.count < 2) {
if (incoming.equals(OUTGOING)) {
val gson = Gson()
val gson1 = GsonBuilder().create()
val model = it
val IT = gson.toJson(model)
val pref = cc.applicationContext.getSharedPreferences("MyPrefSend", 0)
val editor = pref.edit()
editor.putString("NEWIT_SEND", IT)
editor.apply()
adapter.addFragment(SendingFragment(),"SEND")
adapter.notifyDataSetChanged()
} else if (incoming.equals(INCOMING)) {
val gson = Gson()
val gson1 = GsonBuilder().create()
val model = it
val IT = gson.toJson(model)
val pref = cc.applicationContext.getSharedPreferences("MyPrefRec", 0)
val editor = pref.edit()
editor.putString("NEWIT_REC", IT)
editor.apply()
adapter.addFragment(ReceiveFragment(), "RECEIVE")
adapter.notifyDataSetChanged()
}
viewPager.adapter = adapter
}
}
class ViewPagerAdapter(manager: FragmentManager) : FragmentStatePagerAdapter(manager)
{
private val mFragmentList: ArrayList<Fragment> = ArrayList<Fragment>()
private val mFragmentTitleList: ArrayList<String> = ArrayList<String>()
override fun getCount(): Int {
return mFragmentList.size
}
override fun getItem(position: Int): Fragment? {
var fragment: Fragment? = null
if (position == 0) {
fragment = SendingFragment()
} else if (position == 1) {
fragment = ReceiveFragment()
}
return fragment
}
fun addFragment(fragment: Fragment, title: String) {
mFragmentList.add(fragment)
mFragmentTitleList.add(title)
}
override fun getPageTitle(position: Int): CharSequence? {
return mFragmentTitleList[position]
}
override fun getItemPosition(fragItem: Any): Int {
var position = 0
if (fragItem is ReceiveFragment) {
position = 0
} else if (fragItem is SendingFragment) {
position = 1
}
return if (position >= 0) position else PagerAdapter.POSITION_NONE
}
}
**Framgnet.kt**
class ReceiveFragment: Fragment()
{
private var linearLayoutManager: LinearLayoutManager? = null
fun fromJson(jsonString: String, type: Type): Any {
return Gson().fromJson(jsonString, type)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val pref = context!!.getSharedPreferences("MyPrefRec", 0)
val mFragIT = pref.getString("NEWIT_REC", "")
val NewIT = fromJson(mFragIT,
object : TypeToken<List<TransactionEntity>>() {
}.type) as List<TransactionEntity>
val activity = activity as MainActivity
val myAppDatabaseData = activity.getAppDatabaseData()
val myNetwrk = activity.getNwtwrkData()
// Inflate the layout for this fragment
val rootView = inflater.inflate(R.layout.receive_fragment, container, false)
val recyclerView = rootView.findViewById<RecyclerView>(R.id.transaction_recycler_in) as RecyclerView
linearLayoutManager = LinearLayoutManager(activity, LinearLayout.VERTICAL, false)
recyclerView.layoutManager = linearLayoutManager
recyclerView.adapter = TransactionRecyclerAdapter(NewIT,myAppDatabaseData,TransactionAdapterDirection.INCOMING,myNetwrk)
recyclerView.setHasFixedSize(true)
return rootView
}
}
I have tried this, mAdapter.notifyDataSetChanged();
If you want to update data from activity/fragment than you can make one function inside adapter as below:
public void update(ArrayList<String> modelList){
//string arraylist is for example pass your data
// replace your adapter data with argument data
mAdapter.notifyDataSetChanged();
}
For kotlin:
fun update(modelList:ArrayList<String){
myList = modelList
myAdapter!!.notifyDataSetChanged()
}
Call this function from activity/fragment as below :
mAdapter.update(response.getList());
You can do it with a function in your ItemAdapter:
/**
* Refresh the whole data set of items.
*/
fun refreshDataset() {
mDataset = parseItems(mAppCtx)
notifyDataSetChanged()
}
and then, any time you receive a new set of data, you can call such function directly.
If you want to do in more Kotlin style do like this:
1) Create an abstract class for Adapter:
abstract class BaseRecyclerAdapter<Type, ViewHolder : BaseViewHolder<Type>>(list: List<Type> = mutableListOf()) : RecyclerView.Adapter<ViewHolder>() {
var items: MutableList<Type> = list.toMutableList()
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemCount() = items.size
enter code here
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position]) }
2) Create an abstract class for ViewHolder:
abstract class BaseViewHolder<in T>(override val containerView: View): RecyclerView.ViewHolder(containerView), LayoutContainer {
abstract fun bind(item: T) }
3) How to use:
class MyActivity : AppCompatActivity() { private var adapter: MyAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
//This is your data which you set first time
val initData = mutableListOf<String>()
adapter = MyAdapter(initData)
myRecyclerView.layoutManager = adapter
myRecyclerView.layoutManager = LinearLayoutManager(this)
}
// Call this function when you need to update adapter
private fun notifyAdapter(list: MutableList<String>){
adapter.items = list
}}
Create MyAdapter:
private class MyAdapter(list: MutableList<String>) : BaseRecyclerAdapter<String, ViewHolder>(list) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder.newInstance(parent)
}
Create ViewHolder for MyAdapter:
private class ViewHolder(containerView: View) : BaseViewHolder<String>(containerView) {
companion object {
fun newInstance(parent: ViewGroup) = ViewHolder(parent.inflate(R.layout.item))
}
override fun bind(item: String) {
title.text = item
}}
Create item for ViewHolder:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
If you have any questions please ask
You should try LiveData
LiveData is a data holder class that can be observed within a given
lifecycle. This means that an Observer can be added in a pair with a
LifecycleOwner, and this observer will be notified about modifications
of the wrapped data only if the paired LifecycleOwner is in active
state.
Documentation: https://developer.android.com/reference/android/arch/lifecycle/LiveData