The idea of my project is to click in one recyclerView in MainActivity and after one dialog with other recyclerView is opened.
The error is that my recyclerView isn't updating in the Dialog
MainActivity:
private fun openDialog(postModel: PostModel){
CommentDialogFragment(postModel).show(supportFragmentManager, "dialog")
}
CommentDialogFragment:
class CommentDialogFragment(postSelected: PostModel): AppCompatDialogFragment() {
private var mCommentsList: MutableList<CommentModel> = arrayListOf()
private val mCommentServiceRequest = CommentServiceRequest()
private lateinit var mContext: Context
private lateinit var mRecyclerView: RecyclerView
private var postModel: PostModel = postSelected
private lateinit var mCommentListAdapter: CommentListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.DialogStyle)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val viewGroup = inflater.inflate(R.layout.comment, container)
mContext = inflater.context
mCommentListAdapter = CommentListAdapter(mCommentsList)
mRecyclerView = viewGroup.findViewById(R.id.recyclerViewComments)
setupCommentObserver(postModel.id)
loadCommentRecyclerView()
dialog!!.setTitle("${mContext.getString(R.string.comments)} ${postModel.title}")
return viewGroup
}
private fun setupCommentObserver(postId: Int){
mCommentServiceRequest.searchCommentsFromAPI(postId)
.observe(this, Observer { comments ->
if (comments != null){
mCommentsList = comments.toMutableList()
mCommentListAdapter.setData(mCommentsList)
}
})
}
private fun loadCommentRecyclerView() {
mCommentsList.clear()
mRecyclerView.setHasFixedSize(true)
mRecyclerView.adapter = mCommentListAdapter
mRecyclerView.layoutManager = LinearLayoutManager(mContext)
}}
CommentListAdapter:
class CommentListAdapter(private var commentList: MutableList<CommentModel>):
RecyclerView.Adapter<CommentViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommentViewHolder {
val inflate = LayoutInflater.from(parent.context)
val view = inflate.inflate(R.layout.comment_list , parent, false)
return CommentViewHolder(view)
}
override fun getItemCount(): Int {
return commentList.count()
}
override fun onBindViewHolder(holder: CommentViewHolder, position: Int) {
holder.bindTask(commentList[position])
}
fun setData(list: MutableList<CommentModel>){
this.commentList = list
this.notifyDataSetChanged()
}}
I did the same thing in other project without use notifyDataSetChanged() only using callbacks and worked, so I tried here too but didn't work, I think the dialog is the problem
Related
First of all, I am Spanish so my english is not very good.
I have a list of items on a Recyclerview, and I also have a SearchView to filter those items.
Every item has a favourite button, so when you click, the item adds to favorite table.
The problem is that, when I filter something and I start clicking those buttons, odd things happens: some items dissapear from the filtered list. It doesn't happen always, only sometimes. How can I fix this?
My class:
class CoasterFragment : Fragment() {
private val myAdapter by lazy { CoasterRecyclerViewAdapter(CoasterListenerImpl(requireContext(), viewModel),requireContext()) }
private lateinit var searchView: SearchView
private var _binding: FragmentCoasterBinding? = null
private val binding get() = _binding!!
private val viewModel: CoastersViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentCoasterBinding.inflate(inflater, container, false)
val root: View = binding.root
val recyclerView = binding.recyclerCoaster
recyclerView.adapter = myAdapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
viewModel.coasters().observe(viewLifecycleOwner){myAdapter.setData(it)}
searchView = binding.search
searchView.clearFocus()
searchView.setOnQueryTextListener(object: SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
if(query != null){
searchDatabase(query)
searchView.clearFocus()
}
return true
}
override fun onQueryTextChange(query: String?): Boolean {
if(query != null){
searchDatabase(query)
}
return true
}
})
return root
}
fun searchDatabase(query: String) {
val searchQuery = "%$query%"
viewModel.searchDatabase(searchQuery).observe(viewLifecycleOwner) { myAdapter.setData(it)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
My adapter:
class CoasterRecyclerViewAdapter( val listener: CoasterListener,
val context: Context ) : RecyclerView.Adapter<CoasterRecyclerViewAdapter.ViewHolder>(){
private var coasterList = emptyList<CoasterFavorito>()
class ViewHolder private constructor(val binding: CoasterItemBinding, private val listener: CoasterListener,
private val context: Context): RecyclerView.ViewHolder(binding.root){
companion object{
fun crearViewHolder(parent: ViewGroup, listener: CoasterListener, context: Context):ViewHolder{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CoasterItemBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding, listener, context )
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder.crearViewHolder(parent, listener, context)
override fun onBindViewHolder(holder: ViewHolder, position: Int){
holder.binding.nombre.text = coasterList[position].coaster.nombre
holder.binding.parque.text = coasterList[position].coaster.parque
holder.binding.ciudad.text = coasterList[position].coaster.ciudad
holder.binding.provincia.text = coasterList[position].coaster.provincia
holder.binding.comunidad.text = coasterList[position].coaster.comunidadAutonoma
Glide
.with(context)
.load(coasterList[position].coaster.imagen)
.centerCrop()
.into(holder.binding.imagen)
holder.binding.check.isChecked = coasterList[position].favorito
holder.binding.check.setOnClickListener{
if (coasterList[position].favorito) {
listener.delFavorito(coasterList[position].coaster.id)
holder.binding.check.isChecked = false
} else {
listener.addFavorito(coasterList[position].coaster.id)
holder.binding.check.isChecked = true
}
}
}
override fun getItemCount(): Int{
return coasterList.size
}
fun setData(coaster: List<CoasterFavorito>){
coasterList = coaster
notifyDataSetChanged()
}
}
interface CoasterListener {
fun addFavorito(id: Long)
fun delFavorito(id: Long)
}
I tried changing the focus, changing the notifydatasetchanged with notifyitemchanged, and nothing happens...
I am a newcomer in Kotlin and synchronous programming.
I have code:
open class MyFragment : DialogFragment() {
private var fragmentBinding: FragmentBinding? = null
private var resultList: List<MyObject> = ArrayList()
private var list = ArrayList<MyObjectItem>()
private lateinit var adapter: MynAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
resultList = getActivity.loadObjects()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentBinding = FragmentBinding.inflate(inflater, container, false)
lifecycleScope.launch {
getListData()
initAdapter()
}
return fragmentBinding?.root
}
private fun initAdapter() {
val layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(requireContext())
fragmentBinding?.recyclerView?.layoutManager = layoutManager
adapter = MyAdapter(list, requireContext(), this, this)
fragmentBinding?.recyclerView?.adapter = adapter
}
private fun getListData() {
for (value in resultList) {
list.add(
MyObjectItem(
value.title!!,
value.numbert!!,
)
)
}
}
as result I get empty list in UI. But in debug I see, that from loadObjects() method I get not empty list. I understand , that it works in debug mode only because I stop execution of UI thread on my breakpoint and I should set up the view (namely calling initAdapter() on the UI thread, not in my worker thread. But I don't understand, how can I do this....
Here is the simple example of the adapter. You can use addAll() function of the adapter to add the data into the adapter.
public class SenListAdapter() :
RecyclerView.Adapter<SenListAdapter.MyViewHolder>() {
var data: MutableList<QuizResponse.Datum> = ArrayList()
var itemListener: EventListener? = null
var inflater: LayoutInflater? = null
private var onLoadMoreListener: OnLoadMoreListener? = null
var mData = ""
var context:Context?=null
constructor(context: Context, quizType: String) : this() {
this.quizType = quizType
this.context=context
}
override fun getItemViewType(position: Int): Int {
return position
}
fun addAll(mData: List<QuizResponse.Datum>?) {
data.clear();
data.addAll(mData!!)
notifyDataSetChanged()
}
fun clear() {
data.clear()
notifyDataSetChanged()
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): SenListAdapter.MyViewHolder {
inflater = LayoutInflater.from(parent.context)
val itemHomeSenBinding = DataBindingUtil.inflate<ItemHomeSenBinding>(
inflater!!,
R.layout.item_home_sen, parent, false
)
return MyViewHolder(itemHomeSenBinding)
}
override fun onBindViewHolder(holder: SenListAdapter.MyViewHolder, position: Int) {
populateItemRows(holder, position)
}
private fun populateItemRows(holder: SenListAdapter.MyViewHolder, position: Int) {
var item = data.get(position)
holder.itemHomeSenBinding.tvQuizName.text = item.name
holder.itemHomeSenBinding.tvCost.text = "₹ " + item.entryFee.toString()
holder.itemHomeSenBinding.tvEstimatedTime.text = item.time
}
override fun getItemCount(): Int {
return data.size
}
inner class MyViewHolder(var itemHomeSenBinding: ItemHomeSenBinding) :
RecyclerView.ViewHolder(
itemHomeSenBinding.root
) {
init {
setEventlistener(itemListener)
}
}
interface EventListener {
fun onClick(position: Int, item: QuizResponse.Datum?)
}
fun setEventlistener(onItemClick: EventListener?) {
itemListener = onItemClick
}
}
To set the adapter just use the object of recycler view :
var adapter=SenListAdapter()
adapter.addAll(<Your List>)
recyclerView.setAdapter(adapter)
resultList = getActivity.loadObjects() - I suppose this is your suspend (network) call. This shouldn't be called from Main thread, move this call in coroutine launcher, and when is done you can init your adapter from Main thread.
Try this:
open class MyFragment : DialogFragment() {
private var fragmentBinding: FragmentBinding? = null
private var list = ArrayList<MyObjectItem>()
private lateinit var adapter: MynAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentBinding = FragmentBinding.inflate(inflater, container, false)
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO){
val res = getActivity.loadObjects()
withContext(Dispatchers.Main){
getListData(res)
initAdapter()
}
}
return fragmentBinding?.root
}
private fun initAdapter() {
val layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(requireContext())
fragmentBinding?.recyclerView?.layoutManager = layoutManager
adapter = MyAdapter(list, requireContext(), this, this)
fragmentBinding?.recyclerView?.adapter = adapter
}
private fun getListData(res:List<MyObject>) {
for (value in res) {
list.add(
MyObjectItem(
value.title!!,
value.numbert!!,
)
)
}
}
Maybe your issue is different, but if you are going to load data asynchronous you need to use at least RecyclerView.Adapter#notifyDataSetChanged() (You can get better results using notifyItemChanged methods but those are more advanced).
Your code should look similar to:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentBinding = FragmentBinding.inflate(inflater, container, false)
initAdapter()
lifecycleScope.launch {
adapter.list = getListData()
adapter.notifyDataSetChanged() // Notify changes in the adapter
}
return fragmentBinding?.root
}
I made an app from which I get data from TMDB API, everything seems to work but when I start the app, it displays only hint text, after scrolling the View get's updated with the data from the API
This is how it looks before scrolling
And this is how it looks after scrolling down a a bit
This is how I implemented it:
HomeFragment.kt
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private lateinit var popularMovies: RecyclerView
private lateinit var popularMoviesAdapter: MoviesAdapter
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val homeViewModel = ViewModelProvider(this)[HomeViewModel::class.java]
_binding = FragmentHomeBinding.inflate(inflater, container, false)
popularMovies = binding.popularMovies
popularMovies.layoutManager = LinearLayoutManager(context,LinearLayoutManager.VERTICAL,false)
popularMoviesAdapter = MoviesAdapter(listOf())
popularMovies.addItemDecoration(DividerItemDecoration(context,DividerItemDecoration.VERTICAL))
popularMovies.adapter = popularMoviesAdapter
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
MoviesRepository.getPopularMovies(page = 1,onSuccess = ::onPopularMoviesFetched,onError = ::onError)
}
private fun onPopularMoviesFetched(movies: List<Movie>) {
popularMoviesAdapter.updateMovies(movies)
}
private fun onError() {
Toast.makeText(context, getString(R.string.error_fetch_movies), Toast.LENGTH_SHORT).show()
}
MovieAdapter.kt
class MoviesAdapter(
private var movies: List<Movie>
) : RecyclerView.Adapter<MoviesAdapter.MovieViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
val view = LayoutInflater
.from(parent.context)
val binding = MovieItemBinding.inflate(view)
return MovieViewHolder(binding)
}
override fun getItemCount(): Int = movies.size
override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
holder.bind(movies[position])
}
fun updateMovies(movies: List<Movie>) {
this.movies = movies
notifyDataSetChanged()
}
inner class MovieViewHolder(private val binding: MovieItemBinding) : RecyclerView.ViewHolder(binding.root) {
private val poster: ImageView = itemView.findViewById(R.id.item_movie_poster)
fun bind(movie: Movie) {
binding.movieTitle.text =movie.title
binding.movieReleaseDate.text = movie.releaseDate
binding.movieOverview.text = movie.overview
binding.movieReleaseDate.text = movie.releaseDate
Glide.with(itemView)
.load("https://image.tmdb.org/t/p/w342${movie.posterPath}")
.transform(CenterCrop())
.into(poster)
}
}
make adapter initialization on onViewCreated instead of onCreateView
Currently I have a recyclerview adapter that gets data from the database and displays it in the dashboard fragment. Once the item is clicked, I want to pass the item id to the details fragment to get the correct items information on the detailed view. How can I pass this ID to the detailed fragment?
Dashboard Fragment
class DashboardFragment : Fragment() {
private lateinit var viewModel: DashboardViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Get a reference to the binding object and inflate the fragment views.
val binding: DashboardFragmentBinding = DataBindingUtil.inflate(
inflater, R.layout.dashboard_fragment, container, false)
val application = requireNotNull(this.activity).application
val dataSource = NumberDatabase.getInstance(application).numberDatabaseDao
val viewModelFactory = DashboardViewModelFactory(dataSource, application)
// Get a reference to the ViewModel associated with this fragment.
val dashboardViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(DashboardViewModel::class.java)
// To use the View Model with data binding, you have to explicitly
// give the binding object a reference to it.
binding.dashboardViewModel = dashboardViewModel
val adapter = CounterAdapter(CounterListener { nightId ->
dashboardViewModel.onCountClicked(nightId)
})
binding.counterList.adapter = adapter
dashboardViewModel.counts.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
}
Adapter
class CounterAdapter (val clickListener: CounterListener): ListAdapter<Number, CounterAdapter.ViewHolder>(NightDiffCallback()) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position)!!, clickListener)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
class ViewHolder private constructor(val binding: ListItemCounterBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(item: Number, clickListener: CounterListener) {
binding.night = item
binding.clickListener = clickListener
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemCounterBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class CounterListener(val clickListener: (nightId: Long) -> Unit) {
fun onClick(night: Number) = clickListener(night.nightId)
}
In your recyclerview
class RecipeRecyclerAdapter(
mFragmentCommunication: FragmentCommunication
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mFragmentCommunication: FragmentCommunication? = null
init {
this.mFragmentCommunication =mFragmentCommunication
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
RecyclerView.ViewHolder {
view = LayoutInflater.from(parent.context)
.inflate(
com.broadcast.recipeslistapp.R.layout.layout_recipe_list_item,
parent,
false
)
return RecipeViewHolder(view)
}
}
override fun getItemCount(): Int {
if (mRecipes.isNullOrEmpty()) {
return 0
} else {
return mRecipes!!.size
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.setOnClickListener {
mFragmentCommunication!!.PassNightId(nightId = 1) //value of nightId
}
}
interface FragmentCommunication {
fun PassNightId(nightId: Int)
}
Fragment A from where you want to send data :
class RecipeListActivity : BaseActivity(),
RecipeRecyclerAdapter.FragmentCommunication {
private lateinit var layoutManager: LinearLayoutManager
}
private fun initRecyclerView() {
mAdapter = RecipeRecyclerAdapter( this)
val itemDecorator = VerticalSpacingItemDecorator(30);
recipe_list.addItemDecoration(itemDecorator);
recipe_list.adapter = mAdapter
layoutManager = LinearLayoutManager(this);
recipe_list.layoutManager = layoutManager
}
override fun PassNightId(nightId: Int) {
val testFragment = TestFragment()
val bundle = Bundle()
bundle.putString("nightId", "$nightId")
testFragment.arguments = bundle
supportFragmentManager.beginTransaction().replace(R.id.containerId,testFragment).commit()
}
}
Fragment B where you want to getData :
class TestFragment : Fragment()
{
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val nightId =arguments?.getString("nightId")
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.test_fragment,container,false)
}
}
This is how you can send data from on fragment to another fragment.
I am using MVVM with Room persistence and livedata. I am fetching the data from local database and want to show in the form of list in the recyclerView and my recyclerView is not showing anything.
My adapter is like any other regular adapter
RecyclerView Code
class MyInformationAdapter : RecyclerView.Adapter<MyInformationAdapter.ViewHolder>() {
private var myList: List<PersonInformation> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.my_adapter_data, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int = myList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
var myDataClass: PersonInformation = myList[position]
holder.name.text = myDataClass.name
holder.fName.text = myDataClass.fatherName
holder.email.text = myDataClass.email
holder.contact.text = myDataClass.contactNo.toString()
}
fun updateTheState(myList: List<PersonInformation>){
this.myList = myList
notifyDataSetChanged()
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var name: TextView = itemView.findViewById(R.id.yourName)
var fName: TextView = itemView.findViewById(R.id.yourFatherName)
var email: TextView = itemView.findViewById(R.id.yourEmail)
var contact: TextView = itemView.findViewById(R.id.yourContact)
}
}
RecyclerView Activity Code
class FinalActivity : AppCompatActivity() {
private var myDataList : List<PersonInformation> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_final)
setAdapter()
}
private fun setAdapter() {
val adapter = MyInformationAdapter()
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
adapter.updateTheState(myDataList)
}
}
*Fragment as a View of MVVM *
class PersonalInformationFragment : Fragment() {
private var viewModel: PersonInformationViewModel? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_personal_information, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
btn.setOnClickListener {
viewModel = ViewModelProviders.of(this)[PersonInformationViewModel::class.java]
viewModel?.getAllData()?.observe(this, Observer {
this.setAllData(it)
})
val intent = Intent(activity, FinalActivity::class.java)
startActivity(intent)
}
}
private fun setAllData(personInformation: List<PersonInformation>) {
val setData = PersonInformation(
name.text.toString(),
fName.text.toString(),
email.text.toString(),
contact.text.toString().toInt()
)
viewModel?.setTheData(setData)
}
}