Andoroid MVI Example: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
Line 60: Line 60:
}
}
</syntaxhighlight>
</syntaxhighlight>
=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))
            }
        }
    }
}

Revision as of 01:35, 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. 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))
           }
       }
   }

}