Setting up the Adapter in my Fragment so that it works - android

When I tried running the app, I got this error
I've tried different ways to implement the adapter into the fragment, but it doesn't seem to work
The Adapter
class AsteroidViewAdapter(private val onClickListener: OnClickListener) :
ListAdapter<Asteroid, AsteroidViewAdapter.AsteroidViewHolder>(DiffCallback) {
companion object DiffCallback : DiffUtil.ItemCallback<Asteroid>() {
override fun areItemsTheSame(oldItem: Asteroid, newItem: Asteroid): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Asteroid, newItem: Asteroid): Boolean {
return oldItem == newItem
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): AsteroidViewHolder {
return AsteroidViewHolder(
AsteroidListContainerBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: AsteroidViewHolder, position: Int) {
holder.bind(getItem(position), onClickListener)
}
class AsteroidViewHolder(private val binding: AsteroidListContainerBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Asteroid, listener: OnClickListener) {
binding.value = item
binding.listener = listener
binding.executePendingBindings()
}
}
class OnClickListener(val clickListener: (asteroid: Asteroid) -> Unit) {
fun onClick(asteroid: Asteroid) = clickListener(asteroid)
}
}
The ViewModel
private const val TAG = "MainViewModel"
class MainViewModel(application: Application) : ViewModel() {
private val database = AsteroidDatabase.getDatabase(application)
private val repository = AsteroidRepository(database.asteroidDao)
private val _pictureOfDay = MutableLiveData<PictureOfDay>()
val pictureOfDay: LiveData<PictureOfDay>
get() = _pictureOfDay
val info: LiveData<List<Asteroid>> = Transformations.map(repository.feeds) {
it
}
private val _showProgress = MutableLiveData(true)
val showProgress: LiveData<Boolean>
get() = _showProgress
private val _navigation: MutableLiveData<Asteroid> = MutableLiveData()
val navigation: LiveData<Asteroid>
get() = _navigation
init {
fetchThePictureOfTheDay()
loadFeeds()
}
private fun loadFeeds() {
_showProgress.value = true
viewModelScope.launch {
try {
repository.fetchFeeds()
}catch (e: Exception) {
Log.e(TAG, e.message, e.cause)
}
}
}
private fun fetchThePictureOfTheDay() {
viewModelScope.launch {
try {
val picture = repository.getPictureOfTheDay()
_pictureOfDay.postValue(picture)
}catch (e: Exception) {
Log.e(TAG, e.message, e.cause)
}
}
}
fun navigateToDetails(asteroid: Asteroid) {
_navigation.value = asteroid
}
fun navigationDone() {
_navigation.value = null
}
fun progress(empty: Boolean) {
_showProgress.value = empty
}
fun filter(filter: Filter) {
viewModelScope.launch(Dispatchers.IO) {
repository.filterFeeds(filter)
}
}
}
The Fragment
class MainFragment : Fragment() {
// private lateinit var manager: RecyclerView.LayoutManager
private lateinit var viewModel: MainViewModel
private lateinit var adapter: AsteroidViewAdapter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val mainViewModelFactory = MainViewModelFactory(requireActivity().application)
viewModel = ViewModelProvider(this, mainViewModelFactory).get(MainViewModel::class.java)
val binding = FragmentMainBinding.inflate(inflater)
binding.lifecycleOwner = this
binding.viewModel = viewModel
// val mutableList: MutableList<Asteroid> = ArrayList()
// mutableList.add(Asteroid(1, "fgnuugrhrg", "bihagtyjerwailgubivb", 4.0, 8.0,3.0, 9.0, false))
// mutableList.add(Asteroid(2, "fguk.nuugrhrg", "bidjswjyhagrwailgubivb", 3.0, 90.0,355.0, 9.0, true))
// mutableList.add(Asteroid(3, "fgnssuugrhrg", "bshjtihagrwailgubivb", 4.0, 33.0,33.0, 9.0, false))
// mutableList.add(Asteroid(4, "fgnuw4suugrhrg", "bjsryjihagrwailgubivb", 6.0, 8.0,11.0, 9.0, true))
// mutableList.add(Asteroid(5, "fgnuugrudkdkhrg", "bihjjkkuagrwailgubivb", 4.0, 5.0,77.0, 9.0, false))
// manager = LinearLayoutManager(this.context)
binding.asteroidRecycler.adapter = AsteroidViewAdapter(AsteroidViewAdapter.OnClickListener {
viewModel.navigateToDetails(it)
})
viewModel.info.observe(viewLifecycleOwner, Observer {
viewModel.progress(it.isNullOrEmpty())
binding.asteroidRecycler.smoothScrollToPosition(0)
adapter.submitList(it)
})
viewModel.navigation.observe(viewLifecycleOwner, Observer { asteroid ->
asteroid?.let {
findNavController().navigate(MainFragmentDirections.actionShowDetail(it))
viewModel.navigationDone()
}
})
// binding.asteroidRecycler.adapter = adapter
setHasOptionsMenu(true)
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.main_overflow_menu, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_show_week -> {
viewModel.filter(Filter.WEEK)
true
}
R.id.menu_show_today -> {
viewModel.filter(Filter.TODAY)
true
}
R.id.menu_show_saved -> {
viewModel.filter(Filter.SAVED)
true
}
else -> false
}
}
}

