I just started learning Android development. I want to find a case, using Viewmodel+Room+Flow+collectAsStateWithLifecycle(), unfortunately I didn't find such a case. So I wrote the following code referring to the documentation and some information on the Internet.
Dao
#Dao
interface IArticleDao {
#Query("select * from article")
fun getAll(): List<Article>
#Insert
suspend fun add(article: Article): Long
}
Repository
private const val TAG = "ArticleRepository"
class ArticleRepository #Inject constructor(
private val articleDao: IArticleDao
) {
private val ioDispatcher = IO
fun add(article: Article): Long {
var id = 0L
CoroutineScope(ioDispatcher).launch {
try {
id = articleDao.add(article)
}catch (e: Exception){
Log.e(TAG, "There is an exception in the new article")
}
}
return id
}
fun getAll(): Flow<List<Article>> =
flow {
emit(
articleDao.getAll()
)
}.flowOn(ioDispatcher)
}
ViewModel
#HiltViewModel
class ArticleViewModel #Inject constructor(private val articleRepository: ArticleRepository) : ViewModel() {
val articles = articleRepository.getAll().stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList()
)
fun add(article: Article): Long{
return articleRepository.add(article)
}
}
Activity
Scaffold(
content = { innerPadding ->
val articles by articleViewModel.articles.collectAsStateWithLifecycle()
Content(
articles = articles, articleViewModel
)
}
)
#Composable
fun Content(
articles:
List<Article>, viewModel: ArticleViewModel
) {
Column {
Row {
Button(onClick = {
viewModel.add(Article(title = "this is a test", url = "http://www.test4.com/"))
}) {
Text("add")
}
}
Row {
LazyColumn(
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(16.dp)
) {
items(items = articles, key = { article -> article.id }) { article ->
Row {
Text(text = article.title)
}
}
}
}
}
}
This creates a problem. When a new piece of data is added, a new piece of data is added to the database, but the UI interface is not updated accordingly. Only when the Home button is clicked and the application is opened again can the UI interface be updated.
I know I must be missing something, but I don't know what. Possibly the code in the Repository?
Instead of returning a list in your DAO, try returning a Flow<List<Article>>.
To do that, don't forget to add the following dependency implementation("androidx.room:room-ktx:$room_version")
#Dao
interface IArticleDao {
#Query("select * from article")
fun getAll(): Flow<List<Article>>
#Insert
suspend fun add(article: Article): Long
}
private const val TAG = "ArticleRepository"
class ArticleRepository #Inject constructor(
private val articleDao: IArticleDao
) {
private val ioDispatcher = IO
fun add(article: Article): Long {
var id = 0L
CoroutineScope(ioDispatcher).launch {
try {
id = articleDao.add(article)
}catch (e: Exception){
Log.e(TAG, "There is an exception in the new article")
}
}
return id
}
fun getAll(): Flow<List<Article>> = articleDao.getAll().flowOn(ioDispatcher)
}
Using a flow, you will listen the database changes instead of just querying one time the datas.
Related
I am trying to do some stuff on background and then displaying it to the user, but for some reason this does not work as it should and I am not sure what I am doing wrong.
It is an app with possibility to encrypt images and storing them on app-specific folder and holding the reference inside a database. While presenting it to the user following steps are done:
Get the reference of pictures and metadata from database.
read encrypted images and decrypt them while reading.
Print the pictures in composable.
How it works is:
Composable asks for getting data -> the repository gets the data -> my storage manager reads the files and uses the cryptomanager to decrypt them -> decrypted pictures are stored as live data
But the operation above blocks the interaction with the UI. Here is some Code:
Composable:
#Composable
fun WelcomeView(
viewModel: WelcomeViewModel = hiltViewModel()
) {
LaunchedEffect(Unit) {
viewModel.getGalleryItems()
}
val list = viewModel.images.observeAsState()
Column() {
//this button does not response until the data request and processing is done
Button(onClick = {}){
Text(text = "Click me while pictures are requested")
}
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
if (list.value != null) {
items(list.value as List<GalleryElement>) { item: GalleryElement ->
GalleryItem(element = item)
}
}
}
}
}
Thats the view model:
#HiltViewModel
class WelcomeViewModel #Inject constructor(
private val secretDataManager: SecretDataManager,
) : ViewModel() {
private val _images = MutableLiveData<List<GalleryElement>>()
val images: LiveData<List<GalleryElement>> = _images
suspend fun getGalleryItems() {
viewModelScope.launch {
_images.value = secretDataManager.getImages()
}
}
}
User data manager:
class SecretDataManager #Inject constructor(
private val cryptoManager: CryptoManager,
private val storageManager: StorageManager,
private val repo: EncryptedVaultDataRepo,
#ApplicationContext
private val ctx: Context
) : SecretDataManagerService {
override suspend fun getImages(): List<GalleryElement> {
val result: MutableList<GalleryElement> = mutableListOf()
repo.getAll().forEach {
var image: ByteArray
storageManager.readFile(File("${ctx.filesDir}/${it.name}").toUri()).use { b ->
image = cryptoManager.decrypt(it.iv, b?.readBytes()!!)
}
result.add(GalleryElement(BitmapFactory.decodeByteArray(image, 0, image.size)))
}
return result
}
}
Any ideas what I am doing wrong?
I believe the main problem is that the viewModelScope.launch(){} starts on the Dispatchers.Main(UI) thread. I recommend going to viewModelScope.launch(Dispatchers.IO){}. I am trying to find the documentation to support that but should be an easy change. I also recommended populating the list on the initialization of the view model.
#Composable
fun WelcomeView(
viewModel: WelcomeViewModel = hiltViewModel()
) {
val list = viewModel.images.observeAsState()
Column() {
//this button does not response until the data request and processing is done
Button(onClick = {}){
Text(text = "Click me while pictures are requested")
}
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
if (list.value != null) {
items(list.value as List<GalleryElement>) { item: GalleryElement ->
GalleryItem(element = item)
}
}
}
}
}
#HiltViewModel
class WelcomeViewModel #Inject constructor(
private val secretDataManager: SecretDataManager,
) : ViewModel() {
private val _images = MutableLiveData<List<GalleryElement>>()
val images: LiveData<List<GalleryElement>> = _images
init{
getGalleryImages()
}
fun getGalleryItems() {
viewModelScope.launch(Dispatchers.Default) {
_images.value = secretDataManager.getImages()
}
}
}
The following code is an MVVM architecture-based ROOM implementation in Kotlin for Android.
I have a problem with the pre-population of the database. My database doesn't have any data or I can't find the solution to get or insert data.
The IDE doesn't throw any compilation problems.
I think the problem is in the ViewModel but I'm at a dead end.
EDITED: I have seen the DB and it's populated. The insert is fine but not the data read. I will try to modify the ViewModel and its associated livedata and mutablelive data.
This is my data class:
#Entity(tableName = Constants.DATABASE_NAME)
data class Card(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = Constants.COLUMN_NAME_ID) val id: Int?,
#ColumnInfo(name = Constants.COLUMN_NAME_FAV) val fav: Boolean,
#ColumnInfo(name = Constants.COLUMN_NAME_DATE) val date: Date,
#ColumnInfo(name = Constants.COLUMN_NAME_RAW_VALUE) val rawValue: String,
#ColumnInfo(name = Constants.COLUMN_NAME_STORE_TYPE) val storeType: String,
#ColumnInfo(name = Constants.COLUMN_NAME_STORE_NAME) val storeName: String,
#ColumnInfo(name = Constants.COLUMN_NAME_STORE_NOTES) val storeNotes: String
)
This is my interface dao
#Insert
suspend fun insertNewCard(card: Card) {
}
#Delete
suspend fun deleteCard(card: Card)
#Query("SELECT * FROM ${Constants.DATABASE_NAME}")
fun getAllCards(): LiveData<List<Card>>
UPDATE:
This is my Database class
#Database(
entities = [Card::class],
version = 1,
exportSchema = true,
//autoMigrations = [AutoMigration (from = 1, to = 2)]
)
#TypeConverters(Converters::class)
abstract class CardsDatabase : RoomDatabase() {
abstract fun cardsDao(): CardsDao
private class CardsDatabaseCallback(private val scope: CoroutineScope)
: RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.cardsDao())
}
}
}
suspend fun populateDatabase(cardsDao: CardsDao) {
val testCard = Card(null,
false,
((Calendar.getInstance()).time),
"test",
"codebar test 1",
"Prueba tienda",
"Esta es una tienda de prueba"
)
val testCardTwo = Card(null,
false,
((Calendar.getInstance()).time),
"barcode test 2",
"codebar",
"Prueba tienda 2",
"Esta es una segunda prueba de tienda"
)
Log.d("ROOM", "$testCard")
cardsDao.insertNewCard(testCard)
}
}
companion object {
#Volatile
private var INSTANCE: CardsDatabase? = null
fun getDatabase(context: Context, scope: CoroutineScope): CardsDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
CardsDatabase::class.java,
Constants.DATABASE_NAME)
.addCallback(CardsDatabaseCallback(scope))
.build()
INSTANCE = instance
instance
}
}
}
This is the repository class
val allCards: LiveData<List<Card>> = cardsDao.getAllCards()
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun insertCard(card: Card) {
cardsDao.insertNewCard(card)
}
suspend fun deleteCard(card: Card) {
cardsDao.deleteCard(card)
}
This is my viewModel:
The Logcat shows as null the LiveData at this point.
private val _allCards = MutableLiveData<List<Card>>()
val allCards : LiveData<List<Card>> = _allCards
init {
getAllCards()
Log.d("ROOM", "${allCards.value}")
}
private fun getAllCards() = viewModelScope.launch {
_allCards.value = cardRepository.allCards.value
Log.d("ROOM", "_ : ${_allCards.value}")
}
fun insertCard(card: Card) = viewModelScope.launch {
cardRepository.insertCard(card)
Log.d("ROOM", "inserted: $card")
}
}
class CardsViewModelFactory(private val repository: CardRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(CardsViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return CardsViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
This is the application class:
private val applicationScope = CoroutineScope(SupervisorJob())
private val database by lazy { CardsDatabase.getDatabase(this, applicationScope) }
val repository by lazy { CardRepository(database.cardsDao()) }
For last, the observe in MainActivity:
cardsViewModel.allCards.observe(this) { cards ->
cards?.let { adapter.setData(it) }
}
My version.
It works with classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21'
add to build.gradle:
plugins {
id 'kotlin-kapt'
}
dependencies {
def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
testImplementation "androidx.room:room-testing:$room_version"
}
create Entity.class:
#Entity(tableName = "cards")
data class CardEntity(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
val data: String
)
create Dao.class
#Dao
interface CardDAO {
#Insert
suspend fun insert(cardEntity: CardEntity): Long
#Delete
suspend fun delete(cardEntity: CardEntity): Int
#Update
suspend fun update(cardEntity: CardEntity): Int
#Query("SELECT * FROM cards WHERE id = :id")
fun getDataById(id:Int): Flow<CardEntity?>
#Query("SELECT * FROM cards")
fun getAll(): Flow<List<CardEntity>>
}
create Database.class:
for work you should to use singleton
#Database(
entities = [
CardEntity::class
],
version = 1
)
abstract class MyDatabase : RoomDatabase() {
companion object {
private var INSTANCE: MyDatabase? = null
fun get(context: Context): MyDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context, MyDatabase::class.java,
"testBase").build()
}
return INSTANCE as MyDatabase
}
}
abstract fun cardDAO(): CardDAO
}
test TestDatabase.class:
#RunWith(AndroidJUnit4::class)
class TestDatabase {
lateinit var db: MyDatabase
#Before
fun createDB() {
val appContext =
InstrumentationRegistry.getInstrumentation().targetContext
db = Room.inMemoryDatabaseBuilder(appContext,
MyDatabase::class.java).build()
}
#After
#Throws(IOException::class)
fun closeDB() {
db.close()
}
private fun setData() = runBlocking {
db.cardDAO().insert(CardEntity(data = "1"))
db.cardDAO().insert(CardEntity(data = "2"))
db.cardDAO().insert(CardEntity(data = "3"))
db.cardDAO().insert(CardEntity(data = "4"))
db.cardDAO().insert(CardEntity(data = "5"))
}
#Test
fun test() = runBlocking {
setData()
Assert.assertEquals(db.cardDAO().getAll().first().size, 5)
val flowIdFirst = db.cardDAO().getDataById(1)
Assert.assertEquals(flowIdFirst.first()?.data, "1")
Assert.assertEquals(db.cardDAO().getDataById(2).first()?.data, "2")
Assert.assertEquals(db.cardDAO().getDataById(3).first()?.data, "3")
Assert.assertEquals(db.cardDAO().getDataById(4).first()?.data, "4")
Assert.assertEquals(db.cardDAO().getDataById(5).first()?.data, "5")
Assert.assertTrue(db.cardDAO().update(CardEntity(1, "my new data")) > 0)
Assert.assertEquals(flowIdFirst.first()?.data, "my new data")
Assert.assertTrue(db.cardDAO().delete(CardEntity(1,"")) > 0)
Assert.assertEquals(flowIdFirst.first(), null)
}
}
The problem is the LiveData created in DAO. To fix it:
Remove LiveData from DAO to return a List:
#Query("SELECT * FROM ${Constants.DATABASE_NAME}")
suspend fun getAllCards(): List<Card>
From the repository, make a suspend fun that gets access to DAO and gets the List, and remove the LiveData type.
suspend fun getAllCards() : List<Card> = cardsDao.getAllCards()
Change the method to access the repository in ViewModel. It works fine.
private fun getAllCards() = viewModelScope.launch {
_allCards.value = cardRepository.getAllCards()
}
Solved.
I am learning Android development, and as I saw in many topics, people were talking about that LiveData is not recommended to use anymore. I mean it's not up-to-date, and we should use Flows instead.
I am trying to get data from ROOM database with Flows and then convert them to StateFlow because as I know they are observables, and I also want to add UI states to them. Like when I get data successfully, state would change to Success or if it fails, it changes to Error.
I have a simple app for practicing. It stores subscribers with name and email, and show them in a recyclerview.
I've checked a lot of sites, how to use stateIn method, how to use StateFlows and Flows but didn't succeed. What's the most optimal way to do this?
And also what's the proper way of updating recyclerview adapter? Is it okay to change it all the time in MainActivity to a new adapter?
Here is the project (SubscriberViewModel.kt - line 30):
Project link
If I am doing other stuff wrong, please tell me, I want to learn. I appreciate any kind of help.
DAO:
import androidx.room.*
import kotlinx.coroutines.flow.Flow
#Dao
interface SubscriberDAO {
#Insert
suspend fun insertSubscriber(subscriber : Subscriber) : Long
#Update
suspend fun updateSubscriber(subscriber: Subscriber) : Int
#Delete
suspend fun deleteSubscriber(subscriber: Subscriber) : Int
#Query("DELETE FROM subscriber_data_table")
suspend fun deleteAll() : Int
#Query("SELECT * FROM subscriber_data_table")
fun getAllSubscribers() : Flow<List<Subscriber>>
#Query("SELECT * FROM subscriber_data_table WHERE :id=subscriber_id")
fun getSubscriberById(id : Int) : Flow<Subscriber>
}
ViewModel:
class SubscriberViewModel(private val repository: SubscriberRepository) : ViewModel() {
private var isUpdateOrDelete = false
private lateinit var subscriberToUpdateOrDelete: Subscriber
val inputName = MutableStateFlow("")
val inputEmail = MutableStateFlow("")
private val _isDataAvailable = MutableStateFlow(false)
val isDataAvailable : StateFlow<Boolean>
get() = _isDataAvailable
val saveOrUpdateButtonText = MutableStateFlow("Save")
val deleteOrDeleteAllButtonText = MutableStateFlow("Delete all")
/*
//TODO - How to implement this as StateFlow<SubscriberListUiState> ??
//private val _subscribers : MutableStateFlow<SubscriberListUiState>
//val subscribers : StateFlow<SubscriberListUiState>
get() = _subscribers
*/
private fun clearInput() {
inputName.value = ""
inputEmail.value = ""
isUpdateOrDelete = false
saveOrUpdateButtonText.value = "Save"
deleteOrDeleteAllButtonText.value = "Delete all"
}
fun initUpdateAndDelete(subscriber: Subscriber) {
inputName.value = subscriber.name
inputEmail.value = subscriber.email
isUpdateOrDelete = true
subscriberToUpdateOrDelete = subscriber
saveOrUpdateButtonText.value = "Update"
deleteOrDeleteAllButtonText.value = "Delete"
}
fun saveOrUpdate() {
if (isUpdateOrDelete) {
subscriberToUpdateOrDelete.name = inputName.value
subscriberToUpdateOrDelete.email = inputEmail.value
update(subscriberToUpdateOrDelete)
} else {
val name = inputName.value
val email = inputEmail.value
if (name.isNotBlank() && email.isNotBlank()) {
insert(Subscriber(0, name, email))
}
inputName.value = ""
inputEmail.value = ""
}
}
fun deleteOrDeleteAll() {
if (isUpdateOrDelete) {
delete(subscriberToUpdateOrDelete)
} else {
deleteAll()
}
}
private fun insert(subscriber: Subscriber) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(subscriber)
_isDataAvailable.value = true
}
private fun update(subscriber: Subscriber) = viewModelScope.launch(Dispatchers.IO) {
repository.update(subscriber)
clearInput()
}
private fun delete(subscriber: Subscriber) = viewModelScope.launch(Dispatchers.IO) {
repository.delete(subscriber)
clearInput()
}
private fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
repository.deleteAll()
//_subscribers.value = SubscriberListUiState.Success(emptyList())
_isDataAvailable.value = false
}
sealed class SubscriberListUiState {
data class Success(val list : List<Subscriber>) : SubscriberListUiState()
data class Error(val msg : String) : SubscriberListUiState()
}
}
MainActivity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: SubscriberViewModel
private lateinit var viewModelFactory: SubscriberViewModelFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val dao = SubscriberDatabase.getInstance(application).subscriberDAO
viewModelFactory = SubscriberViewModelFactory(SubscriberRepository(dao))
viewModel = ViewModelProvider(this, viewModelFactory)[SubscriberViewModel::class.java]
binding.viewModel = viewModel
binding.lifecycleOwner = this
initRecycleView()
}
private fun initRecycleView() {
binding.recyclerViewSubscribers.layoutManager = LinearLayoutManager(
this#MainActivity,
LinearLayoutManager.VERTICAL, false
)
displaySubscribersList()
}
private fun displaySubscribersList() {
/*
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.subscribers.collect { uiState ->
when (uiState) {
is SubscriberViewModel.SubscriberListUiState.Success -> {
binding.recyclerViewSubscribers.adapter = SubscriberRecyclerViewAdapter(uiState.list) {
subscriber: Subscriber -> listItemClicked(subscriber)
}
}
is SubscriberViewModel.SubscriberListUiState.Error -> {
Toast.makeText(applicationContext,uiState.msg, Toast.LENGTH_LONG).show()
}
}
}
}
}*/
}
private fun listItemClicked(subscriber: Subscriber) {
Toast.makeText(this, "${subscriber.name} is selected", Toast.LENGTH_SHORT).show()
viewModel.initUpdateAndDelete(subscriber)
}
}
You can convert a Flow type into a StateFlow by using stateIn method.
private val coroutineScope = CoroutineScope(Job())
private val flow: Flow<CustomType>
val stateFlow = flow.stateIn(scope = coroutineScope)
In order to transform the CustomType into UIState, you can use the transformLatest method on Flow. It will be something like below:
stateFlow.transformLatest { customType ->
customType.toUiState()
}
Where you can create an extension function to convert CustomType to UiState like this:
fun CustomType.toUiState() = UiState(
x = x,
y = y... and so on.
)
I want to update the data in the database and display the updated data on the screen.But I can't do it. The update occurs only after restarting the application.
What am I doing wrong?. Help please
fun list(model: MyViewModel = viewModel()) {
val list = model.listToDo.observeAsState(listOf()).value
val grouped = list.groupBy { it.isFinished }
LazyColumn(contentPadding = PaddingValues(vertical = 8.dp)
, verticalArrangement = Arrangement.spacedBy(8.dp)
, ){
grouped.forEach { (initial, contactsForInitial) ->
stickyHeader {
Text(
"Section $initial",
Modifier
.fillMaxWidth()
.padding(8.dp)
)
}
items(contactsForInitial) { contact ->
card(contact, model = model)
}
}
}
}
Function in viewmodel
fun update(task: ToDo){
viewModelScope.launch(Dispatchers.IO) {
repository.update(task)
}
}
Function in the dao
#Update
fun update(task: ToDo)
ViewModel
class MyViewModel : ViewModel() {
private val repository = Repository.get()
val listToDo = repository.getListToDo()
fun delete(task: ToDo){
viewModelScope.launch(Dispatchers.IO) {
repository.delete(task)
}
}
fun insertToDo(task: ToDo){
repository.insertToDo(task = task)
}
fun update(task: ToDo){
viewModelScope.launch(Dispatchers.IO) {
repository.update(task)
}
}}
You are not observing the state. I think this code:
val list = model.listToDo.observeAsState(listOf()).value just takes a single snapshot of the flow. Try it like that:
val list by model.listToDo.observeAsState(listOf())
So, I have two ViewModels and two screens in my app. The first screen is for the presentation of a list of diary item elements, second is for detailed information on diary items. In a second ViewModel, I have an id to get a record from DB but can find it. What can I do to get it?
DAO:
interface DiaryDao {
#Query("SELECT * FROM diaryItems")
fun getAllDiaryPosts(): LiveData<List<DiaryItem>>
#Query("Select * from diaryItems where id = :id")
fun getDiaryPostById(id: Int) : DiaryItem
#Query("Delete from diaryItems where id = :index")
fun deleteDiaryPostByIndex(index : Int)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertDiaryPost(diaryItem: DiaryItem)
#Update
suspend fun updateDiaryPost(diaryItem: DiaryItem)
#Delete
suspend fun deleteDiaryPost(diaryItem: DiaryItem)
#Query("Delete from diaryItems")
suspend fun deleteAllDiaryItems()
}
Repository
class DiaryRepository #Inject constructor(private val diaryDao: DiaryDao) {
val readAllData: LiveData<List<DiaryItem>> = diaryDao.getAllDiaryPosts()
suspend fun getDiaryPostDyIndex(index: Int): DiaryItem {
return diaryDao.getDiaryPostById(index)
}
}
First viewmodel
#HiltViewModel
class PostListViewModel
#Inject
constructor(
private val diaryRepository: DiaryRepository,
) : ViewModel() {
private var allDiaryItems: LiveData<List<DiaryItem>> = diaryRepository.readAllData
}
Second viewmodel
#HiltViewModel
class PostDetailViewModel
#Inject
constructor(
private val savedStateHandle: SavedStateHandle,
private val diaryRepository: DiaryRepository
) : ViewModel() {
sealed class UIState {
object Loading: UIState()
data class Success(val currentPosts: DiaryItem) : UIState()
object Error : UIState()
}
val postDetailState: State<UIState>
get() = _postDetailState
private val _postDetailState = mutableStateOf<UIState>(UIState.Loading)
init {
viewModelScope.launch (Dispatchers.IO) {
try {
val diaryList: DiaryItem = diaryRepository.getDiaryPostDyIndex(2) //it is for test
_postDetailState.value = UIState.Success(diaryList)
} catch (e: Exception) {
withContext(Dispatchers.Main) {
_postDetailState.value = UIState.Error
}
}
}
}
}
I am sure you are getting errors. because you updating UI State in IO Thread
fun getDairyItem(itemId: Int){
viewModelScope.launch (Dispatchers.IO) {
try {
val diaryList: DiaryItem = diaryRepository.getDiaryPostDyIndex(itemId)
withContext(Dispatchers.Main) {
_postDetailState.value = UIState.Success(diaryList)
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
_postDetailState.value = UIState.Error
}
}
}
}