Observables updates in the live data dont working in fragment - android

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

Related

Filter recyclerview on firestore realtime data

I am trying to filter my recyclerview using filterable, it works fine on static data, but on dynamic data from firestore
First, I got the data from Firestore in arrayList
Then, I filter those arrayList to new arrayList
But when any change happended in Firestore, the original arrayList will be updated and my recyclerview will display this data instead the data I currently filltered on searchview
What I wanted to do is my recyclerview to aslway display filtered data when I type any word on searchview whether they are any updated data or not
added screenshot
here is my adapter
class SearchAdapter() : RecyclerView.Adapter<SearchAdapter.ListViewHolder>(), Filterable {
private var listSearch = ArrayList<Dosen>()
private var listSearchFull = ArrayList<Dosen>()
fun setData(list: ArrayList<Dosen>){
this.listSearch = list
listSearchFull = ArrayList(listSearch)
}
inner class ListViewHolder(itemView: UserListBinding) : RecyclerView.ViewHolder(itemView.root) {
private val binding = itemView
fun bind(dosen: Dosen) {
with(binding){
val db = Firebase.firestore
val collection = db.collection("alat")
.whereEqualTo("id", dosen.alat_id)
collection.get()
.addOnSuccessListener { document ->
try {
val location = document.toObjects(Alat::class.java)[0].lokasi
tvLocation.text = location
} catch (e: Exception){
Log.d("rv", "system error $e")
}
}
.addOnFailureListener { exception ->
Log.d("rv", "get failed with ", exception)
}
tvUsername.text = dosen.nama
val simpleDateFormat = SimpleDateFormat("EEEE, dd LLLL yyyy")
val date = simpleDateFormat.format(dosen.datetime!!.toDate())
Log.d("rvTime", date)
tvDate.text = date
val simpleTimeFormat = SimpleDateFormat("KK:mm:ss aaa")
val time = simpleTimeFormat.format(dosen.datetime.toDate())
Log.d("rvTime", time)
tvTime.text = time
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
return ListViewHolder(UserListBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
holder.bind(listSearch[position])
}
override fun getItemCount(): Int = listSearch.size
override fun getFilter(): Filter {
return object : Filter(){
override fun performFiltering(query: CharSequence?): FilterResults {
val filteredList: ArrayList<Dosen> = ArrayList()
if (query == null || query.length == 0){
filteredList.addAll(listSearchFull)
} else{
val searchQuery = query.toString().toLowerCase().trim()
for (item in listSearchFull) {
if (item.nama!!.lowercase(Locale.ROOT).contains(searchQuery)){
filteredList.add(item)
}
}
}
val filterResults = FilterResults()
filterResults.values = filteredList
return filterResults
}
override fun publishResults(query: CharSequence?, filteredResult: FilterResults?) {
//error
listSearch.clear()
listSearch.addAll(filteredResult!!.values as ArrayList<Dosen>)
notifyDataSetChanged()
}
}
}
}
and here is my fragment
class SearchFragment : Fragment() {
private var _binding: FragmentSearchBinding? = null
private val binding get() = _binding!!
private lateinit var searchAdapter: SearchAdapter
private lateinit var searchArrayList : ArrayList<Dosen>
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
_binding = FragmentSearchBinding.inflate(inflater, container, false)
val root: View = binding.root
searchArrayList = arrayListOf()
#Suppress("DEPRECATION")
setHasOptionsMenu(true)
eventChangeListener()
return root
}
private fun eventChangeListener() {
val db = Firebase.firestore
db.collection("presensi")
.orderBy("datetime", Query.Direction.DESCENDING)
.addSnapshotListener(object : EventListener<QuerySnapshot>{
override fun onEvent(value: QuerySnapshot?, error: FirebaseFirestoreException?) {
searchArrayList.clear()
for (document in value!!){
searchArrayList.add(document.toObject(Dosen::class.java))
searchAdapter.setData(searchArrayList)
Log.d("arraySearch", document.toObject<Dosen>().toString())
Log.d("arraySearchList", searchArrayList.toString())
}
searchAdapter.notifyDataSetChanged()
}
})
searchAdapter = SearchAdapter()
with(binding){
tvNoData.visibility = View.GONE
rvSearch.layoutManager = LinearLayoutManager(activity)
rvSearch.setHasFixedSize(true)
rvSearch.adapter = searchAdapter
}
}
/// prob need fix
#Deprecated("Deprecated in Java")
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
#Suppress("DEPRECATION")
super.onCreateOptionsMenu(menu, inflater)
menu.clear()
inflater.inflate(R.menu.main_menu, menu)
val searchView = context?.let { SearchView(it) }
menu.findItem(R.id.menu_search).apply {
setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW or MenuItem.SHOW_AS_ACTION_IF_ROOM)
actionView = searchView
}
searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
return false
}
override fun onQueryTextChange(newText: String): Boolean {
searchAdapter.filter.filter(newText)
return false
}
})
}
override fun onStart() {
super.onStart()
Log.d("firebaseFirestoreListener", "onStart")
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
Log.d("firebaseFirestoreListener", "onDestroyView")
}
}
Is it actually possible to do what I want?
or should I use 3rd party library like algolia, but it's not free.
I hope you guys understand my question