As per the error message, you've never actually set your adapter to any value - you just directly assign it to binding.asteroidRecycler.adapter.
Since an adapter keeps a hard reference to the RecyclerView itself, you shouldn't hold a reference to it at the fragment level anyways, you should instead create a local variable for it before assigning it to your RecyclerView:
val adapter = binding.asteroidRecycler.adapter = AsteroidViewAdapter(AsteroidViewAdapter.OnClickListener {
viewModel.navigateToDetails(it)
})
binding.asteroidRecycler.adapter = adapter

Related

How to navigate to 2 different fragments using same adapter according to the type of model?

I want to navigate 2 different fragments using same adapter according to the type of my model. But my problem is I can't handle onItemClicked with two different models. As you can see:
private val onItemClicked: (WordsWithMeanings) -> Unit
FavoriteFragment
class FavoriteFragment : Fragment(R.layout.fragment_favorite) {
private var _binding: FragmentFavoriteBinding? = null
private val binding get() = _binding!!
private val viewModel: WordViewModel by activityViewModels {
WordViewModelFactory(
(activity?.application as WordApplication).database.wordDao()
)
}
private lateinit var adapter: FavoriteListAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentFavoriteBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = FavoriteListAdapter(viewModel) {
val action = FavoriteFragmentDirections.actionFavouriteFragmentToWordDetailFragment(it).
this.findNavController().navigate(action)
}
binding.recyclerView.adapter = adapter
viewModel.allWordsWithMeanings.observe(this.viewLifecycleOwner) { items ->
items.let {
for (i in items) {
if (i.word.isFavorite == true) {
adapter.mData.add(i)
}
}
}
favoriteFragmentFavoriteStatus()
}
viewModel.allProverbs.observe(this.viewLifecycleOwner) { items ->
items.let {
for (i in items) {
if (i.isFavorite == true) {
adapter.mData.add(i)
}
}
}
}
binding.recyclerView.layoutManager = LinearLayoutManager(this.context)
}
}`
```
FavoriteListAdapter
private const val WORD_VIEW_TYPE = 0
private const val PROVERB_VIEW_TYPE = 1
class FavoriteListAdapter(
private val viewModel: WordViewModel,
private val onItemClicked: (WordsWithMeanings) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val mData = mutableListOf<Any>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
WORD_VIEW_TYPE -> WordItemViewHolder(
FavoriteItemsBinding.inflate(
inflater,
parent,
false
)
)
PROVERB_VIEW_TYPE -> ProverbItemViewHolder(
FavoriteItemsBinding.inflate(
inflater,
parent,
false
)
)
else -> throw IllegalArgumentException("Invalid view type")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val element = mData[position]
when (getItemViewType(position)) {
WORD_VIEW_TYPE -> {
holder.itemView.setOnClickListener {
onItemClicked(element as WordsWithMeanings)
}
(holder as WordItemViewHolder).bind(
element as WordsWithMeanings,
viewModel
)
}
PROVERB_VIEW_TYPE -> {
(holder as ProverbItemViewHolder).bind(element as Proverb, viewModel)
}
}
}
class WordItemViewHolder(private var binding: FavoriteItemsBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(favoriteWordsWithMeanings: WordsWithMeanings, viewModel: WordViewModel) {
binding.apply {
title.text = favoriteWordsWithMeanings.word.word
content.text = favoriteWordsWithMeanings.means[0].wordMean
favoriteButton.isChecked =
favoriteWordsWithMeanings.word.isFavorite == true
favoriteButton.setOnClickListener {
if (favoriteButton.isChecked) {
favoriteButton.isChecked = true
viewModel.updateFavoriteWord(favoriteWordsWithMeanings, true)
} else {
favoriteButton.isChecked = false
viewModel.updateFavoriteWord(favoriteWordsWithMeanings, false)
}
}
}
}
}
class ProverbItemViewHolder(private var binding: FavoriteItemsBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(favoriteProverb: Proverb, viewModel: WordViewModel) {
binding.apply {
title.text = favoriteProverb.proverb
content.text = favoriteProverb.proverbMean
favoriteButton.isChecked =
favoriteProverb.isFavorite == true
favoriteButton.setOnClickListener {
if (favoriteButton.isChecked) {
favoriteButton.isChecked = true
viewModel.updateFavoriteProverb(favoriteProverb, true)
} else {
favoriteButton.isChecked = false
viewModel.updateFavoriteProverb(favoriteProverb, false)
}
}
}
}
}
override fun getItemCount(): Int {
return mData.size
}
override fun getItemViewType(position: Int): Int {
return when (mData[position]) {
is WordsWithMeanings -> WORD_VIEW_TYPE
is Proverb -> PROVERB_VIEW_TYPE
else -> throw IllegalArgumentException("Unsupported type")
}
}
}
So I only navigate to one fragment. What I want to do is if clicked item is 'WordsWithMeanings' navigate to WordDetailFragment, if clicked item is 'Proverb' navigate to ProverbDetailFragment. Is there proper way to do this?
SOLUTION:
Firstly I changed this
private val onItemClicked: (WordsWithMeanings) -> Unit
to this
private val onItemClicked: (Any) -> Unit
After that using action like this solved my problem.
action = if (it is WordsWithMeanings) {
FavoriteFragmentDirections.actionFavouriteFragmentToWordDetailFragment(it)
} else {
FavoriteFragmentDirections.actionFavouriteFragmentToProverbDetailFragment(it as Proverb)
}
this.findNavController().navigate(action as NavDirections)
You are passing a WordsWithMeaning item in your adapter onItemClicked callback.
You can check if the item passed to your callback is of WordsWithMeaning type or Proverb type and then act accordingly:
adapter = FavoriteListAdapter(viewModel) {
val action = if (it is Proverb) {
// navigate to proverb details
} else {
// navigate to word details
}
}

