I'm adding data to my list but it doesn't appear in RecyclerView (Kotlin, MVVM) - kotlin

I am making a simple application. There are 2 screens in the application: List screen, Add screen.
Scenario: When the application is opened, there is a list page on the screen. The user goes to the Adding page with the help of the Floating Action Button on the list page. Here, he enters the items with the help of Edit Text and comes to the List page by pressing the Add Button.
I am using MVVM in this application and I am a beginner. So I can't find the problem. Here is the problem: I am adding new elements to my list in the Add Fragment, but these elements are not seems in the List Fragment.
Thanks in advance.
Model Class
data class Movie(
val movieName: String,
val releaseDate: String
)
ViewModel class
class MovieViewModel : ViewModel() {
val movies = MutableLiveData<ArrayList<Movie>>()
var movieList = arrayListOf<Movie>()
fun addMovie(movie: Movie) {
movieList.add(movie)
movies.value = movieList
}
}
Adapter Class
class MovieAdapter : RecyclerView.Adapter<MovieAdapter.MyViewHolder>() {
private var movieList = emptyList<Movie>()
class MyViewHolder(private val binding: RowItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(movie: Movie) {
binding.textViewMovieName.text = movie.movieName
binding.textViewReleaseDate.text = movie.releaseDate
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = RowItemBinding.inflate(layoutInflater, parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = movieList[position]
holder.bind(currentItem)
}
override fun getItemCount(): Int {
return movieList.size
}
fun setData(movie: List<Movie>) {
val movieDiffUtil = MovieDiffUtil(movieList, movie)
val movieDiffResult = DiffUtil.calculateDiff(movieDiffUtil)
this.movieList = movie
movieDiffResult.dispatchUpdatesTo(this)
}
}
Add Fragment
class AddFragment : Fragment() {
private var _binding: FragmentAddBinding? = null
private val binding get() = _binding!!
private val movieViewModel: MovieViewModel by viewModels<MovieViewModel>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentAddBinding.inflate(inflater, container, false)
binding.buttonAdd.setOnClickListener {
insertData()
}
return binding.root
}
private fun insertData() {
val movieName = binding.editTextMovieName.text.toString()
val releaseDate = binding.editTextReleaseDate.text.toString()
val movie = Movie(movieName, releaseDate)
movieViewModel.addMovie(movie)
findNavController().navigate(R.id.action_addFragment_to_listFragment)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
List Fragment
class ListFragment : Fragment() {
private var _binding: FragmentListBinding? = null
private val binding get() = _binding!!
private val adapter: MovieAdapter by lazy { MovieAdapter() }
private val movieViewModel: MovieViewModel by viewModels<MovieViewModel>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentListBinding.inflate(inflater, container, false)
setAdapter()
movieViewModel.movies.observe(viewLifecycleOwner, { data ->
adapter.setData(data)
binding.textView.setText(data.get(0).movieName)
})
binding.floatingActionButton.setOnClickListener {
findNavController().navigate(R.id.action_listFragment_to_addFragment)
}
return binding.root
}
private fun setAdapter() {
val recyclerView = binding.recyclerView
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireActivity())
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

After adding an item to a List<> you need to notify the Adapter that the data has changed.
You can do so by calling adapter.notifyDataSetChanged().
I added my List directly into the Adapter but you can create a function like in the following stackoverflow post.
How to update my Recyclerview using kotlin android?

Related

how can i add counter to viewpager2

image
I couldn't find how to do the counter algorithm
here is the adapter i wrote for my viewpager
class ViewPagerAdapter(val images : List,val mContext: Context) :
RecyclerView.Adapter<ViewPagerAdapter.ViewPagerViewHolderClass>() {
inner class ViewPagerViewHolderClass(view: View) : RecyclerView.ViewHolder(view){
val viewPagerFoto : ImageView
init {
viewPagerFoto = view.findViewById(R.id.viewPagerFoto)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewPagerViewHolderClass {
val view = LayoutInflater.from(mContext).inflate(R.layout.item_view_pager,parent,false)
return ViewPagerViewHolderClass(view)
}
override fun onBindViewHolder(holder: ViewPagerViewHolderClass, position: Int) {
val image = images[position]
holder.viewPagerFoto.setImageResource(image)
}
override fun getItemCount(): Int {
return images.size
}
}
My home Fragment
I am performing operations on homefragment
class HomeFragment : Fragment() {
private lateinit var _binding : FragmentHomeBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = FragmentHomeBinding.inflate(inflater,container,false)
val images = listOf(
R.drawable.f1,
R.drawable.f2,
R.drawable.f3,
R.drawable.f4,
R.drawable.f5,
R.drawable.f6,
)
val adapter by lazy { ViewPagerAdapter(images,requireContext()) }
_binding.viewPager.adapter = adapter
return _binding.root
}
}
override fun onBindViewHolder(holder: ViewPagerViewHolderClass, position: Int) {
val image = images[position]
holder.viewPagerFoto.setImageResource(image)
}
You can use position parameter, if you want just use it on there.
But i suggest that use it on ViewPagerViewHolderClass with declaring new parameter named position and pass it over there.
In onBindViewHolder method you have position parameter. You can pass it to viewHolder and use it

RecyclerView adapter doesnt execute any methods

I am working on a simple project. I am trying to show list of data but in adapter none of the methods are called
class PlacnikiAdapter(private val placniki: List<Placnik>) :
RecyclerView.Adapter<PlacnikiAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView
init {
// Define click listener for the ViewHolder's View.
textView = view.findViewById(R.id.ime)
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(viewGroup.context).inflate(R.layout.layout_rv_item,
viewGroup, false)
return ViewHolder(inflater)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.text = placniki[position].ime
}
override fun getItemCount(): Int {
return placniki.size
}
class PlacnikiFragment : Fragment() {
private val TAG = "PlacnikiFragment"
private lateinit var binding: PlacnikiFragmentBinding
lateinit var viewModel: MainViewModel
private val retrofitService = RetrofitService.getInstance()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Defines the xml file for the fragment
return inflater.inflate(R.layout.placniki_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = PlacnikiFragmentBinding.inflate(layoutInflater)
//get viewmodel instance using MyViewModelFactory
viewModel =
ViewModelProvider(this, ViewModelFactory(Repository(retrofitService))).get(
MainViewModel::class.java
)
//set recyclerview adapter
viewModel.placnikiList.observe(viewLifecycleOwner, {
Log.d(TAG, "placnikiList: $it")
binding.recyclerview.adapter = PlacnikiAdapter(it)
})
viewModel.errorMessage.observe(viewLifecycleOwner, {
Log.d(TAG, "errorMessage: $it")
})
viewModel.getVsiPlacniki()
}
I dont know what could be causing this. I changed activity to a fragment and beforehand everything worked normally and after changing to fragment recyclerview isnt showing items and the list isnt empty either so i dont pass list of zero items
I found the solution, the problem was in data binding. I had to add below code in gradle module
buildFeatures{dataBinding = true}

How to pass data from a BottomSheetFragment to another fragment?

I have an Activity, called 'HomeActivity', where I have a bottom navigation menu and four fragments. The activity has a FloatingActionButton and when I hit this, it opens a bottomSheetFragment which contain a RecyclerView and when I hit an item from it, I want some values to send to the fragment ('HomeFragment') which is part of the 'HomeActivity'
After doing a lot of searches online, I have done the following but I am not able to get the data in the fragment.
Following is the fragment from which I want to send the data.
'HomeBottomSheetFragment.kt'
class HomeBottomSheetFragment: BottomSheetDialogFragment() {
private lateinit var binding: FragmentHomeBottomSheetBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
FirestoreClass().getCategoryList(this)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
binding = FragmentHomeBottomSheetBinding.inflate(inflater, container, false)
return binding.root
}
fun successCategoryList(categoryList: ArrayList<ProdCategories>) {
binding.rvHomeCategories.visibility = View.VISIBLE
binding.rvHomeCategories.layoutManager = GridLayoutManager(context, 4)
binding.rvHomeCategories.setHasFixedSize(true)
val categoryAdapter = CategoryListAdapter(requireContext(), categoryList)
binding.rvHomeCategories.adapter = categoryAdapter
categoryAdapter.setOnClickListener(object :CategoryListAdapter.OnClickListener{
override fun onClick(position: Int, category: ProdCategories) {
val myFragment = HomeFragment()
val bundle = Bundle()
bundle.putString("category", category.category_name)
myFragment.arguments = bundle
fragmentManager?.beginTransaction()?.replace(R.id.nav_host_fragment,HomeFragment())?.commit()
}
})
}
}
Following is the adapter of the above fragment ('HomeBottomSheetFragment.kt'), 'CategoryListAdapter.kt'.
open class CategoryListAdapter(private val context: Context, private var list: ArrayList<ProdCategories>)
: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var onClickListener: OnClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return MyViewHolder(
HomeBottomsheetCategoryListLayoutBinding.inflate(
LayoutInflater.from(
parent.context
), parent, false
)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val model = list[position]
if (holder is MyViewHolder) {
GlideLoader(context).loadProductPicture(model.category_image, holder.binding.ivCategoryImage)
holder.binding.tvCategoryName.text=model.category_name
}
holder.itemView.setOnClickListener {
if (onClickListener != null) {
onClickListener!!.onClick(position, model)
}
}
}
override fun getItemCount(): Int {
return list.size
}
fun setOnClickListener(onClickListener: OnClickListener) {
this.onClickListener = onClickListener
}
interface OnClickListener{
fun onClick(position: Int, category: ProdCategories)
}
private class MyViewHolder(val binding: HomeBottomsheetCategoryListLayoutBinding) : RecyclerView.ViewHolder(binding.root)
}
Following is the fragment where I want to receive the data.
class HomeFragment : BaseFragment() {
private var filterCategory: String?= null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
binding = FragmentHomeBinding.inflate(inflater, container, false)
val bundle = this.arguments
if (bundle!=null) {
filterCategory = bundle.getString("category")
}
return binding.root
}
}
All I know is that I am not doing things right, please help me fix it.
In this line,
fragmentManager?.beginTransaction()?.replace(R.id.nav_host_fragment,HomeFragment())?.commit()
Replace HomeFragment() with myFragment because that's where you are attaching the bundle.
See if this solves your problem.

RecyclerView in Fragment not populating

So I've been wrestling with this for days and I need some help. I've made this code work in an activity, but then I move it to a fragment it doesn't work. Everything else is the same between the two.
Using the debugger with the working Activity, the line
apiService = retrofit.create<HomeJsonApiService>(HomeJsonApiService::class.java)
goes to getItemCount(). However in the fragment it goes directly to onCreateView in the Fragment. I've attached my code below. Thanks in advance for the help! And be gentle. I'm still new to this :)
First is my fragment:
class TabHomeActivity : Fragment() {
val itemList = ArrayList<HomeCards>()
lateinit var adapter: HomeCardsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var binding = FragmentTabHomeActivityBinding.inflate(layoutInflater)
adapter = HomeCardsAdapter()
var rv = binding.rvHomeCards
rv.adapter = adapter
loadData()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.cards_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
private fun loadData() {
ApiManager.getInstance().service.listHeroes()
.enqueue(object : Callback<ResponseData<List<HomeCards>>> {
override fun onResponse(
call: Call<ResponseData<List<HomeCards>>>,
response: Response<ResponseData<List<HomeCards>>>
) {
val listData: List<HomeCards> = response.body()!!.data
// updating data from network to adapter
itemList.clear()
itemList.addAll(listData)
adapter.updateData(itemList)
adapter.notifyDataSetChanged()
}
override fun onFailure(call: Call<ResponseData<List<HomeCards>>>, t: Throwable) {
}
})
}
}
The HTTP request:
data class ResponseData<T> (
val code: Int,
val data: T
)
interface HomeJsonApiService {
#GET("marvel-heroes.asp?h=2")
fun listHeroes(): retrofit2.Call<ResponseData<List<HomeCards>>>
}
class ApiManager {
private var apiService: HomeJsonApiService? = null
init {
createService()
}
val service: HomeJsonApiService get() = apiService!!
private fun createService() {
val loggingInterceptor =
HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Log.i("Retrofit", message)
}
})
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor(loggingInterceptor)
.build()
val retrofit: Retrofit = Retrofit.Builder()
.client(client)
.baseUrl("https://www.mywebsite.com/jsonfolder/JSON/")
.addConverterFactory(GsonConverterFactory.create())
.build()
apiService = retrofit.create(HomeJsonApiService::class.java)
}
companion object {
private var instance: ApiManager? = null
fun getInstance(): ApiManager {
return instance ?: synchronized(this) {
ApiManager().also { instance = it }
}
}
}
}
And my adapter:
class HomeCardsAdapter() : RecyclerView.Adapter<HomeCardsAdapter.ViewHolder>() {
private lateinit var itemList: List<HomeCards>
lateinit var context: Context
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
context = parent.context
val view = LayoutInflater.from(context).inflate(R.layout.cards_home, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return if (::itemList.isInitialized) itemList.size else 0
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind()
}
fun updateData(list: List<HomeCards>) {
itemList = list;
notifyDataSetChanged()
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
//var binding = ActivityMainBinding(layoutInflater(inf))
fun bind() {
val item = itemList.get(adapterPosition)
ViewHolder(itemView).itemView.findViewById<TextView>(R.id.cardHomeTitle).text = item.name
ViewHolder(itemView).itemView.findViewById<TextView>(R.id.cardHomeTitle).text = item.superheroName
Glide.with(context)
.load(item.photo)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.circleCrop()
.into(ViewHolder(itemView).itemView.findViewById<ImageView>(R.id.cardHomeIcon))
}
}
}
class HomeCards {
#SerializedName("superhero_name")
var superheroName: String = ""
var name: String = ""
var photo: String = ""
}
The main problem is:
var binding = FragmentTabHomeActivityBinding.inflate(layoutInflater)
That is inside on onCreate but onCreateView is returning another view inflater.inflate(R.layout.cards_home, container, false)
So you are applying the adapter to a recycler that is on the binding, but the view on the screen is inflated from the layout. Change it to this:
private lateinit var binding: FragmentTabHomeActivityBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
binding = FragmentTabHomeActivityBinding.inflate(layoutInflater, container, false)
return binding.root
}
And move the code from from onCreate to onViewCreated but make sure to use the lateinit binding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = HomeCardsAdapter()
var rv = binding.rvHomeCards
rv.adapter = adapter
loadData()
}
After that there is a problem in your adapter: private lateinit var itemList: List<HomeCards> very specifically List<HomeCards>. The method notifyDataSetChanged doesn't work by changing or updating the reference of the data structure but when the collection is modified. Change it to this:
private val list = mutableListOf<HomeCards>()
override fun getItemCount(): Int {
return list.size()
}
fun updateData(list: List<HomeCards>) {
this.itemList.clear()
this.itemList.addAll(list)
notifyDataSetChanged()
}
If onResponse() gets called and provides response, verify that code updating UI is running on main/ui thread. Common source of issue when working with network (other threads).
activity?.runOnUiThread {
itemList.clear()
itemList.addAll(listData)
adapter.updateData(itemList)
adapter.notifyDataSetChanged()
}

