I have a ViewPager with one tab that is retrieving data from Room trough a ViewModel with LiveData objects.
When I switch to any tab and coming back to the one is showing the list of items everything works perfect but when coming from background I don't know why but I am receiving one list less.
eg First time I enter into the "List fragment".
I retrieve data from 5 different entities and the 5 list items are shown
Go to Background and Back to "List fragment"
4 list have data but 1 is empty
Go to Background and Back to "List fragment"
3 list have data but 2 are empty
... until is fully empty but then I switch tabs and the list well filled
Thanks for any help.
ViewModel
class TagsViewModel(application: Application) : AndroidViewModel(application) {
private var repository: TagsRepository = TagsRepository(application)
private var listText: LiveData<List<Text>> = repository.getAllText()
private var listUrl: LiveData<List<Url>> = repository.getAllUrl()
private var listEmail: LiveData<List<Email>> = repository.getAllEmail()
private var listPhone: LiveData<List<Phone>> = repository.getAllPhone()
private var listLauncher: LiveData<List<Launcher>> = repository.getAllLauncher()
fun getData(): List<BaseTag>{
var allData= mutableListOf<BaseTag>()
if (listText.value != null){
listText.value!!.forEach {
allData.add(it)
}
}
if (listUrl.value != null){
listUrl.value!!.forEach {
allData.add(it)
}
}
if (listEmail.value != null){
listEmail.value!!.forEach {
allData.add(it)
}
}
if (listPhone.value != null){
listPhone.value!!.forEach {
allData.add(it)
}
}
if (listLauncher.value != null){
listLauncher.value!!.forEach {
allData.add(it)
}
}
return allData
}
//-------TEXT--------
fun insertText(text: Text) {
repository.insertText(text)
}
fun deleteTextByID(id: Int) {
repository.deleteTextByID(id)
}
fun getAllText(): LiveData<List<Text>> {
return listText
}
fun deleteAllText() {
repository.deleteAllText()
}
repository getAllText() returns tagsDao.getTextList()
and Dao is:
@Query("SELECT * FROM Text")
fun getTextList(): LiveData<List<Text>>
EDIT
I changed the viewPager to not recreate the fragments again, Observing LiveData in onViewCreated, clearing the list in onPause and setting data again in onResume and adding viewLyfeCycleOwner
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
v = inflater.inflate(R.layout.fragment_my_tags, container, false)
return v
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupUI()
}
private fun setupUI(){
val recyclerView = v.findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.setHasFixedSize(true)
recyclerView.adapter = adapter
tagsViewModel = ViewModelProviders.of(this).get(TagsViewModel::class.java)
getTags()
(activity as MainActivity).mToolbar.onMenuItemClick {
when (it!!.itemId){
R.id.btnDeleteAll -> toast("sort")
R.id.btnOrderBy -> tagsViewModel.deleteAll()
}
}
}
private fun getTags(){
tagsViewModel.getAllText().observe(viewLifecycleOwner,
Observer<List<Text>> { t -> adapter.setTags(t!!) })
tagsViewModel.getAllEmail().observe(viewLifecycleOwner,
Observer<List<Email>> { t -> adapter.setTags(t!!) })
tagsViewModel.getAllUrl().observe(viewLifecycleOwner,
Observer<List<Url>> { t -> adapter.setTags(t!!) })
tagsViewModel.getAllPhone().observe(viewLifecycleOwner,
Observer<List<Phone>> { t -> adapter.setTags(t!!) })
tagsViewModel.getAllLauncher().observe(viewLifecycleOwner,
Observer<List<Launcher>> { t -> adapter.setTags(t!!) })
}
override fun onPause() {
adapter.clear()
super.onPause()
}
override fun onResume() {
adapter.setTags(tagsViewModel.getData())
super.onResume()
}
and it is working the same than before, when I come back from background in onResume my viewModel is returning always one list less which is totally random, sometimes textList, sometimes emailList... Why it is loosing its data? I have no clue why.
Edit 2
class TagsAdapter : RecyclerView.Adapter<TagsAdapter.TagHolder>() {
private var baseList: MutableList<BaseTag> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TagHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.tag_item, parent, false)
return TagHolder(itemView)
}
override fun onBindViewHolder(holder: TagHolder, position: Int) {
val tag = baseList[position]
holder.tvAlias.text = tag.content
holder.tvDate.text = tag.date?.let { AppUtils.toDate(it) }
holder.ivType.background = tag.type?.let { getDrawable(it) }
}
override fun getItemCount(): Int {
return baseList.size
}
fun setTags(list: List<BaseTag>) {
if (this.baseList.isEmpty()) {
this.baseList = list as MutableList<BaseTag>
} else {
this.baseList.addAll(list)
}
orderList()
notifyDataSetChanged()
}
private fun getDrawable(type: String): Drawable? {
when (type) {
AppConstants.TYPE_TEXT -> return ContextCompat.getDrawable(App.instance, R.drawable.ic_text)
AppConstants.TYPE_EMAIL -> return ContextCompat.getDrawable(App.instance, R.drawable.ic_write)
AppConstants.TYPE_URL -> return ContextCompat.getDrawable(App.instance, R.drawable.ic_clean)
AppConstants.TYPE_PHONE -> return ContextCompat.getDrawable(App.instance, R.drawable.ic_mobile)
AppConstants.TYPE_LAUNCHER -> return ContextCompat.getDrawable(App.instance, R.drawable.ic_code)
}
return null
}
private fun orderList(){
baseList.sortBy { it.date }
baseList.reverse()
}
fun clear(){
this.baseList.clear()
}
inner class TagHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tvAlias: TextView = itemView.findViewById(R.id.txtAlias)
var tvDate: TextView = itemView.findViewById(R.id.txtDate)
var ivType: ImageView = itemView.findViewById(R.id.ivType)
}
}
Although I am not sure why the fragment is behaving in this way--probably need to look at the adapter implemenation--but few things caught my attention:
observe
in onResume
but not cleared in onPause
Even if the adapter is "cleared" in onPause
state, the observers are not. This means that each time fragment goes to background then foreground, the fragment is creating a new set of observers while previous observers are still alive and observing the same LiveData
, consequently making the adapter less predictable due to these duplicated observers.
Using this
in observing LiveData
LiveData
observers are unsubscribed when the LifeCycle
object goes into DESTROYED state. This means that in order to avoid observers being duplicated, the observers should be subscribed before CREATED
state and that's onCreate
method is Activity
and Fragment
. If you don't like this behavior you need to pass in a different LifeCycleOwner
object.
1. Use getViewLifecycleOwner()
It is quite impractical to observe in fragment.onCreate
because fragment's view is not even ready. So Android has this convenient method called getViewLifecycleOwner()
that can signal DESTROYED
in onDestroyView()
phase so that the Fragment
can observe in onCreateView()
or onViewCreated()
. You can use it like this:
override fun onCreateView(...): View {
...
getTags()
...
return view
}
private fun getTags(){
tagsViewModel.getAllText().observe(getViewLifecycleOwner(),
Observer<List<Text>> { t -> adapter.setTags(t!!) })
tagsViewModel.getAllEmail().observe(getViewLifecycleOwner(),
Observer<List<Email>> { t -> adapter.setTags(t!!) })
tagsViewModel.getAllUrl().observe(getViewLifecycleOwner(),
Observer<List<Url>> { t -> adapter.setTags(t!!) })
tagsViewModel.getAllPhone().observe(getViewLifecycleOwner(),
Observer<List<Phone>> { t -> adapter.setTags(t!!) })
tagsViewModel.getAllLauncher().observe(getViewLifecycleOwner(),
Observer<List<Launcher>> { t -> adapter.setTags(t!!) })
}
2. Use custom LifeCycleOwner
Implement custom LifeCycleOwner
. I think this article can help.
Fragment
lifecycle in ViewPager
Depends on the ViewPager
implementation, Fragment
will not go through onPause
-> onResume
state just by switching the tabs. Also, distance between the tabs can also matter. For instance, the Fragment
will probably not "pause" if you switch to a tab right next to it. Try to log the fragment state to see how switching tabs affect their life cycle.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.