Kotlin RecyclerView MVVM data not showing

I have a RecyclerView in MVVM project. I have to get text from editText (searchWordEt) and then pass it to the function that invokes API method in viewmodel.API works fine and returns data. But when I invoke searchDefAdapter.submitList(response) in SearchFragment nothing happens and RecyclerView data not showing.
class SearchDefAdapter(
private var infoListener: OnItemClickListener,
private var addListener: OnItemClickListener
):
ListAdapter<Def, SearchDefViewHolder>(differCallback) {
interface OnItemClickListener {
fun onItemClick(position: Int)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchDefViewHolder {
return SearchDefViewHolder(
SearchWordCardBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
addListener,
infoListener
)
}
override fun onBindViewHolder(holder: SearchDefViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
class SearchDefViewHolder(
private val binding: SearchWordCardBinding,
addListener: SearchDefAdapter.OnItemClickListener,
infoListener: SearchDefAdapter.OnItemClickListener
): RecyclerView.ViewHolder(binding.root) {
fun bind(data: Def) {
with (binding) {
searchCardTv.text = "${data.text} - ${data.tr[0].text}"
}
}
init {
binding.addSearchCard.setOnClickListener {
addListener.onItemClick(adapterPosition)
}
binding.infoSearchCard.setOnClickListener {
infoListener.onItemClick(adapterPosition)
}
}
}
val differCallback = object : DiffUtil.ItemCallback<Def>() {
override fun areItemsTheSame(oldItem: Def, newItem: Def): Boolean {
return oldItem.text == newItem.text
}
override fun areContentsTheSame(oldItem: Def, newItem: Def): Boolean {
return oldItem == newItem
}
}
#AndroidEntryPoint
class SearchFragment : Fragment() {
private var _binding: FragmentSearchBinding? = null
private val binding get() = _binding!!
lateinit var searchDefAdapter: SearchDefAdapter
private val viewModel: DictionaryViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentSearchBinding.inflate(inflater, container, false)
val view = binding.root
searchDefAdapter = SearchDefAdapter(
object : SearchDefAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
Log.d("tag", "Item Added!")
//viewModel.saveWord(position)
}
},
object : SearchDefAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
val wordFragment = WordFragment()
fragmentManager?.beginTransaction()?.replace(
R.id.nav_host_fragment_container,
wordFragment
)?.commit()
}
}
)
setUpRecyclerView(searchDefAdapter)
var job: Job? = null
binding.searchWordEt.addTextChangedListener { editable ->
job?.cancel()
job = MainScope().launch {
delay(SEARCH_WORD_TIME_DELAY)
editable?.let {
if (editable.toString().isNotEmpty())
viewModel.getTranslation(editable.toString())
}
}
}
viewModel.def.observe(viewLifecycleOwner, Observer { response ->
binding.apply {
searchDefAdapter.submitList(response)
}
})
return view
}
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
private fun setUpRecyclerView(adapter: SearchDefAdapter){
binding.searchRv.apply {
adapter
layoutManager = LinearLayoutManager(activity)
}
}
}
You haven't actually set the adapter in setUpRecyclerView()
private fun setUpRecyclerView(myAdapter: SearchDefAdapter){
binding.searchRv.apply {
layoutManager = LinearLayoutManager(activity)
adapter = myAdapter // here
}
}