Recycler view item on click not working properly after searchview kotlin

I have implemented recyclerview with searchview in my app. But on clicking the item after search, my app crashes.
My app works fine when item on list is clicked before search.
It only stops working when clicking search result.
App Screen Record
My code for listview
class BookFragment : Fragment() , SearchView.OnQueryTextListener {
private var columnCount = 1
private lateinit var bookViewModel: BookViewModel
private val myAdapter: BookAdapter by lazy { BookAdapter(requireContext()) }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.main_book_list, container, false)
val recyclerView: RecyclerView = view.findViewById(R.id.mainList)
val fab_add_button: FloatingActionButton = view.findViewById(R.id.FAB_add_new)
setHasOptionsMenu(true)
bookViewModel = ViewModelProvider(this)[BookViewModel::class.java]
bookViewModel.allBooks.observe(viewLifecycleOwner) { books ->
myAdapter.setBooks(books)
}
with(recyclerView) {
adapter = myAdapter
layoutManager = when {
columnCount <= 1 -> LinearLayoutManager(context)
else -> GridLayoutManager(context, columnCount)
}
}
fab_add_button.setOnClickListener {
findNavController().navigate(R.id.action_bookFragment_to_addFragment)
}
postponeEnterTransition()
recyclerView.doOnPreDraw {
startPostponedEnterTransition()
}
return view
}
#Deprecated("Deprecated in Java")
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.list_menu, menu)
val search = menu.findItem(R.id.list_menu_search)
val searchView = search.actionView as SearchView
searchView.isSubmitButtonEnabled = true
searchView.setOnQueryTextListener(this)
}
#Deprecated("Deprecated in Java")
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.list_menu_setting -> findNavController().navigate(R.id.action_bookFragment_to_settingsFragment)
}
return super.onOptionsItemSelected(item)
}
override fun onQueryTextSubmit(query: String?): Boolean {
if (query != null ) searchDB(query)
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
if (newText != null ) searchDB(newText)
return true
}
private fun searchDB(string: String) {
val query = "%$string%"
bookViewModel.fetchSearchedBook(query).observe(viewLifecycleOwner) { resp ->
myAdapter.setBooks(resp)
}
}
}
My Adapter Code
class BookAdapter(context: Context) : RecyclerView.Adapter<BookAdapter.ViewHolder>() {
private var books = emptyList<LightNovel>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
SingleBookBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = books[position]
holder.idView.text = item.id.toString()
holder.contentView.text = item.title
Glide.with(holder.itemView.context)
.load(item.coverRemote)
.error(R.drawable.ic_image_error)
.into(holder.imageView)
holder.itemView.setOnClickListener {
holder.itemView.findNavController().navigate(R.id.action_bookFragment_to_detailFragment)
val action = BookFragmentDirections.actionBookFragmentToDetailFragment(item.id)
holder.itemView.findNavController().navigate(action)
}
}
override fun getItemCount(): Int = books.size
inner class ViewHolder(binding: SingleBookBinding) : RecyclerView.ViewHolder(binding.root) {
val idView: TextView = binding.itemNumber
val contentView: TextView = binding.content
val imageView: ImageView = binding.listCover
override fun toString(): String {
return super.toString() + " '" + contentView.text + "'"
}
}
#SuppressLint("NotifyDataSetChanged")
fun setBooks(lightNovels: List<LightNovel>) {
this.books = lightNovels
notifyDataSetChanged()
}
}
And Detail Fragment
class DetailFragment : Fragment() {
private val args: DetailFragmentArgs by navArgs()
private var _binding: FragmentDetailBinding? = null
private val binding get() = _binding!!
private lateinit var bookViewModel: BookViewModel
private lateinit var bookLN: LightNovel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentDetailBinding.inflate(inflater, container, false)
val view = binding.root
val bookid = args.bookID
setHasOptionsMenu(true)
bookViewModel = ViewModelProvider(this)[BookViewModel::class.java]
bookViewModel
.fetchLiveBook(bookid)
.observe(viewLifecycleOwner) {
book ->
bookLN = book
binding.detailTitle.text = book.title
val image = binding.detailCover
val imageUrl = bookLN.coverRemote
val valid = URLUtil.isValidUrl(imageUrl)
if (valid) {
Glide.with(requireContext())
.load(bookLN.coverRemote)
.error(R.drawable.ic_image_error)
.into(image)
}
if (book.synopsis != null && book.synopsis.length >= 2) {
binding.detailSynopsis.text = book.synopsis
}
if (URLUtil.isValidUrl(book.download)) {
binding.detailButton.setBackgroundColor(Color.RED) }
binding.detailButton.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(book.download))
try {
startActivity(intent)
} catch (_: ActivityNotFoundException) {
}
}
}
return view
}
#Deprecated("Deprecated in Java")
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.detail_menu, menu)
}
#Deprecated("Deprecated in Java")
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.detail_menu_update_book -> {
val action = DetailFragmentDirections.actionDetailFragmentToUpdateFragment(args.bookID)
findNavController().navigate(action)
}
R.id.detail_menu_delete_book -> {
deleteBook(bookLN)
}
}
return super.onOptionsItemSelected(item)
}
private fun deleteBook(Ln: LightNovel) {
val builder = AlertDialog.Builder(requireContext())
builder.setPositiveButton("Delete") {_,_ ->
bookViewModel.deleteBook(Ln)
findNavController().navigate(R.id.action_detailFragment_to_bookFragment)
}
builder.setNegativeButton("Cancel") {_,_ ->}
builder.setTitle("Delete Entry?")
builder.setMessage("Are you sure you want to delete ${Ln.title}")
builder.create().show()
}
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
}
I think it might be due to bookViewModel.fetchLiveBook(bookid) not working. But I do not know where it is not working.
Logcat shows following
FATAL EXCEPTION: main
Process: com.knight.moonreaderdatabase, PID: 21562
java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()
at androidx.fragment.app.Fragment.getViewLifecycleOwner(Fragment.java:377)
at com.knight.moonreaderdatabase.fragments.list.BookFragment.searchDB(BookFragment.kt:98)
at com.knight.moonreaderdatabase.fragments.list.BookFragment.onQueryTextChange(BookFragment.kt:92)
at androidx.appcompat.widget.SearchView.onTextChanged(SearchView.java:1198)
at androidx.appcompat.widget.SearchView$10.onTextChanged(SearchView.java:1736)
at android.widget.TextView.sendOnTextChanged(TextView.java:10586)
at android.widget.TextView.setText(TextView.java:6322)
at android.widget.TextView.setText(TextView.java:6147)
at android.widget.EditText.setText(EditText.java:121)
at android.widget.TextView.setText(TextView.java:6099)
at androidx.appcompat.widget.SearchView.setQuery(SearchView.java:579)
at androidx.appcompat.widget.SearchView.onActionViewCollapsed(SearchView.java:1295)
at androidx.appcompat.widget.Toolbar$ExpandedActionViewMenuPresenter.collapseItemActionView(Toolbar.java:2656)
at androidx.appcompat.view.menu.MenuBuilder.collapseItemActionView(MenuBuilder.java:1384)
at androidx.appcompat.view.menu.MenuBuilder.clear(MenuBuilder.java:607)
at androidx.appcompat.app.AppCompatDelegateImpl.doInvalidatePanelMenu(AppCompatDelegateImpl.java:2183)
at androidx.appcompat.app.AppCompatDelegateImpl$2.run(AppCompatDelegateImpl.java:273)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:975)
at android.view.Choreographer.doCallbacks(Choreographer.java:799)
at android.view.Choreographer.doFrame(Choreographer.java:730)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:960)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:236)
at android.app.ActivityThread.main(ActivityThread.java:7864)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:620)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1011)

