Andoroid MVI Example: Difference between revisions
Tag: Reverted |
|||
Line 106: | Line 106: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=MVI Component= | =MVI Component= | ||
==View State== | ==View Model State== | ||
We need to hold the state of the view so we make a data class to hold this. This is like the state in react. I.E. it is the state for the view not for the model. | We need to hold the state of the view so we make a data class to hold this. This is like the state in react. I.E. it is the state for the view not for the model. | ||
<syntaxhighlight lang="kotlin"> | <syntaxhighlight lang="kotlin"> | ||
class | class PostViewModelState { | ||
val progressBar: Boolean = false | val progressBar: Boolean = false | ||
val posts: List<Post> = emptyList() | val posts: List<Post> = emptyList() |
Revision as of 01:57, 8 March 2025
Introduction
Wanted to revisit the MVI pattern to just have another look using another approach this example uses ktor, an asyncronous client, where I was previously using Retrofit, and Oribit MVI which provides the container and the Store elements. (See Below)
The Pattern
Last time I looked at this we had this diagram
For this example I will be using this one
Setup
Like this, as I never knew you could do this to get the latest version, don't do it at work but safe for home I guess. For Orbit MVI we add
implementation("org.orbit-mvi:orbit-core:<latest-version>")
implementation("org.orbit-mvi:orbit-viewmodel:<latest-version>")
implementation("org.orbit-mvi:orbit-compose:<latest-version>")
testImplementation("org.orbit-mvi:orbit-test:<latest-version>")
And for Ktor
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
implementation ('io.ktor:ktor-client-serialization:<latest-version>')
implementation("io.ktor:ktor-client-core:<latest-version>")
implementation("io.ktor:ktor-client-content-negotiation:<latest-version>")
implementation("io.ktor:ktor-client-android:<latest-version>")
runtimeOnly("io.ktor:ktor-serialization-kotlinx-json:<latest-version>")
Resource
This is a class which is called in our case DataState but lots of people call this Resource. In this class we create a function for each state we are managing and the data we need to create this State.
package nz.co.bibble.mviexample
sealed class DataState<T> {
data class Loading<T>(val isLoading: Boolean) : DataState<T>()
data class Success<T>(val data: T) : DataState<T>()
data class Error<T>(val uiComponent: UIComponent) : DataState<T>()
}
sealed class UIComponent {
data class Total(val text: String) : UIComponent(),
}
Post API
This not part of MVI, it is a service used for the data. Previously I have used Retrofit to do this job and it would live in the Data Layer.
Post API Interface
Here we create an interface which would normally be in the Domain Layer. It has a companion object to create the http client, this would normally be injected using Dagger or some other DI.
interface PostApi {
suspend fun getPosts(): List<Post>
companion object {
val httpClient = HttpClient(Android) {
install(ContentNegotiation) {
Json {
this.ignoreUnknownKeys = true
}
}
}
fun providePostApi(): PostApi {
return PostApiImpl(httpClient)
}
}
}
Post API Implementation
And here is the implementation
class PostApiImpl(
private val httpClient :HttpClient
):PostApi {
override suspend fun getPosts(): List<Post> {
return httpClient.get(
"https://jsonplaceholder.typicode.com/posts"
).body()
}
}
Use Case
This is the use case to get the posts
class GetPosts(
private val postApi: PostApi
) {
fun execute(): Flow<DataState<List<Post>>> {
return flow {
emit(DataState.Loading(true))
try {
val posts = postApi.getPosts()
emit(DataState.Success(posts))
} catch (e: Exception) {
e.printStackTrace()
emit(DataState.Error(UIComponent.Total("Failed to get posts")))
}
finally {
emit(DataState.Loading(false))
}
}
}
}
MVI Component
View Model State
We need to hold the state of the view so we make a data class to hold this. This is like the state in react. I.E. it is the state for the view not for the model.
class PostViewModelState {
val progressBar: Boolean = false
val posts: List<Post> = emptyList()
val error: String? = null
}
View Model
So here is the View Model. This initiates the use case. In our case this is getPosts(). The Post API would normally be injected into to Data Layer.
class PostViewModel: ViewModel() {
val getPosts = GetPosts(PostApi.providePostApi())
}