Сombine 3 LiveData into one with Paging 3

How can I combine 3 LiveData into one, I have a snippet in my project where I need to display the result of 3 different requests, into one RecyclerView,
I read about it, and found out that it is possible to combine three LiveData into one, but I do not understand how this can be done using Paging 3.
Sample code in my ViewModel:
#HiltViewModel
class SearchViewModel #Inject constructor(
private val getAllCharacters: GetCharactersUseCase,
private val getAllLocations: GetLocationsUseCase,
private val getAllEpisodes: GetEpisodesUseCase
) : BaseViewModel() {
private val _charactersState = MutableLiveData<PagingData<RickAndMorty>>()
val charactersState: LiveData<PagingData<RickAndMorty>> = _charactersState
private val _locationsState = MutableLiveData<PagingData<RickAndMorty>>()
val locationsState: LiveData<PagingData<RickAndMorty>> = _locationsState
private val _episodesState = MutableLiveData<PagingData<RickAndMorty>>()
val episodesState: LiveData<PagingData<RickAndMorty>> = _episodesState
val liveDataMerger: MediatorLiveData<PagingData<RickAndMorty>> =
MediatorLiveData<PagingData<RickAndMorty>>()
fun merge() {
liveDataMerger.addSource(locationsState) {
liveDataMerger.value = it
}
liveDataMerger.addSource(charactersState) {
liveDataMerger.value = it
}
}
fun processAllRequests(
name: String
) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
getAllCharacters(name, "", "")
}.collect {
_charactersState.value = it as PagingData<RickAndMorty>
}
}
viewModelScope.launch {
withContext(Dispatchers.IO) {
getAllLocations(name, "", "")
}.collect {
_locationsState.value = it as PagingData<RickAndMorty>
}
}
viewModelScope.launch {
withContext(Dispatchers.IO) {
getAllEpisodes(name, "")
}.collect {
_episodesState.value = it as PagingData<RickAndMorty>
}
}
}
}
functions getAllCharacters, getAllLocations, getAllEpisodes are suspend, and all my models are combined into one Sealed class.
example code in Fragment:
#AndroidEntryPoint
class SearchFragment : BaseFragment<FragmentSearchBinding, SearchViewModel>(
R.layout.fragment_search
) {
override val binding by viewBinding(FragmentSearchBinding::bind)
override val viewModel by viewModels<SearchViewModel>()
private val adapter: SearchAdapter = SearchAdapter()
private val args: SearchFragmentArgs by navArgs()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun initialize() {
setupRecycler()
}
private fun setupRecycler() = with(binding) {
searchRecycler.layoutManager = LinearLayoutManager(requireContext())
searchRecycler.adapter = adapter.withLoadStateFooter(LoadStateAdapter {
adapter.retry()
})
}
override fun setupRequests() {
viewModel.processAllRequests("")
Log.e("anime", args.filterType)
viewModel.merge()
}
override fun setupObserves() {
viewModel.liveDataMerger.observe(viewLifecycleOwner, {
lifecycleScope.launch {
adapter.submitData(it)
}
})
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.toolbar_menu, menu)
val searchItem = menu.findItem(R.id.search)
val searchView = searchItem.actionView as SearchView
searchView.setTools(context)
searchView.submitSearch { viewModel.processAllRequests(it.toString()) }
searchItem.setOnActionExpandListener(searchView) { hideKeyboard() }
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.filter) {
findNavController().navigate(
SearchFragmentDirections.actionSearchFragmentToFilterDialogFragment(
getString(R.string.search_filter_type)
)
)
}
return super.onOptionsItemSelected(item)
}
}
My Adapter:
class SearchAdapter :
PagingDataAdapter<RickAndMorty, SearchRecyclerViewHolder<ViewBinding>>(
SearchDiffUtil()
) {
class SearchDiffUtil : DiffUtil.ItemCallback<RickAndMorty>() {
override fun areItemsTheSame(oldItem: RickAndMorty, newItem: RickAndMorty): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: RickAndMorty, newItem: RickAndMorty): Boolean {
return oldItem == newItem
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): SearchRecyclerViewHolder<ViewBinding> {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
R.layout.item_characters -> SearchRecyclerViewHolder.CharactersViewHolder(
ItemCharactersBinding.inflate(
inflater,
parent,
false
)
)
R.layout.item_locations -> SearchRecyclerViewHolder.LocationsViewHolder(
ItemLocationsBinding.inflate(
inflater,
parent,
false
)
)
R.layout.item_episodes -> SearchRecyclerViewHolder.EpisodesViewHolder(
ItemEpisodesBinding.inflate(
inflater,
parent,
false
)
)
else -> throw IllegalAccessException("Invalid viewType provided")
}
}
override fun onBindViewHolder(
holder: SearchRecyclerViewHolder<ViewBinding>,
position: Int
) {
when (holder) {
is SearchRecyclerViewHolder.CharactersViewHolder ->
holder.onBind(getItem(position) as RickAndMorty.CharactersItem)
is SearchRecyclerViewHolder.LocationsViewHolder ->
holder.onBind(getItem(position) as RickAndMorty.LocationsItem)
is SearchRecyclerViewHolder.EpisodesViewHolder ->
holder.onBind(getItem(position) as RickAndMorty.EpisodesItem)
}
}
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is RickAndMorty.CharactersItem -> R.layout.item_characters
is RickAndMorty.LocationsItem -> R.layout.item_locations
is RickAndMorty.EpisodesItem -> R.layout.item_episodes
null -> throw IllegalStateException("Unknown view")
}
}
}

