Search code examples
android-jetpack-composeandroid-roomandroid-livedata

How to observe on LiveData ViewModel within LazyColumn?


I am trying to observe on LiveData within LazyColumn, but that does not work. However, outside the LazyColumn that works. The same is when I do within LaunchedEffect.

Perhaps, the CoroutineScope and LazyScope are triggered before items are observed. Am I right?

How to get items within the LazyColumn or LaunchedEffect?

Below my implementation.

Dao:

@Dao
interface ItemDao {
    @Query("SELECT * FROM $ITEMS")
    fun getAll(): LiveData<List<Item>>
}

Repository:

class Repository(private val itemDao: ItemDao) {
    val getAll: LiveData<List<Item>> = itemDao.getAll()
}

DataModel:

class DataModel(private val app: Application) : AndroidViewModel(app) {
    fun getAll(): LiveData<List<Item>> {
        return repository.getAll
    }
}

AppDatabase:

@Database(entities = [Item::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {

    abstract val itemDao: ItemDao

    companion object {

       private const val ItemsDB = "Items.db"

        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase = INSTANCE ?: synchronized(this) {
            val instance = Room.databaseBuilder(
                context.applicationContext,
                AppDatabase::class.java,
                ItemsDB
            )
                .build()
            INSTANCE = instance
            return instance
        }

    }
}

MyApp:

class MyApp : Application() {

    lateinit var mDatabase: AppDatabase

    override fun onCreate() {
        super.onCreate()

        mDatabase = AppDatabase.getInstance(applicationContext)
    }
}

MainActivity:

class MainActivity : ComponentActivity() {
    @OptIn(ExperimentalAnimationApi::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val dataModel = ViewModelProvider(this)[DataModel::class.java]

        setContent {
            MyAppTheme {
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                    NavGraph(rememberAnimatedNavController(), dataModel)
                }
            }
        }
    }
}

NavGraph:

@Composable
@OptIn(ExperimentalAnimationApi::class)
fun NavGraph(controller: NavHostController = rememberAnimatedNavController(), dataModel: DataModel = viewModel()) {
    AnimatedNavHost(controller, HOME) {
        composable(HOME) { HomeScreen(controller, dataModel) }
    }
}

HomeScreen:

@Composable
@Preview(showBackground = true)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
fun HomeScreen(controller: NavController = rememberNavController(), dataModel: DataModel = viewModel()) {

    val items by dataModel.getAll().observeAsState(listOf())

    Column(Modifier.fillMaxWidth()) {

        Text(text = items[0].text) // This one works fine.
       

        LazyColumn(Modifier.padding(start = 6.dp, end = 6.dp)) {

            itemsIndexed(questions) { index, _ ->
                Text(text = items[index].text)
            }
        }
    }

    LaunchedEffect(Unit) {
         // items[0].count -> Does not work... :(
    }
}

Solution

  • The problem could be that you are trying to access the first item in the list in LaunchedEffect, even though the list itself is not loaded. The problem can be solved like this:

    LaunchedEffect(items) {
        if (items.isEmpty()) {
            // Your code if list is loading
        }
        else {
            // Your code if list already loaded
        }
    }
    

    Explanation: The code in LaunchedEffect is called only when the val items changes, in it we check if the list is empty, we can display the load animation, in the other case your code will be invoked

    Also if variable questions doesn't used in your lazy column. Recommend to change it to itemsIndexed(items) { index, item -> }

    If that does not help, please send the error log