Skip to content

MVP Architecture

Thiago Fernando Rech edited this page Jul 6, 2025 · 1 revision

MVP Architecture - ToneForge

πŸ—οΈ Overview

ToneForge has been completely refactored to implement the MVP (Model-View-Presenter) pattern, resulting in a more modular, testable, and maintainable architecture.

🎯 Problem Solved

Before Refactoring:

  • ❌ Monolithic Architecture - Overloaded MainActivity
  • ❌ Giant Fragments - Up to 2,590 lines in a single file
  • ❌ Strong Coupling - Business logic mixed with UI
  • ❌ Hard to Test - Direct dependencies between components
  • ❌ Complex Maintenance - Changes affected multiple modules

After Refactoring:

  • βœ… MVP Architecture - Clear separation of responsibilities
  • βœ… Focused Fragments - 40% code reduction
  • βœ… Low Coupling - Well-defined interfaces
  • βœ… Easily Testable - Isolated presenters
  • βœ… Simple Maintenance - Localized changes

πŸ“± MVP Architecture Structure

Architecture Layers:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        VIEW LAYER                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚  β”‚   HomeFragment  β”‚  β”‚ EffectsFragment β”‚  β”‚  LooperFragment β”‚β”‚
β”‚  β”‚   (UI Logic)    β”‚  β”‚   (UI Logic)    β”‚  β”‚   (UI Logic)    β”‚β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
                                β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     PRESENTER LAYER                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚  β”‚  HomePresenter  β”‚  β”‚ EffectsPresenterβ”‚  β”‚ LooperPresenter β”‚β”‚
β”‚  β”‚ (Business Logic)β”‚  β”‚(Business Logic) β”‚  β”‚(Business Logic) β”‚β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
                                β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      MODEL LAYER                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚  β”‚ AudioRepository β”‚  β”‚   AudioState    β”‚  β”‚ EffectParametersβ”‚β”‚
β”‚  β”‚  (Data Access)  β”‚  β”‚  (Data Model)   β”‚  β”‚  (Data Model)   β”‚β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“‚ New Package Organization

com.thiagofernendorech.toneforge/
β”œβ”€β”€ ui/
β”‚   β”œβ”€β”€ base/
β”‚   β”‚   β”œβ”€β”€ BaseView.java              # Common interface for views
β”‚   β”‚   β”œβ”€β”€ BasePresenter.java         # Base class for presenters
β”‚   β”‚   └── BaseFragment.java          # Base fragment with MVP
β”‚   β”œβ”€β”€ navigation/
β”‚   β”‚   └── NavigationController.java  # Decoupled navigation
β”‚   └── fragments/
β”‚       β”œβ”€β”€ home/
β”‚       β”‚   β”œβ”€β”€ HomeContract.java      # MVP interface
β”‚       β”‚   β”œβ”€β”€ HomePresenter.java     # Business logic
β”‚       β”‚   └── HomeFragmentRefactored.java # Pure UI
β”‚       β”œβ”€β”€ effects/
β”‚       β”‚   β”œβ”€β”€ EffectsContract.java
β”‚       β”‚   β”œβ”€β”€ EffectsPresenter.java
β”‚       β”‚   └── EffectsFragmentRefactored.java
β”‚       └── [7 other fragments...]
β”œβ”€β”€ data/
β”‚   └── repository/
β”‚       └── AudioRepository.java       # Unified data layer
└── domain/
    └── models/
        β”œβ”€β”€ AudioState.java            # Audio state
        └── EffectParameters.java      # Effect parameters

πŸ”„ Implemented MVP Pattern

1. Contracts (Interfaces)

Defines the contract between View and Presenter:

public interface HomeContract {
    interface View extends BaseView {
        void navigateToEffects();
        void navigateToLooper();
        void updateConnectionStatus(boolean connected);
    }
    
    interface Presenter extends BasePresenter<View> {
        void onEffectsClicked();
        void onLooperClicked();
        void onWifiClicked();
    }
}

2. Presenters (Business Logic)

Manages business logic and coordinates View and Model:

public class HomePresenter implements HomeContract.Presenter {
    private WeakReference<HomeContract.View> viewRef;
    private AudioRepository audioRepository;
    private NavigationController navigation;
    
    @Override
    public void onEffectsClicked() {
        ifViewAttached(view -> {
            navigation.navigateToEffects();
        });
    }
    
    @Override
    public void onLooperClicked() {
        ifViewAttached(view -> {
            navigation.navigateToLooper();
        });
    }
}

3. Views (Interface)

Only UI logic, no business rules:

public class HomeFragmentRefactored extends BaseFragment<HomeContract.Presenter> 
    implements HomeContract.View {
    
    @Override
    protected HomeContract.Presenter createPresenter() {
        return new HomePresenter(
            requireContext(),
            NavigationController.getInstance(),
            AudioRepository.getInstance(requireContext())
        );
    }
    
    @Override
    public void navigateToEffects() {
        // Navigation will be handled by NavigationController
    }
}

πŸ”— Decoupled Navigation System

NavigationController

Centralizes all navigation between fragments:

