最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

android - Updating UiState of a screen from a viewModel of another screen Kotlin - Stack Overflow

programmeradmin1浏览0评论

In my Kotlin app I have 3 screens:

  1. QueryScreen
    • has one field where the user enters a query, and a button for search action. When pressed, it fetches data from the Google Books API as a list of books.
  2. BookListScreen
    • needs to show that list of books and when the user selects one book, it performs another API request to get details of that specific book.
  3. BookDetailsScreen
    • needs to show details of the book fetched by BookListScreen.

Currently, I tried to approach this problem by having UiState and ViewModel classes for each of the screens. The data in my app needs to flow like this: QueryScreen fetches data from the server and updates BookListUiState to reflect those changes. BookListViewModel sees the data and updates the Ui accordingly for the BookListScreen. User taps on a book then another request is performed which updates BookDetailsUiState. I need to use manual DI because I am learning Kotlin and I want to work with the basics.

My questions for this problem are:

  • Do I need 3 separate UiStates and ViewModel for each screen?
  • What is the best approach for my case? (Best means general recommendation according to Android official design patterns)
data class QueryScreenState(
    val query: String = "",
    val isLoading: Boolean = false,
    val errorMessage: String? = null
)

data class BookListScreenState(
    val books: List<Book> = emptyList(),
    val isLoading: Boolean = false,
    val errorMessage: String? = null
)

data class BookDetailsScreenState(
    val book: Book? = null,
    val isLoading: Boolean = false,
    val errorMessage: String? = null
)

Also, I am using a repository

sealed interface BookshelfResult<out T> {
    data class Success<T>(val data: T) : BookshelfResult<T>
    data class Error(val exception: Exception) : BookshelfResult<Nothing>
}

interface BookshelfRepository {
    suspend fun getBooks(query: String): BookshelfResult<List<Book>>
    suspend fun getBook(id: String): BookshelfResult<Book>
}

class DefaultBookshelfRepository(
    private val bookshelfApiService: BookshelfApiService
): BookshelfRepository {
    override suspend fun getBooks(query: String): BookshelfResult<List<Book>> {
        return try {
            val res = bookshelfApiService.getBooks(query)
            if(res.isSuccessful) {
                val books = res.body()?.items ?: emptyList()
                BookshelfResult.Success(books)
            } else {
                BookshelfResult.Error(HttpException(res))
            }
        } catch (e: IOException) {
            BookshelfResult.Error(e)
        } catch (e: HttpException) {
            BookshelfResult.Error(e)
        }
    }

    override suspend fun getBook(id: String): BookshelfResult<Book> {
        return try {
            val res = bookshelfApiService.getBook(id)
            if (res.isSuccessful) {
                val book = res.body()
                if (book != null) {
                    BookshelfResult.Success(book)
                } else {
                    BookshelfResult.Error(Exception("Book not found"))
                }
            } else {
                BookshelfResult.Error(HttpException(res))
            }
        } catch (e: IOException) {
            BookshelfResult.Error(e)
        } catch (e: HttpException) {
            BookshelfResult.Error(e)
        }
    }
}

I am a beginner, and all of your advice will be helpful. This problem is from the Android Development with Kotlin official course:

In my Kotlin app I have 3 screens:

  1. QueryScreen
    • has one field where the user enters a query, and a button for search action. When pressed, it fetches data from the Google Books API as a list of books.
  2. BookListScreen
    • needs to show that list of books and when the user selects one book, it performs another API request to get details of that specific book.
  3. BookDetailsScreen
    • needs to show details of the book fetched by BookListScreen.

Currently, I tried to approach this problem by having UiState and ViewModel classes for each of the screens. The data in my app needs to flow like this: QueryScreen fetches data from the server and updates BookListUiState to reflect those changes. BookListViewModel sees the data and updates the Ui accordingly for the BookListScreen. User taps on a book then another request is performed which updates BookDetailsUiState. I need to use manual DI because I am learning Kotlin and I want to work with the basics.

My questions for this problem are:

  • Do I need 3 separate UiStates and ViewModel for each screen?
  • What is the best approach for my case? (Best means general recommendation according to Android official design patterns)
data class QueryScreenState(
    val query: String = "",
    val isLoading: Boolean = false,
    val errorMessage: String? = null
)

data class BookListScreenState(
    val books: List<Book> = emptyList(),
    val isLoading: Boolean = false,
    val errorMessage: String? = null
)

data class BookDetailsScreenState(
    val book: Book? = null,
    val isLoading: Boolean = false,
    val errorMessage: String? = null
)

Also, I am using a repository

sealed interface BookshelfResult<out T> {
    data class Success<T>(val data: T) : BookshelfResult<T>
    data class Error(val exception: Exception) : BookshelfResult<Nothing>
}

interface BookshelfRepository {
    suspend fun getBooks(query: String): BookshelfResult<List<Book>>
    suspend fun getBook(id: String): BookshelfResult<Book>
}

class DefaultBookshelfRepository(
    private val bookshelfApiService: BookshelfApiService
): BookshelfRepository {
    override suspend fun getBooks(query: String): BookshelfResult<List<Book>> {
        return try {
            val res = bookshelfApiService.getBooks(query)
            if(res.isSuccessful) {
                val books = res.body()?.items ?: emptyList()
                BookshelfResult.Success(books)
            } else {
                BookshelfResult.Error(HttpException(res))
            }
        } catch (e: IOException) {
            BookshelfResult.Error(e)
        } catch (e: HttpException) {
            BookshelfResult.Error(e)
        }
    }

    override suspend fun getBook(id: String): BookshelfResult<Book> {
        return try {
            val res = bookshelfApiService.getBook(id)
            if (res.isSuccessful) {
                val book = res.body()
                if (book != null) {
                    BookshelfResult.Success(book)
                } else {
                    BookshelfResult.Error(Exception("Book not found"))
                }
            } else {
                BookshelfResult.Error(HttpException(res))
            }
        } catch (e: IOException) {
            BookshelfResult.Error(e)
        } catch (e: HttpException) {
            BookshelfResult.Error(e)
        }
    }
}

I am a beginner, and all of your advice will be helpful. This problem is from the Android Development with Kotlin official course: https://developer.android/codelabs/basic-android-kotlin-compose-bookshelf#0

Share Improve this question asked Feb 3 at 14:34 Andrei RusuAndrei Rusu 173 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0
  1. Do I need 3 separate UiStates and ViewModel for each screen?

    Yes, three separate UiStates needs.

    ViewModel for each screen? Its depends on the your modules. if you separate each feature as individual that case each screen needs separate view model.

    otherwise you can define multiple Uistate in single view model

    val topicUiState: StateFlow<TopicUiState> = topicUiState(
        topicId = topicId,
        userDataRepository = userDataRepository,
        topicsRepository = topicsRepository,
    ).stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = TopicUiState.Loading,
        )
    
    val newsUiState: StateFlow<NewsUiState> = newsUiState(
        topicId = topicId,
        userDataRepository = userDataRepository,
        userNewsResourceRepository = userNewsResourceRepository,
    ).stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = NewsUiState.Loading,
        )
    

    then you can use your screen like this

      val topicUiState: TopicUiState by viewModel.topicUiState.collectAsStateWithLifecycle()
      val newsUiState: NewsUiState by viewModel.newsUiState.collectAsStateWithLifecycle()
    
  2. What is the best approach for my case?

    The following Google samples demonstrate good app architecture. Go explore them to see this guidance in practice samples

发布评论

评论列表(0)

  1. 暂无评论