In my Kotlin app I have 3 screens:
- 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.
- 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.
- 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:
- 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.
- 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.
- 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 badges1 Answer
Reset to default 0Do 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()
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