public class NavigationController {
    private static NavigationController instance;
    private WeakReference<MainActivity> mainActivityRef;
    
    public void navigateToHome() {
        executeNavigation(activity -> {
            activity.loadFragment(new HomeFragmentRefactored());
            activity.updateHeaderTitle("ToneForge");
        });
    }
    
    public void navigateToEffects() {
        executeNavigation(activity -> {
            activity.loadFragment(new EffectsFragmentRefactored());
            activity.updateHeaderTitle("Effects");
        });
    }
}

πŸ“Š Repository Pattern

AudioRepository

Unified interface for all audio operations:

public class AudioRepository {
    private static AudioRepository instance;
    private AudioEngine audioEngine;
    private AudioStateManager stateManager;
    private PresetManager presetManager;
    
    // Pipeline Operations
    public boolean startAudioPipeline() {
        return audioEngine.startPipeline();
    }
    
    public void pauseAudioPipeline() {
        audioEngine.pausePipeline();
    }
    
    // Effects Operations
    public void applyEffectParameters(EffectParameters params) {
        audioEngine.applyEffectParameters(params);
    }
    
    // Preset Operations
    public void savePreset(String name) {
        presetManager.savePreset(name);
    }
    
    public AudioState getCurrentAudioState() {
        return stateManager.getCurrentState();
    }
}

πŸ“‹ Refactored Fragments

Refactoring Status:

  • βœ… HomeFragment β†’ HomeFragmentRefactored
  • βœ… EffectsFragment β†’ EffectsFragmentRefactored
  • βœ… LooperFragment β†’ LooperFragmentRefactored
  • βœ… TunerFragment β†’ TunerFragmentRefactored
  • βœ… MetronomeFragment β†’ MetronomeFragmentRefactored
  • βœ… RecorderFragment β†’ RecorderFragmentRefactored
  • βœ… SettingsFragment β†’ SettingsFragmentRefactored
  • βœ… LoopLibraryFragment β†’ LoopLibraryFragmentRefactored
  • βœ… LearningFragment β†’ LearningFragmentRefactored

Improvement Metrics:

  • EffectsFragment: 2,590 β†’ 926 lines (-64%)
  • LooperFragment: 1,433 β†’ 850 lines (-40%)
  • Total: ~5,000 β†’ ~3,000 lines (-40%)

πŸ§ͺ Testability

Before (Hard to Test):

// Fragment with mixed logic
public class OldHomeFragment extends Fragment {
    private void onEffectsClicked() {
        // Business logic mixed with UI
        if (AudioEngine.getInstance().isRunning()) {
            FragmentManager fm = getParentFragmentManager();
            FragmentTransaction transaction = fm.beginTransaction();
            transaction.replace(R.id.fragment_container, new EffectsFragment());
            transaction.commit();
        }
    }
}

After (Easy to Test):

// Isolated and testable presenter
@Test
public void testHomePresenter_onEffectsClicked() {
    // Arrange
    HomePresenter presenter = new HomePresenter(context, navigation, repository);
    
    // Act
    presenter.onEffectsClicked();
    
    // Assert
    verify(navigation).navigateToEffects();
}

🎯 Benefits of the New Architecture

1. Separation of Responsibilities

  • View: Only UI and events
  • Presenter: Business logic
  • Model: Data and state

2. Ease of Testing

  • Presenters can be tested in isolation
  • Mocks and Stubs easy to implement
  • Test coverage significantly improved

3. Maintainability

  • Cleaner code and organized
  • Localized changes in each layer
  • Easy addition of new features

4. Reusability

  • Base components reusable
  • Consistent patterns throughout the project
  • Well-defined interfaces

5. Scalability

  • Easy addition of new fragments
  • Architecture prepared for growth
  • Established patterns for development

πŸ”§ How to Use

Creating a New Fragment:

  1. Create the Contract:
public interface MyContract {
    interface View extends BaseView {
        void updateData(String data);
    }
    
    interface Presenter extends BasePresenter<View> {
        void loadData();
    }
}
  1. Implement the Presenter:
public class MyPresenter implements MyContract.Presenter {
    // Business logic implementation
}
  1. Create the Fragment:
public class MyFragment extends BaseFragment<MyContract.Presenter> 
    implements MyContract.View {
    // UI implementation
}

πŸ“ˆ Comparison: Before vs After

Aspect Before After
Architecture Monolithic MVP Modular
Lines of Code 5,000+ 3,000+
Testability Difficult Easy
Maintainability Low High
Coupling High Low
Reusability None High
Scalability Limited Excellent

πŸŽ‰ Conclusion

The MVP refactoring completely transformed ToneForge, creating a solid foundation for future development. The new architecture offers:

  • Cleaner and organized code
  • Ease of testing and maintenance
  • Scalability for new features
  • Consistent patterns throughout the project
  • Better experience for developers

For technical implementation details, see Development and Testing Strategy.

ToneForge Wiki

πŸš€ Getting Started

πŸŽ›οΈ Effects & Features

🧠 Advanced Features πŸ†•

🎡 Learning & Practice

πŸ”§ Technical


Quick Links:

Clone this wiki locally