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

      Sunshine And March Vibes (2025 Wallpapers Edition)

      June 2, 2025

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

      June 2, 2025

      How To Fix Largest Contentful Paint Issues With Subpart Analysis

      June 2, 2025

      How To Prevent WordPress SQL Injection Attacks

      June 2, 2025

      The Alters: Release date, mechanics, and everything else you need to know

      June 2, 2025

      I’ve fallen hard for Starsand Island, a promising anime-style life sim bringing Ghibli vibes to Xbox and PC later this year

      June 2, 2025

      This new official Xbox 4TB storage card costs almost as much as the Xbox SeriesXitself

      June 2, 2025

      I may have found the ultimate monitor for conferencing and productivity, but it has a few weaknesses

      June 2, 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

      May report 2025

      June 2, 2025
      Recent

      May report 2025

      June 2, 2025

      Write more reliable JavaScript with optional chaining

      June 2, 2025

      Deploying a Scalable Next.js App on Vercel – A Step-by-Step Guide

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

      The Alters: Release date, mechanics, and everything else you need to know

      June 2, 2025
      Recent

      The Alters: Release date, mechanics, and everything else you need to know

      June 2, 2025

      I’ve fallen hard for Starsand Island, a promising anime-style life sim bringing Ghibli vibes to Xbox and PC later this year

      June 2, 2025

      This new official Xbox 4TB storage card costs almost as much as the Xbox SeriesXitself

      June 2, 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

    Security

    ⚡ Weekly Recap: APT Intrusions, AI Malware, Zero-Click Exploits, Browser Hijacks and More

    June 2, 2025
    Security

    Qualcomm fixes three Adreno GPU zero-days exploited in attacks

    June 2, 2025
    Leave A Reply Cancel Reply

    Continue Reading

    Sellafield Nuclear Site Issues Apology Over Series of Cybersecurity Failings

    Development

    CVE-2025-3845 – Markparticle WebServer Buffer Overflow Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    Microsoft confirms multiple issues in Windows 11’s 2024 security updates

    Development

    CVE-2025-4222 – WordPress Database Toolset Sensitive Information Exposure

    Common Vulnerabilities and Exposures (CVEs)

    Highlights

    How to Implement Reliable WebSocket Reconnection Logic with Ease

    December 31, 2024

    Comments Source: Read More 

    Maximizing ROI with Experience Cloud: Best Practices for Analytics and Reporting

    January 21, 2025

    This 3-in-1 wireless charger should be on every Apple user’s wishlist – and it’s super sleek

    February 12, 2025

    Creating a Composable AI Roadmap for Sitecore

    April 17, 2024
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

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