Refactored GameActivity to make use of the newly introduced GameManager to centralize the Game logic into one class and shrink the GameActivity to the relevant parts.
9.6 KiB
GameActivity Refactoring Summary
Overview
Successfully refactored the GameActivity to extract all business logic into a new singleton class called GameManager. This refactoring significantly improves code organization, maintainability, and testability.
What Was Changed
1. Created New GameManager Singleton Class
Location: app/src/main/java/com/aldo/apps/ochecompanion/game/GameManager.java
The GameManager class now handles:
- Match Initialization & Loading: Automatically loads ongoing matches from the database or creates new ones
- Game State Management: Tracks all player states, scores, current turn, darts thrown, etc.
- Game Rules & Logic: Implements bust detection, double-out enforcement, win conditions
- Database Operations: All save/load operations for match progress and statistics
- Statistics Tracking: Player stats, double attempts, dart hit distributions
- Match Persistence: Replaces
onSaveInstanceState- the singleton persists across configuration changes
2. Defined GameStateCallback Interface
The callback interface enables clean separation between business logic and UI:
public interface GameStateCallback {
void onGameStateChanged(); // General UI refresh needed
void onTurnIndicatorsChanged(); // Update dart pills display
void onMultiplierChanged(int m); // Update multiplier buttons
void onBust(); // Bust animation/sound
void onPlayerWin(PlayerState w, int checkout); // Win celebration
void onOneEightyScored(); // 180 celebration
void onResetVisuals(); // Clear bust/error visuals
}
3. Refactored GameActivity
Before: 1298 lines
After: 680 lines
Reduction: 47.6% fewer lines!
What Was Removed:
- ❌ All game state variables (
mPlayerStates,mActivePlayerIndex,mMultiplier, etc.) - ❌
onSaveInstanceState()andonRestoreInstanceState()methods - ❌
setupGame()method - ❌
loadMatchProgress()method - ❌
saveMatchProgress()method - ❌
saveCompletedMatch()method - ❌
updatePlayerStats()methods - ❌
trackDoubleAttempt()method - ❌
incrementMatchesPlayed()method - ❌
recordTurnHitsToStatistics()method - ❌ Inner
X01Stateclass - ❌ Inner
DartHitclass - ❌ Complex match loading logic in
onCreate()
What Remains (UI Only):
- ✅ UI component initialization and references
- ✅ View setup and event listeners
- ✅ Visual effects (animations, vibrations, sounds)
- ✅ UI update methods that read from
GameManager - ✅ Callback method implementations
- ✅ Checkout suggestion display
- ✅ Turn indicator (dart pills) updates
4. Key Architectural Improvements
Before:
// GameActivity handled everything
public class GameActivity extends BaseActivity {
private int mActivePlayerIndex;
private List<X01State> mPlayerStates;
private List<Integer> mCurrentTurnDarts;
// ... dozens more state variables
@Override
protected void onCreate(Bundle savedInstanceState) {
// 100+ lines of complex loading logic
if (savedInstanceState == null) {
new Thread(() -> {
// Load players
// Check if match exists
// Load progress
// Create new match
// etc...
}).start();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// Manually save all game state
outState.putInt("activePlayerIndex", ...);
outState.putIntArray("currentTurnDarts", ...);
// ... many more lines
}
}
After:
// GameActivity is now just a UI controller
public class GameActivity extends BaseActivity
implements GameManager.GameStateCallback {
private GameManager mGameManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
// Simple initialization
mGameManager = GameManager.getInstance(this);
mGameManager.setCallback(this);
// One line to initialize/load match
mGameManager.initializeMatch(matchId, startingScore, () -> {
runOnUiThread(() -> updateUI());
});
}
// No onSaveInstanceState needed!
// GameManager singleton persists across config changes
@Override
public void onGameStateChanged() {
updateUI(); // Just update the display
}
}
Benefits of This Refactoring
1. Separation of Concerns
- GameManager: Pure business logic, no Android dependencies beyond Context
- GameActivity: Pure UI controller, no game logic
2. Easier Testing
- Business logic can now be unit tested independently
- Mock the callback interface to test GameManager
- Test UI separately without complex game state setup
3. No More Configuration Change Issues
- Singleton pattern means state survives screen rotations automatically
- No need for
onSaveInstanceState/onRestoreInstanceState - Match progress is always saved to database, never lost
4. Improved Maintainability
- Clear boundaries between layers
- Single Responsibility Principle enforced
- Easier to find and fix bugs
- New features can be added to GameManager without touching UI
5. Better Code Reusability
- GameManager can be used from other Activities/Fragments
- Game logic is centralized in one place
- Future features (e.g., match history viewer) can read from GameManager
6. Cleaner Data Flow
User Input → GameActivity → GameManager.onNumberTap()
↓
[Process Game Logic]
↓
GameStateCallback.onGameStateChanged()
↓
GameActivity.updateUI()
API Overview
GameManager Public Methods
Initialization
GameManager.getInstance(Context) // Get singleton instance
void setCallback(GameStateCallback) // Register UI callback
void initializeMatch(int matchId, int startScore, Runnable onComplete)
void resetGame() // Clear state for new match
Game Actions
void onNumberTap(int baseValue) // Process dart throw
void submitTurn() // End turn, advance player
void undoLastDart() // Remove last dart
void setMultiplier(int multiplier) // Set 1x/2x/3x
State Getters
PlayerState getActivePlayer() // Current player
List<PlayerState> getPlayerStates() // All players
List<Integer> getCurrentTurnDarts() // Darts in current turn
int getCurrentTarget() // Score after current darts
int getDartsRemainingInTurn() // 0-3 darts left
boolean isTurnOver() // Turn complete?
boolean isBustedTurn() // Current turn bust?
boolean isMatchCompleted() // Match finished?
Migration Guide (For Future Reference)
If you need to add new game features:
-
Add business logic to GameManager
- Add new methods to handle the logic
- Update game state
- Call appropriate callback methods
-
Add UI response in GameActivity
- Implement any new callback methods
- Update UI based on GameManager getters
-
Example: Adding "Undo Turn" Feature
// In GameManager: public void undoTurn() { if (turnHistory.isEmpty()) return; // Restore previous state notifyGameStateChanged(); } // In GameActivity: findViewById(R.id.btnUndoTurn) .setOnClickListener(v -> mGameManager.undoTurn());
Files Changed
Created:
- ✨
app/src/main/java/com/aldo/apps/ochecompanion/game/GameManager.java(665 lines)
Modified:
- 🔧
app/src/main/java/com/aldo/apps/ochecompanion/GameActivity.java(1298 → 680 lines, -47.6%)
Total Impact:
- Lines of Code: Net addition of 47 lines (665 new - 618 removed)
- Maintainability: Significantly improved
- Testability: Dramatically improved
- Architecture: Clean separation of concerns achieved
Testing Recommendations
Unit Tests for GameManager
- Test match initialization with/without existing data
- Test dart scoring logic (bust, double-out, valid throws)
- Test turn submission and player rotation
- Test win condition detection
- Test statistics tracking
- Test state persistence
Integration Tests for GameActivity
- Test UI updates on game state changes
- Test callback invocations
- Test animations and visual feedback
- Test multiplier UI updates
- Test checkout suggestions
Manual Testing Checklist
- Start new match - verify it creates in database
- Resume existing match - verify state restored
- Rotate screen - verify no state loss
- Throw darts - verify UI updates
- Hit bust - verify visual feedback
- Score 180 - verify celebration
- Win match - verify win screen
- Check statistics - verify accurate tracking
- Use undo - verify state restored correctly
Conclusion
This refactoring successfully decouples business logic from UI presentation in the GameActivity. The new GameManager singleton provides a clean, testable, and maintainable architecture that follows SOLID principles and Android best practices. The 47.6% reduction in GameActivity size demonstrates the effectiveness of this separation of concerns.
The singleton pattern eliminates the need for onSaveInstanceState while maintaining state across configuration changes. All match persistence is now handled transparently by GameManager through database operations, making the code more robust and reliable.