Andoroid MVI Example: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
Line 42: Line 42:
</syntaxhighlight>
</syntaxhighlight>
=Post API=
=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
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.
<syntaxhighlight lang="kotlin">  
<syntaxhighlight lang="kotlin">  
interface PostApi {
interface PostApi {
     suspend fun getPosts(): List<Post>
     suspend fun getPosts(): List<Post>
    companion object {
        val httpClient = HttpClient(Android) {
            install(ContentNegotiation) {
                Json {
                    this.ignoreUnknownKeys = true
                }
            }
        }
        fun providePostApi(): PostApi {
            return PostApiImpl(httpClient)
        }
    }
}
}
</syntaxhighlight>
</syntaxhighlight>
And the implementation
==Post API Implementation==
And here is the implementation
<syntaxhighlight lang="kotlin">  
<syntaxhighlight lang="kotlin">  
class PostApiImpl(
class PostApiImpl(
Line 60: Line 78:
}
}
</syntaxhighlight>
</syntaxhighlight>
=Use Case=
=Use Case=
This is the use case to get the posts
This is the use case to get the posts

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

}