RecyclerView doesn't display data but onBindViewHolder never called

I'm using recyclerView to display my data but anything is displaying. The onBindViewHolder is never called and the onCreateViewHolder also.
I initialize the adapter in the viewModel with the data which I got from room but anything is displaying. The data in the viewModel is there when I want give it to the adaptater.
There's the adaptater,
class CountryInfoAdapter : RecyclerView.Adapter<TextItemViewHolder>() {
var data = listOf<DevByteCountryProperty>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemCount() = data.size
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
val item = data[position]
holder.textView.text = item.name.toString()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
//The inflater knows how to create views from XML layout. The context is the context of the RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.text_item_view, parent, false) as TextView
Timber.i("Data from adaptater : ${data.toString()}")
return TextItemViewHolder(view)
}
}
There is the Fragment,
class CountryInfoFragment : Fragment() {
private lateinit var viewModel: CountryInfoViewModel
private lateinit var viewModelFactory: CountryInfoViewModelFactory
private lateinit var binding: FragmentCountryInfoBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater,
R.layout.fragment_country_info,container,false)
val application = requireNotNull(this.activity).application
viewModelFactory = CountryInfoViewModelFactory(application)
viewModel = ViewModelProvider(this,viewModelFactory).get(CountryInfoViewModel::class.java)
binding.viewModel = viewModel
val adapter = CountryInfoAdapter()
viewModel.countriesProperty.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})
binding.countryList.adapter = adapter
binding.lifecycleOwner = viewLifecycleOwner
setHasOptionsMenu(true)
return binding.root
}
}
Please try below code, setting up layout manager before assigning adapter to recyclerView
binding.countryList.layoutManager = LinearLayoutManager(this)
binding.countryList.adapter = adapter