The most likely reason for a UI freeze is that you're retrieving the data from some slow source, like the network or a database.
If that is the case, the solution is to retrieve the data on a background thread. These days I'd recommend using Kotlin Coroutines and Kotlin Flow.
Your DB would return a Flow<List>, the contents of which will automatically be updated whenever the data in the DB changes, and you can observe the data updates in the ViewModel.
Get a Flow object from your database:
@Dao
interface MyDataDao {
@Query("SELECT * FROM mydata")
fun flowAll(): Flow<List<MyDataEntity>>
}
The Fragment will observe LiveData and update the list adapter when it changes:
class MyScreenFragment(): Fragment() {
override fun onCreate() {
viewModel.myDataListItems.observe(viewLifecycleOwner) { listItems ->
myListAdapter.setItems(listItems)
myListAdapter.notifyDataSetChanged()
}
}
}
In the ViewModel:
@ExperimentalCoroutinesApi
class MyScreenViewModel(
val myApi: MyApi,
val myDataDao: MyDataDao
) : ViewModel() {
val myDataListItems = MutableLiveData<List<MyDataListItem>>()
init {
// launch a coroutine in the background to retrieve our data
viewModelScope.launch(context = Dispatchers.IO) {
// trigger the loading of data from the network
// which you should then save into the database,
// and that will trigger the Flow to update
myApi.fetchMyDataAndSaveToDB()
collectMyData()
}
}
/** begin collecting the flow of data */
private fun collectMyData() {
flowMyData.collect { myData ->
// update the UI by posting the list items to a LiveData
myDataListItems.postValue(myData.asListItems())
}
}
/** retrieve a flow of the data from database */
private fun flowMyData(): Flow<List<MyData>> {
val roomItems = myDataDao.flowAll()
return roomItems.map { it.map(MyDataEntity::toDomain) }
}
/** convert the data into list items */
private fun MyData.asListItems(): MyDataListItem {
return this.map { MyDataListItem(it) }
}
}
In case you didn't know how to define the objects for in a Room Database, I'll give you this as a hint:
// your data representation in the DB, this is a Room entity
@Entity(tableName = "mydata")
class MyDataEntity(
@PrimaryKey
val id: Int,
val date: String,
// ...
)
// Domain representation of your data
data class MyData(
val id: Int,
val date: SomeDateType,
// ...
)
// Map your DB entity to your Domain representation
fun MyDataEntity.toDomain(): MyData {
return MyData(
id = id,
date = SomeDateFormatter.format(date),
// ...
)
}