DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Spotify Backstage: A Unifying Force for Developer Experience
  • Serverless Patterns: Web
  • Endpoint Security Controls: Designing a Secure Endpoint Architecture, Part 2
  • Endpoint Security Controls: Designing a Secure Endpoint Architecture, Part 1

Trending

  • From Zero to Production: Best Practices for Scaling LLMs in the Enterprise
  • Beyond ChatGPT, AI Reasoning 2.0: Engineering AI Models With Human-Like Reasoning
  • How to Practice TDD With Kotlin
  • How to Configure and Customize the Go SDK for Azure Cosmos DB
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Testing, Tools, and Frameworks
  4. Using Jetpack Compose With MVI Architecture

Using Jetpack Compose With MVI Architecture

MVI offers a structured, one-way data flow, aligning with Jetpack Compose's reactive design and ensuring clear state handling, easier debugging, and better scalability.

By 
Farhana Haque user avatar
Farhana Haque
·
Mar. 12, 25 · Analysis
Likes (2)
Comment
Save
Tweet
Share
3.5K Views

Join the DZone community and get the full member experience.

Join For Free

Understanding MVVM and MVI

MVVM (Model-View-ViewModel)

MVVM is one of the most popular architecture patterns in Android development. It helps keep UI logic separate from business logic by using a ViewModel, which acts as a bridge between the View (UI) and the Model (data and logic). The View listens for updates from the ViewModel and updates the UI when needed.

  • Model: Handles data and business logic.
  • View: Displays the UI and passes user actions to the ViewModel.
  • ViewModel: Manages data for the View and responds to user interactions.

MVI (Model-View-Intent)

MVI follows a reactive, unidirectional data flow, meaning UI updates happen in a predictable way from a single source of truth. This makes state management clearer and more structured.

  • Model: Represents the UI's state at any given time.
  • View: Displays the UI and sends user actions (intents) to the system.
  • Intent: Captures user actions or events that lead to state changes.

The key difference is that MVI treats the UI as a single, reactive state, while MVVM allows multiple pieces of data to be observed separately.

Why MVI Works Better Than MVVM in Jetpack Compose

1. Smooth Unidirectional Data Flow

MVI enforces a clear, one-way data flow that aligns perfectly with Jetpack Compose’s reactive nature. Here’s how it works:

  • The View sends user actions (Intents) to the ViewModel.
  • The ViewModel processes these actions and updates the State.
  • The View observes the updated state and re-renders accordingly.

This structured approach keeps the UI in sync with the state, making the app more predictable and easier to debug.

2. A Single Source of Truth

MVI represents the entire UI state in one immutable data structure, preventing inconsistent or fragmented states. In MVVM, multiple LiveData or StateFlow objects often track different UI elements, which can lead to inconsistencies.

By keeping everything in one state object, MVI makes state management clearer and more maintainable.

3. Better State Handling

Jetpack Compose is built around immutable state, and MVI naturally supports this with its reducer-based approach:

  • State updates happen through pure functions that take the current state and an intent, then return a new state.
  • This ensures state changes are predictable and easy to test.

On the other hand, MVVM often relies on mutable state (LiveData, StateFlow), which can introduce side effects and make the code harder to maintain.

Implementing MVI With Jetpack Compose

Let's build a basic messaging app together using Jetpack Compose with MVI architecture.

1. Define the Model

The model represents the state of the UI. In this case, it will hold the list of messages:

Kotlin
 
data class Message(val id: Int, val text: String, val isSentByMe: Boolean)
sealed class ChatState {
    object Loading : ChatState()
    data class Success(val data: List<Message>) : ChatState()
    data class Error(val message: String) : ChatState()
}


2. Define Intent

User actions are represented as intents:

Kotlin
 
sealed class ChatIntent {
    object LoadData : ChatIntent()
    data class SendMessage(val text: String) : ChatIntent()
}


3. Create ViewModel With StateFlow

We use StateFlow to manage state and MutableSharedFlow for user intents:

Kotlin
 
class ChatViewModel : ViewModel() {
    private val _state = MutableStateFlow<ChatState>(ChatState.Loading)
    val state: StateFlow<ChatState> = _state.asStateFlow()
    private val _intentFlow = MutableSharedFlow<ChatIntent>()