How to display items loading while data is being fetched from Firebase android kotlin

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

I put the search function in the recyclerView. Items are not only seen as new data, but new data seems to be filled up a little on old data. in kotlin

Pressing the search button in Main Activity changes the values of the items in RecyclerView.
The problem is that the existing data remains except for the newly retrieved data.
How can I show you only the new values on the list?
--- MainActivity.kt ----
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var toolbar: Toolbar
private var logTag : String? = "로그 MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
Log.d(logTag,"onCreate is called")
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
toolbar = binding.searchToolbar
setContentView(view)
setSupportActionBar(toolbar)
val navView: BottomNavigationView = binding.navView
val navController : NavController = findNavController(R.id.nav_host_fragment)
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.A, R.id.B, R.id.C
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
binding.btnSearch.setOnClickListener {
val recordFragment = RecordFragment()
val bundle = Bundle()
if ( binding.etSearch.text.toString() ==""|| binding.etSearch.text.isEmpty()) {
bundle.putString("parameter", "A")
}else {
bundle.putString("parameter", binding.etSearch.text.toString())
}
recordFragment.arguments = bundle
this.supportFragmentManager.beginTransaction().replace(R.id.nav_host_fragment, recordFragment).commit()
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.record_app_bar, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.item_search -> {
when (binding.loSearch.visibility) {
GONE -> binding.loSearch.visibility = VISIBLE
VISIBLE -> binding.loSearch.visibility = GONE
else -> {
Log.d(logTag, "onOptionsItemSelected is called // error")
}
}
}
}
return super.onOptionsItemSelected(item)
}
}
class RecordFragment : Fragment() {
private var rRecyclerView: RecyclerView? = null
private val rApi: ApiInterface? = HttpClient.getRetrofit()?.create(ApiInterface::class.java)
private var EventName : String? = null
var logTag : String? = "로그 RecordFragment"
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.fragment_record, container, false)
EventName = arguments?.getString("parameter")
if (EventName == null||EventName =="") {
EventName = ""
}
rRecyclerView = root.findViewById(R.id.recordRecyclerView)
rRecyclerView?.layoutManager = LinearLayoutManager(context)
val rAdapter = Radapter()
rRecyclerView?.adapter = rAdapter
//쿼리 값을 Map 으로 생성하여 api 호출
val queries = mapOf("parameter" to EventName)
val call: Call<Record?>? = rApi!!.getRecordData(queries)
call?.enqueue(object : Callback<Record?> {
#SuppressLint("NotifyDataSetChanged")
override fun onResponse(
call: Call<Record?>,
response: retrofit2.Response<Record?>
) {
//응답 성공시 어댑터에 결과 전달
val result: Record = response.body() as Record
Log.d(logTag, "onResponse: refresh 직전")
rAdapter.setList(result.data)
rAdapter.notifyDataSetChanged()
//rAdapter.notifyItemRangeChanged(0, result.data?.size!!);
}
override fun onFailure(call: Call<Record?>, t: Throwable) {
Log.e("D/OkHttp", "onFailure - message=" + t.message)
}
})
return root
}
override fun onDestroyView() {
super.onDestroyView()
rRecyclerView?.adapter = null
rRecyclerView = null
}
inner class Radapter : RecyclerView.Adapter<RecordFragment.Radapter.RvhItem>() {
private var showMatchCount: Int? = 0
private var matchCount: Int? = 0
private var mRecord: ArrayList<Event>? = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RvhItem {
val vhView = LayoutInflater.from(parent.context).inflate(R.layout.vh_record, parent, false)
return RvhItem(vhView)
}
#SuppressLint("NotifyDataSetChanged")
fun setList(Data: List<Event>?) {
mRecord!!.clear()
mRecord!!.addAll(Data!!)
notifyDataSetChanged()
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: RvhItem, position: Int) {
val record: Event = mRecord!![position]
holder.EventName.text = record._event_name
holder.EventDate.text = record._event_date
showMatchCount = record._event_count?.toInt()
matchCount = record._event_result!!.size
}
override fun getItemCount(): Int {
return mRecord!!.size
}
inner class RvhItem(itemView: View) : RecyclerView.ViewHolder(itemView) {
var EventName: TextView = itemView.findViewById(R.id.tv_event_name)
var EventDate: TextView = itemView.findViewById(R.id.tv_fighter_ranking)
}
}
}

Setting up the Adapter in my Fragment so that it works

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

Categories

Resources