简体   繁体   中英

LiveData Room loosing data when going to background

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM