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