Observables updates in the live data dont working in fragment

I have one fragment that instance that instantiates a view model injected by koin, the problem is that, one of the observed attributes are not stimulated in the fragment after the view model's postValue () action, it simply does not enter the method in the fragment even though it has been updated.
Fragement:
class ListFragment : Fragment() {
private lateinit var adapter: PostAdapter
private val viewModel: PostsViewModel by viewModel()
private var _binding: ListFragmentBinding? = null
private var defaultTopic = "news"
private var afterPage: String = ""
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.list_fragment, container, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = ListFragmentBinding.bind(view)
adapter = PostAdapter(mutableListOf(), requireContext()) {
val action = ListFragmentDirections.openDetailsFragment(it)
findNavController().navigate(action)
}
_binding?.let{
val llm = LinearLayoutManager(requireContext())
it.recyclerviewPosts.layoutManager = llm
it.recyclerviewPosts.adapter = adapter
recyclerViewListenerSetup(it, llm)
}
setupObservers()
}
private fun recyclerViewListenerSetup(it: ListFragmentBinding, llm: LinearLayoutManager) {
it.recyclerviewPosts.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val visibleItemCount: Int = llm.childCount
val totalItemCount: Int = llm.itemCount
val firstVisibleItemPosition: Int = llm.findFirstVisibleItemPosition()
if((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
&& firstVisibleItemPosition >= 0) {
viewModel.getNextPage(defaultTopic, afterPage)
}
}
})
}
private fun setupObservers(){
viewModel.getPostList(defaultTopic)
viewModel.posts.observe(this as LifecycleOwner, { posts ->
if (posts.isSuccess && posts.getOrNull() != null) {
adapter.updateList(posts.getOrNull()!! as MutableList<PostsDTO>)
afterPage = posts.getOrNull()!![0].after
showList()
} else {
showError()
}
})
viewModel.loading.observe(this as LifecycleOwner, {
if (it) {
showLoading()
} else {
hideLoading()
}
})
viewModel.next.observe(this as LifecycleOwner, {
if (it.isSuccess && it.getOrNull() != null) {
adapter.addList(it.getOrNull() as MutableList<PostsDTO>)
afterPage = it.getOrNull()!![0].after
}
})
}
private fun showList(){
_binding?.let {
it.recyclerviewPosts.visibility = VISIBLE
}
}
private fun showLoading(){
_binding?.let {
it.loading.visibility = VISIBLE
it.containerError.root.visibility = GONE
it.recyclerviewPosts.visibility = GONE
}
}
private fun hideLoading(){
_binding?.let {
it.loading.visibility = GONE
}
}
private fun showError() {
_binding?.let {
it.containerError.root.visibility = VISIBLE
it.recyclerviewPosts.visibility = GONE
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_main, menu)
val searchItem = menu.findItem(R.id.action_search)
val searchManager =
requireActivity().getSystemService(Context.SEARCH_SERVICE) as SearchManager
val searchView = searchItem.actionView as SearchView
searchView.setSearchableInfo(searchManager.getSearchableInfo(activity?.componentName))
searchView.apply {
queryHint = "Search SubReddit"
setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
defaultTopic = query!!
viewModel.getPostList(defaultTopic)
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
return true
}
})
}
super.onCreateOptionsMenu(menu, inflater)
}
}
I try to call the viewmodel method to update the displayed list when the recyclerView scrolls.
ViewModel:
class PostsViewModel(private val repository: PostsRepository) : ViewModel(){
private val _posts = MutableLiveData<Result<List<PostsDTO>>>()
val posts: LiveData<Result<List<PostsDTO>>>
get() =_posts
private val _loading = MutableLiveData<Boolean>()
val loading: LiveData<Boolean>
get() = _loading
private val _next = MutableLiveData<Result<List<PostsDTO>>>()
val next: LiveData<Result<List<PostsDTO>>>
get() =_posts
fun getPostList(q: String){
viewModelScope.launch {
_loading.postValue(true)
repository.fetchPosts(q)
.collect {
_posts.value = it
}
_loading.postValue(false)
}
}
fun getNextPage(topic: String, afterPage: String) {
viewModelScope.launch {
repository.fetchNextPage(topic, afterPage)
.collect{
_next.postValue(it)
}
}
}
}
In this case after the request result of the next method has updated the viewmodel, the fragment is not stimulated in viewmodel.next.observer()
OPAH! after a unit test I was able to find out what the problem was, the problem was ctrl + v, the next property, it was returning the _post property and not the _next property, so the view was not notified of the update.
The correction the correction was in the viewmoldel, changing the get method of the next variable:
private val _next = MutableLiveData<Result<List<PostsDTO>>>()
val next: LiveData<Result<List<PostsDTO>>>
get() =_next

Data won't display when RecyclerView with ListAdapter is updated

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...

Categories

Resources