Andoroid MVI Example
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. Here we create an interface which would normally be in the Domain Layer
interface PostApi {
suspend fun getPosts(): List<Post>
}
And 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)) } } }
}