Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Sunshine And March Vibes (2025 Wallpapers Edition)

      May 31, 2025

      The Case For Minimal WordPress Setups: A Contrarian View On Theme Frameworks

      May 31, 2025

      How To Fix Largest Contentful Paint Issues With Subpart Analysis

      May 31, 2025

      How To Prevent WordPress SQL Injection Attacks

      May 31, 2025

      Windows 11 version 25H2: Everything you need to know about Microsoft’s next OS release

      May 31, 2025

      Elden Ring Nightreign already has a duos Seamless Co-op mod from the creator of the beloved original, and it’ll be “expanded on in the future”

      May 31, 2025

      I love Elden Ring Nightreign’s weirdest boss — he bargains with you, heals you, and throws tantrums if you ruin his meditation

      May 31, 2025

      How to install SteamOS on ROG Ally and Legion Go Windows gaming handhelds

      May 31, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Oracle Fusion new Product Management Landing Page and AI (25B)

      May 31, 2025
      Recent

      Oracle Fusion new Product Management Landing Page and AI (25B)

      May 31, 2025

      Filament Is Now Running Natively on Mobile

      May 31, 2025

      How Remix is shaking things up

      May 30, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Windows 11 version 25H2: Everything you need to know about Microsoft’s next OS release

      May 31, 2025
      Recent

      Windows 11 version 25H2: Everything you need to know about Microsoft’s next OS release

      May 31, 2025

      Elden Ring Nightreign already has a duos Seamless Co-op mod from the creator of the beloved original, and it’ll be “expanded on in the future”

      May 31, 2025

      I love Elden Ring Nightreign’s weirdest boss — he bargains with you, heals you, and throws tantrums if you ruin his meditation

      May 31, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»Migrating from MVP to Jetpack Compose: A Step-by-Step Guide for Android Developers

    Migrating from MVP to Jetpack Compose: A Step-by-Step Guide for Android Developers

    February 3, 2025

    Migrating an Android App from MVP to Jetpack Compose: A Step-by-Step Guide

    Jetpack Compose is Android’s modern toolkit for building native UI. It simplifies and accelerates UI development by using a declarative approach, which is a significant shift from the traditional imperative XML-based layouts. If you have an existing Android app written in Kotlin using the MVP (Model-View-Presenter) pattern with XML layouts, fragments, and activities, migrating to Jetpack Compose can bring numerous benefits, including improved developer productivity, reduced boilerplate code, and a more modern UI architecture.

    In this article, we’ll walk through the steps to migrate an Android app from MVP with XML layouts to Jetpack Compose. We’ll use a basic News App to explain in detail how to migrate all layers of the app. The app has two screens:

    1. A News List Fragment to display a list of news items.
    2. A News Detail Fragment to show the details of a selected news item.

    We’ll start by showing the original MVP implementation, including the Presenters, and then migrate the app to Jetpack Compose step by step. We’ll also add error handling, loading states, and use Kotlin Flow instead of LiveData for a more modern and reactive approach.

    1. Understand the Key Differences

    Before diving into the migration, it’s essential to understand the key differences between the two approaches:

    • Imperative vs. Declarative UI: XML layouts are imperative, meaning you define the UI structure and then manipulate it programmatically. Jetpack Compose is declarative, meaning you describe what the UI should look like for any given state, and Compose handles the rendering.
    • MVP vs. Compose Architecture: MVP separates the UI logic into Presenters and Views. Jetpack Compose encourages a more reactive and state-driven architecture, often using ViewModel and State Hoisting.
    • Fragments and Activities: In traditional Android development, Fragments and Activities are used to manage UI components. In Jetpack Compose, you can replace most Fragments and Activities with composable functions.

    2. Plan the Migration

    Migrating an entire app to Jetpack Compose can be a significant undertaking. Here’s a suggested approach:

    1. Start Small: Begin by migrating a single screen or component to Jetpack Compose. This will help you understand the process and identify potential challenges.
    2. Incremental Migration: Jetpack Compose is designed to work alongside traditional Views, so you can migrate your app incrementally. Use ComposeView in XML layouts or AndroidView in Compose to bridge the gap.
    3. Refactor MVP to MVVM: Jetpack Compose works well with the MVVM (Model-View-ViewModel) pattern. Consider refactoring your Presenters into ViewModels.
    4. Replace Fragments with Composable Functions: Fragments can be replaced with composable functions, simplifying navigation and UI management.
    5. Add Error Handling and Loading States: Ensure your app handles errors gracefully and displays loading states during data fetching.
    6. Use Kotlin Flow: Replace LiveData with Kotlin Flow for a more modern and reactive approach.

    3. Set Up Jetpack Compose

    Before starting the migration, ensure your project is set up for Jetpack Compose:

    1. Update Gradle Dependencies:
      Add the necessary Compose dependencies to your build.gradle file:
      android {
          ...
          buildFeatures {
              compose true
          }
          composeOptions {
              kotlinCompilerExtensionVersion '1.5.3'
          }
      }
      
      dependencies {
          implementation 'androidx.activity:activity-compose:1.8.0'
          implementation 'androidx.compose.ui:ui:1.5.4'
          implementation 'androidx.compose.material:material:1.5.4'
          implementation 'androidx.compose.ui:ui-tooling-preview:1.5.4'
          implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
          implementation 'androidx.navigation:navigation-compose:2.7.4' // For navigation
          implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' // For Flow
          implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3' // For Flow
      }
    2. Enable Compose in Your Project:
      Ensure your project is using the correct Kotlin and Android Gradle plugin versions.

    4. Original MVP Implementation

    a. News List Fragment and Presenter

    The NewsListFragment displays a list of news items. The NewsListPresenter fetches the data and updates the view.

    NewsListFragment.kt

    class NewsListFragment : Fragment(), NewsListView {
    
        private lateinit var presenter: NewsListPresenter
        private lateinit var adapter: NewsListAdapter
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            val view = inflater.inflate(R.layout.fragment_news_list, container, false)
            val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerView)
            adapter = NewsListAdapter { newsItem -> presenter.onNewsItemClicked(newsItem) }
            recyclerView.adapter = adapter
            recyclerView.layoutManager = LinearLayoutManager(context)
            presenter = NewsListPresenter(this)
            presenter.loadNews()
            return view
        }
    
        override fun showNews(news: List<NewsItem>) {
            adapter.submitList(news)
        }
    
        override fun showLoading() {
            // Show loading indicator
        }
    
        override fun showError(error: String) {
            // Show error message
        }
    }

    NewsListPresenter.kt

    class NewsListPresenter(private val view: NewsListView) {
    
        fun loadNews() {
            view.showLoading()
            // Simulate fetching news from a data source (e.g., API or local database)
            try {
                val newsList = listOf(
                    NewsItem(id = 1, title = "News 1", summary = "Summary 1"),
                    NewsItem(id = 2, title = "News 2", summary = "Summary 2")
                )
                view.showNews(newsList)
            } catch (e: Exception) {
                view.showError(e.message ?: "An error occurred")
            }
        }
    
        fun onNewsItemClicked(newsItem: NewsItem) {
            // Navigate to the news detail screen
            val intent = Intent(context, NewsDetailActivity::class.java).apply {
                putExtra("newsId", newsItem.id)
            }
            startActivity(intent)
        }
    }

    NewsListView.kt

    interface NewsListView {
        fun showNews(news: List<NewsItem>)
        fun showLoading()
        fun showError(error: String)
    }

    b. News Detail Fragment and Presenter

    The NewsDetailFragment displays the details of a selected news item. The NewsDetailPresenter fetches the details and updates the view.

    NewsDetailFragment.kt

    class NewsDetailFragment : Fragment(), NewsDetailView {
    
        private lateinit var presenter: NewsDetailPresenter
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            val view = inflater.inflate(R.layout.fragment_news_detail, container, false)
            presenter = NewsDetailPresenter(this)
            val newsId = arguments?.getInt("newsId") ?: 0
            presenter.loadNewsDetail(newsId)
            return view
        }
    
        override fun showNewsDetail(newsItem: NewsItem) {
            view?.findViewById<TextView>(R.id.title)?.text = newsItem.title
            view?.findViewById<TextView>(R.id.summary)?.text = newsItem.summary
        }
    
        override fun showLoading() {
            // Show loading indicator
        }
    
        override fun showError(error: String) {
            // Show error message
        }
    }

    NewsDetailPresenter.kt

    class NewsDetailPresenter(private val view: NewsDetailView) {
    
        fun loadNewsDetail(newsId: Int) {
            view.showLoading()
            // Simulate fetching news detail from a data source (e.g., API or local database)
            try {
                val newsItem = NewsItem(id = newsId, title = "News $newsId", summary = "Summary $newsId")
                view.showNewsDetail(newsItem)
            } catch (e: Exception) {
                view.showError(e.message ?: "An error occurred")
            }
        }
    }

    NewsDetailView.kt

    interface NewsDetailView {
        fun showNewsDetail(newsItem: NewsItem)
        fun showLoading()
        fun showError(error: String)
    }

    5. Migrate to Jetpack Compose

    a. Migrate the News List Fragment

    Replace the NewsListFragment with a composable function. The NewsListPresenter will be refactored into a NewsListViewModel.

    NewsListScreen.kt

    @Composable
    fun NewsListScreen(viewModel: NewsListViewModel, onItemClick: (NewsItem) -> Unit) {
        val newsState by viewModel.newsState.collectAsState()
    
        when (newsState) {
            is NewsState.Loading -> {
                // Show loading indicator
                CircularProgressIndicator()
            }
            is NewsState.Success -> {
                val news = (newsState as NewsState.Success).news
                LazyColumn {
                    items(news) { newsItem ->
                        NewsListItem(newsItem = newsItem, onClick = { onItemClick(newsItem) })
                    }
                }
            }
            is NewsState.Error -> {
                // Show error message
                val error = (newsState as NewsState.Error).error
                Text(text = error, color = Color.Red)
            }
        }
    }
    
    @Composable
    fun NewsListItem(newsItem: NewsItem, onClick: () -> Unit) {
        Card(
            modifier = Modifier
                .fillMaxWidth()
                .padding(8.dp)
                .clickable { onClick() }
        ) {
            Column(modifier = Modifier.padding(16.dp)) {
                Text(text = newsItem.title, style = MaterialTheme.typography.h6)
                Text(text = newsItem.summary, style = MaterialTheme.typography.body1)
            }
        }
    }

    NewsListViewModel.kt

    class NewsListViewModel : ViewModel() {
    
        private val _newsState = MutableStateFlow<NewsState>(NewsState.Loading)
        val newsState: StateFlow<NewsState> get() = _newsState
    
        init {
            loadNews()
        }
    
        private fun loadNews() {
            viewModelScope.launch {
                _newsState.value = NewsState.Loading
                try {
                    // Simulate fetching news from a data source (e.g., API or local database)
                    val newsList = listOf(
                        NewsItem(id = 1, title = "News 1", summary = "Summary 1"),
                        NewsItem(id = 2, title = "News 2", summary = "Summary 2")
                    )
                    _newsState.value = NewsState.Success(newsList)
                } catch (e: Exception) {
                    _newsState.value = NewsState.Error(e.message ?: "An error occurred")
                }
            }
        }
    }
    
    sealed class NewsState {
        object Loading : NewsState()
        data class Success(val news: List<NewsItem>) : NewsState()
        data class Error(val error: String) : NewsState()
    }

    b. Migrate the News Detail Fragment

    Replace the NewsDetailFragment with a composable function. The NewsDetailPresenter will be refactored into a NewsDetailViewModel.

    NewsDetailScreen.kt

    @Composable
    fun NewsDetailScreen(viewModel: NewsDetailViewModel) {
        val newsState by viewModel.newsState.collectAsState()
    
        when (newsState) {
            is NewsState.Loading -> {
                // Show loading indicator
                CircularProgressIndicator()
            }
            is NewsState.Success -> {
                val newsItem = (newsState as NewsState.Success).news
                Column(modifier = Modifier.padding(16.dp)) {
                    Text(text = newsItem.title, style = MaterialTheme.typography.h4)
                    Text(text = newsItem.summary, style = MaterialTheme.typography.body1)
                }
            }
            is NewsState.Error -> {
                // Show error message
                val error = (newsState as NewsState.Error).error
                Text(text = error, color = Color.Red)
            }
        }
    }

    NewsDetailViewModel.kt

    class NewsDetailViewModel : ViewModel() {
    
        private val _newsState = MutableStateFlow<NewsState>(NewsState.Loading)
        val newsState: StateFlow<NewsState> get() = _newsState
    
        fun loadNewsDetail(newsId: Int) {
            viewModelScope.launch {
                _newsState.value = NewsState.Loading
                try {
                    // Simulate fetching news detail from a data source (e.g., API or local database)
                    val newsItem = NewsItem(id = newsId, title = "News $newsId", summary = "Summary $newsId")
                    _newsState.value = NewsState.Success(newsItem)
                } catch (e: Exception) {
                    _newsState.value = NewsState.Error(e.message ?: "An error occurred")
                }
            }
        }
    }
    
    sealed class NewsState {
        object Loading : NewsState()
        data class Success(val news: NewsItem) : NewsState()
        data class Error(val error: String) : NewsState()
    }

    6. Set Up Navigation

    Replace Fragment-based navigation with Compose navigation:

    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                NewsApp()
            }
        }
    }
    
    @Composable
    fun NewsApp() {
        val navController = rememberNavController()
        NavHost(navController = navController, startDestination = "newsList") {
            composable("newsList") {
                val viewModel: NewsListViewModel = viewModel()
                NewsListScreen(viewModel = viewModel) { newsItem ->
                    navController.navigate("newsDetail/${newsItem.id}")
                }
            }
            composable("newsDetail/{newsId}") { backStackEntry ->
                val viewModel: NewsDetailViewModel = viewModel()
                val newsId = backStackEntry.arguments?.getString("newsId")?.toIntOrNull() ?: 0
                viewModel.loadNewsDetail(newsId)
                NewsDetailScreen(viewModel = viewModel)
            }
        }
    }

    7. Test and Iterate

    After migrating the screens, thoroughly test the app to ensure it behaves as expected. Use Compose’s preview functionality to visualize your UI:

    @Preview(showBackground = true)
    @Composable
    fun PreviewNewsListScreen() {
        NewsListScreen(viewModel = NewsListViewModel(), onItemClick = {})
    }
    
    @Preview(showBackground = true)
    @Composable
    fun PreviewNewsDetailScreen() {
        NewsDetailScreen(viewModel = NewsDetailViewModel())
    }

    8. Gradually Migrate the Entire App

    Once you’re comfortable with the migration process, continue migrating the rest of your app incrementally. Use ComposeView and AndroidView to integrate Compose with existing XML

    Source: Read More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleLeveraging Data Cloud data in your Agentforce Agent
    Next Article Perficient Recognized as Notable Manufacturing Industry Consultancy

    Related Posts

    Artificial Intelligence

    Markus Buehler receives 2025 Washington Award

    May 31, 2025
    Artificial Intelligence

    LWiAI Podcast #201 – GPT 4.5, Sonnet 3.7, Grok 3, Phi 4

    May 31, 2025
    Leave A Reply Cancel Reply

    Continue Reading

    Key Trends and Forecasts Influencing the GenAI Market in 2024

    Development

    Sean Plankey Nominated as Next CISA Director

    Development

    SenseTime Unveiled SenseNova 5.5: Setting a New Benchmark to Rival GPT-4o in 5 Out of 8 Key Metrics

    Development

    Labor Day deals are here: Everything to know, plus deals to shop this weekend

    Development

    Highlights

    Machine Learning

    Biophysical Brain Models Get a 2000× Speed Boost: Researchers from NUS, UPenn, and UPF Introduce DELSSOME to Replace Numerical Integration with Deep Learning Without Sacrificing Accuracy

    April 16, 2025

    Biophysical modeling serves as a valuable tool for understanding brain function by linking neural dynamics…

    Jobs To Be Done Framework: Understanding the User’s Jobs

    April 1, 2025

    Assembly AI Introduces Universal-2: The Next Leap in Speech-to-Text Technology

    November 9, 2024

    Learn Async Programming in TypeScript: Promises, Async/Await, and Callbacks

    January 31, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.