In my Kotlin app I have 3 screens:
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:
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.com/codelabs/basic-android-kotlin-compose-bookshelf#0
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()
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