Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
310 views
in Technique[技术] by (71.8m points)

android - Espresso doesn't wait till RecyclerView gets update from LiveData

StatsFragment contains the recyclerview. The recylcerView displays "Stat". The recylcerView adapter is updated by observing LiveData in the ViewModel. ViewModel gets its update via Flow from a repository. In StatsFragment is a FAB that opens a DialogFragment, with which a new Stat can be added.

I suspect the problem is that espresso is not waiting till the adapter updates and the new data is shown on the screen.

How can I make espresso wait till the repository flow emits its value, passes it to viewModels LiveData, so the list in the fragment can be updated to notify the adapter to display the list in the recyclerview?

StatsFragment

class StatsFragment : Fragment() {

    private lateinit var binding: FragmentStatsBinding
    private val mainViewModel: MainViewModel by viewModel()

    private lateinit var recyclerView: RecyclerView
    private lateinit var statsAdapter: StatsAdapter
    private lateinit var layoutManager: LinearLayoutManager

    private lateinit var addStats: FloatingActionButton

    private lateinit var addStatFragment: DialogFragment

    private val stats = mutableListOf<Stat>()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentStatsBinding.inflate(inflater, container, false)
        binding.lifecycleOwner = viewLifecycleOwner
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initObserver()
        initUi()
    }

    private fun initObserver() {
        mainViewModel.stats.observe(viewLifecycleOwner, Observer { statList ->
            stats.clear()
            stats.addAll(statList)
            statsAdapter.notifyDataSetChanged()
        })
    }

    private fun initUi() {
        statsAdapter = StatsAdapter(stats)
        layoutManager = LinearLayoutManager(requireContext())
        recyclerView = binding.recyclerViewStatsData
        recyclerView.layoutManager = layoutManager
        recyclerView.adapter = statsAdapter

        addStats = binding.fabStatsAddStat
        addStats.setOnClickListener(addStatOnClickListener)
    }

    private val addStatOnClickListener = View.OnClickListener {
        addStatFragment = AddStatFragment()
        addStatFragment.show(requireActivity().supportFragmentManager, "addStatFragment")
    }

}

AddStatFragment

class AddStatFragment : DialogFragment() {

    private lateinit var binding: FragmentInputBinding
    private val mainViewModel: MainViewModel by viewModel()

    private lateinit var date: TextView
    private lateinit var time: TextView

    private lateinit var confirm: Button

    override fun onStart() {
        super.onStart()
        val width = resources.displayMetrics.widthPixels
        val height = resources.displayMetrics.heightPixels
        dialog!!.window?.setLayout(width, WRAP_CONTENT)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentInputBinding.inflate(inflater, container, false)
        binding.viewModel = mainViewModel
        binding.lifecycleOwner = viewLifecycleOwner
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initUi()
    }

    private fun initUi() {
        date = binding.textViewInputDate
        time = binding.textViewInputTime
        confirm = binding.buttonInputConfirm

        confirm.setOnClickListener {
            mainViewModel.insertStat()
            dialog!!.hide()
        }
    }
}

MainViewModel

class MainViewModel(private val repository: Repository, application: Application) :
    AndroidViewModel(application) {

    private val _stats = repository.getAllStats().asLiveData()
    val stats: LiveData<List<Stat>>
        get() = _stats

    private val _insertStatStatus = MutableLiveData<Event<Resource<Stat>>>()
    val insertStatStatus: LiveData<Event<Resource<Stat>>>
        get() = _insertStatStatus

    val weight = MutableLiveData<String>()
    val waist = MutableLiveData<String>()
    val kCal = MutableLiveData<String>()
    val date = MutableLiveData<String>()
    val time = MutableLiveData<String>()

    private fun insertStatIntoDatabase(stat: Stat) {
        viewModelScope.launch {
            repository.insertStat(stat)
        }
    }

    internal fun insertStat(weight: String, waist: String, kCal: String, date: String, time: String) {
        val newStat = Stat(weight.toDouble(), waist.toDouble(), kCal.toDouble(), date, time)
        insertStatIntoDatabase(newStat)
        _insertStatStatus.value = Event(Resource.success(newStat))
    }


    internal fun insertStat() {
        insertStat(weight.value ?: "", waist.value ?: "", kCal.value ?: "", date.value ?: "", time.value ?: "")
    }
}

FakeRepository

class FakeStatsRepositoryAndroidTest: Repository {

    private val fakeStats = MutableLiveData<MutableList<Stat>>()
    private val underlyingList = mutableListOf<Stat>()
    private var idCounter = 1

    init {
        fakeStats.value = underlyingList
    }

    override fun getAllStats(): Flow<List<Stat>> {
        return fakeStats.asFlow()
    }

    override suspend fun insertStat(stat: Stat) {

        val newStat = Stat(stat.weight,stat.waist,stat.kCal,stat.date,stat.time,idCounter)
        idCounter++
        underlyingList.add(newStat)
    }

    override suspend fun deleteStat(stat: Stat) {
        underlyingList.remove(stat)
    }

    override suspend fun updateStat(stat: Stat) {
        if(underlyingList.removeIf { it.id == stat.id }) {
            underlyingList.add(stat)
        } else {
            return
        }
    }
}

Test

    @Test
    fun openingAddStatFragmentWhenEnteringInformationAndPressingConfirmThenStatIsVisibleInList() {
        val statToAdd = Stat(70.1,88.5,2300.0,"today", "now",id = 1)

        onView(withId(R.id.fab_stats_addStat)).perform(click())
        onView(withId(R.id.textInputEditText_input_weight)).perform(typeText(statToAdd.weight.toString()))
        onView(withId(R.id.textInputEditText_input_waist)).perform(typeText(statToAdd.weight.toString()))
        onView(withId(R.id.textInputEditText_input_kcal)).perform(typeText(statToAdd.weight.toString()))
        onView(withId(R.id.button_input_confirm)).perform(click())
        onView(withId(R.id.recyclerView_stats_data)).perform(RecyclerViewActions.scrollTo<StatsViewHolder>(hasDescendant(withText("70.1"))))
    }

I removed some code (input value checking, time&date related stuff, etc.)

question from:https://stackoverflow.com/questions/66055391/espresso-doesnt-wait-till-recyclerview-gets-update-from-livedata

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...