I'm using databinding and fragments with navigation component for the first time in my app. Everything works fine with regard to navigation and loading my list. The problem I have is this:
When I'm in my list and I click on one of the items, I navigate to the item's detail fragment. But when I return to the list (onBack), the fragment and the list are loaded again, they are re-created.
What I want to achieve is that the fragment and therefore the list are not recreated. That they remain in the same state, so that when I return from the detail I can continue to navigate my list from the point where I left off.
My code:
Fragment
class ListFragment : Fragment() {
private lateinit var viewDataBinding: FragmentListBinding
private lateinit var adapter: ListAdapter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
viewDataBinding = FragmentListBinding.inflate(inflater, container, false).apply {
viewmodel = ViewModelProviders.of(this@ListFragment).get(ListViewModel::class.java)
lifecycleOwner = viewLifecycleOwner
}
return viewDataBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupAdapter()
setupObservers()
}
private fun setupObservers() {
viewDataBinding.viewmodel?.listLive?.observe(viewLifecycleOwner, Observer {
adapter.updateList(it)
})
viewDataBinding.viewmodel?.toastMessage?.observe(viewLifecycleOwner, Observer {
activity?.longToast(it)
})
}
private fun setupAdapter() {
val viewModel = viewDataBinding.viewmodel
if (viewModel != null) {
adapter = ListAdapter(viewDataBinding.viewmodel!!)
val layoutManager = LinearLayoutManager(activity)
list_rv.layoutManager = layoutManager
list_rv.adapter = adapter
}
}
override fun onResume() {
super.onResume()
viewDataBinding.viewmodel?.fetchList()
}
}
Fragment layout
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="viewmodel"
type="app.example.list.ListViewModel" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="@{safeUnbox(viewmodel.dataLoading) ? View.GONE : View.VISIBLE}" />
</RelativeLayout>
View model
class ListViewModel : BaseViewModel() {
val listLive = MutableLiveData<List<Item>>()
fun fetchList() {
dataLoading.value = true
ItemRepository.getInstance().getList() { isSuccess, response ->
dataLoading.value = false
if (isSuccess) {
listLive.value = response?.items
empty.value = false
} else {
empty.value = true
}
}
}
}
I'm navigating between the list and the item with navigation
itemView.findNavController().navigate(R.id.action_listFragment_to_detailFragment)
You should never be reloading your data in a Lifecycle method such as onResume()
- that's why your data goes back to loading every time you go back to the fragment.
Instead, you should call fetchList()
just once in your ViewModel's init
block - this ensures that it runs only once:
class ListViewModel : BaseViewModel() {
val listLive = MutableLiveData<List<Item>>()
init {
fetchList()
}
fun fetchList() {
dataLoading.value = true
ItemRepository.getInstance().getList() { isSuccess, response ->
dataLoading.value = false
if (isSuccess) {
listLive.value = response?.items
empty.value = false
} else {
empty.value = true
}
}
}
}
It isn't clear how your getList()
method works. How it is written now is generally correct for a single callback. If your getList()
is actually returning a stream of values that continue to deliver callbacks as the underlying data changes, then you must make sure you unregister that callback in onCleared()
(the callback when the ViewModel is destroyed).