    init {
        processIntents()
    }

    fun sendIntent(intent: ChatIntent) {
        viewModelScope.launch { _intentFlow.emit(intent) }
    }

    private fun processIntents() {
        viewModelScope.launch {
            _intentFlow.collect { intent ->
                when (intent) {
                    is ChatIntent.LoadData -> loadData()
                    is ChatIntent.SendMessage -> sendMessage(intent.text)
                }
            }
        }
    }

    fun processIntent(intent: ChatIntent) {
        when (intent) {
            is ChatIntent.SendMessage -> sendMessage(intent.text)
        }
    }

    private fun loadData() {
        viewModelScope.launch {
            _state.value = try {
                val data = listOf(
                    Message(id = 1, text = "hello", isSentByMe = true)
                )
                UiState.Success(data)
            } catch (e: Exception) {
                UiState.Error("Failed to load data")
            }
        }
    }

    private fun sendMessage(text: String) {
        val newMessage = Message(
            id = _state.value.messages.size + 1,
            text = text,
            isSentByMe = true
        )
        _state.update { it.copy(messages = it.messages + newMessage) }
    }
}


4. Compose UI With State Handling

Now, we build the UI in Jetpack Compose:

Kotlin
 
@Composable
fun ChatScreen(viewModel: ChatViewModel) {
  val state by viewModel.state.collectAsState()

  Column(modifier = Modifier.fillMaxSize()) {
    MessageList(messages = state.messages)
    MessageInput { text ->
      viewModel.processIntent(ChatIntent.SendMessage(text))
    }
  }
}

@Composable
fun MessageList(messages: List<Message>) {
  LazyColumn(modifier = Modifier.weight(1f)) {
    items(messages) { message ->
      MessageItem(message = message)
    }
  }
}

@Composable
fun MessageItem(message: Message) {
  Box(
    modifier = Modifier
      .fillMaxWidth()
      .padding(8.dp),
    contentAlignment = if (message.isSentByMe) Alignment.CenterEnd else Alignment.CenterStart
  ) {
    Text(
      text = message.text,
      modifier = Modifier
        .background(if (message.isSentByMe) Color.Blue else Color.Gray, shape = RoundedCornerShape(8.dp))
        .padding(8.dp),
      color = Color.White
    )
  }
}

@Composable
fun MessageInput(onSend: (String) -> Unit) {
  var text by remember { mutableStateOf("") }

  Row(modifier = Modifier.fillMaxWidth()) {
    TextField(
      value = text,
      onValueChange = { text = it },
      modifier = Modifier.weight(1f),
      placeholder = { Text("Type a message") }
    )
    Button(onClick = {
      onSend(text)
      text = ""
    }) {
      Text("Send")
    }
  }
}


5. Trigger Intent From UI

To trigger an intent, update the UI to send an event to the ViewModel:

Kotlin
 
@Composable
fun ChatScreen(viewModel: ChatViewModel) {
  val state by viewModel.state.collectAsState()
  
  LaunchedEffect(Unit) {
    viewModel.sendIntent(UiIntent.LoadData)
  }
  
  Column(modifier = Modifier.fillMaxSize()) {
    MessageList(messages = state.messages)
    MessageInput { text ->
      viewModel.processIntent(ChatIntent.SendMessage(text))
    }
  }
}


Summary

MVVM has been a solid choice for Android development, but MVI provides a more modern and scalable approach, especially when used with Jetpack Compose. Its structured one-way data flow, single source of truth, and focus on immutable state make it a perfect match for Compose’s declarative and reactive design.

By switching to MVI, developers can create apps that are more predictable, easier to maintain, and simpler to test. As Jetpack Compose becomes the standard for Android UI development, MVI is likely to become the preferred architecture for building modern, scalable apps.

Architecture UI Android (robot)

Opinions expressed by DZone contributors are their own.

Related

  • Spotify Backstage: A Unifying Force for Developer Experience
  • Serverless Patterns: Web
  • Endpoint Security Controls: Designing a Secure Endpoint Architecture, Part 2
  • Endpoint Security Controls: Designing a Secure Endpoint Architecture, Part 1

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!