Android开发:是否需为每个Activity创建MVP的Presenter、Model及契约?
Great question—this is something a lot of Android devs grapple with when adopting MVP, especially as apps scale. Let’s break this down clearly:
Do You Need a Contract for Every Activity?
Short answer: Yes, but with flexibility.
The core idea of MVP contracts is to enforce clear boundaries between the View (your Activity/Fragment), Presenter, and Model. Each Activity (or Fragment, since they’re often the primary View layer in MVP) represents a distinct screen or feature set, so a dedicated contract ensures:
- No cross-feature coupling: Each feature’s responsibilities stay isolated, making it easier to modify one without breaking others.
- Better testability: You can mock View/Presenter interfaces easily, no need to rely on concrete Activity classes for unit tests.
- Improved maintainability: New developers can quickly grasp exactly what a screen does just by looking at its contract.
That said, if you have tiny, reusable components (like a simple settings screen with minimal interactions), you might reuse a base contract or simplify it—but as a general rule, one contract per feature screen (Activity/Fragment) is the best practice.
How to Implement MVP for Multiple Activities
Here’s a practical, scalable approach to keep your codebase clean:
1. Use Base Interfaces to Cut Down Boilerplate
Create base interfaces for View, Presenter, and Model to avoid repeating common methods across every contract. For example:
// Base View interface interface BaseView { fun showLoading() fun hideLoading() fun showError(message: String) } // Base Presenter interface interface BasePresenter<V : BaseView> { fun attachView(view: V) fun detachView() }
Then your feature-specific contracts can extend these to add screen-specific methods:
// Contract for a User Profile screen interface UserProfileContract { interface View : BaseView { fun displayUserDetails(user: User) } interface Presenter : BasePresenter<View> { fun loadUserProfile(userId: String) } interface Model { suspend fun fetchUser(userId: String): User } }
2. Organize Packages by Feature, Not Layer
Instead of having monolithic views/, presenters/, models/ folders, structure your code by feature. This makes it trivial to locate all code related to a single screen:
app/src/main/java/com/yourapp/ ├── userprofile/ │ ├── UserProfileActivity.kt │ ├── UserProfileContract.kt │ ├── UserProfilePresenter.kt │ └── UserProfileModel.kt ├── dashboard/ │ ├── DashboardActivity.kt │ ├── DashboardContract.kt │ ├── DashboardPresenter.kt │ └── DashboardModel.kt └── base/ ├── BaseView.kt └── BasePresenter.kt
3. Handle Navigation Without Tying Presenters to Android Framework
Your Presenter shouldn’t directly start Activities (that would break testability by coupling it to Android classes). Instead:
- Define navigation callbacks in the View interface:
interface UserProfileContract { interface View : BaseView { // ... other methods fun navigateToEditProfileScreen(userId: String) } } - Let the Activity (the View implementation) handle the actual navigation with
Intents:class UserProfileActivity : AppCompatActivity(), UserProfileContract.View { // ... override fun navigateToEditProfileScreen(userId: String) { val intent = Intent(this, EditProfileActivity::class.java) intent.putExtra("USER_ID", userId) startActivity(intent) } } - The Presenter simply calls
view?.navigateToEditProfileScreen(userId)when navigation is needed.
4. Reuse Shared Data Logic with Repositories
If multiple Activities need access to the same data (like user session info), create a shared repository class that’s injected into relevant Presenters. This avoids duplicating data-fetching logic across features.
Final Thoughts
Sticking to one contract per feature screen might feel like extra work upfront, but it pays off massively in maintainability and testability as your app grows. Using base interfaces and feature-based packaging will keep your codebase organized and reduce unnecessary repetition.
内容的提问来源于stack exchange,提问作者Ilya Maximencko

