# Architecture Documentation ## Overview My ActivityPub follows the **MVVM (Model-View-ViewModel)** architecture pattern combined with **Repository pattern** for clean separation of concerns and testability. ## Architecture Layers ``` ┌─────────────────────────────────────────────────┐ │ UI Layer (View) │ │ Jetpack Compose Composables │ └──────────────────┬──────────────────────────────┘ │ observes StateFlow ↓ ┌─────────────────────────────────────────────────┐ │ ViewModel Layer │ │ Business Logic & State Management │ └──────────────────┬──────────────────────────────┘ │ calls methods ↓ ┌─────────────────────────────────────────────────┐ │ Repository Layer │ │ Data Operations Abstraction │ └──────────────────┬──────────────────────────────┘ │ makes API calls ↓ ┌─────────────────────────────────────────────────┐ │ Data Source Layer │ │ API Service (Retrofit) │ └──────────────────┬──────────────────────────────┘ │ HTTP requests ↓ ┌─────────────────────────────────────────────────┐ │ Remote Server │ │ Mastodon/ActivityPub API │ └─────────────────────────────────────────────────┘ ``` ## Layer Details ### 1. UI Layer (View) **Location**: `ui/components/`, `MainActivity.kt` **Responsibilities**: - Display data to the user - Capture user interactions - Observe ViewModel state - Render UI using Jetpack Compose **Key Components**: #### MainActivity.kt The entry point of the application. Sets up the Compose UI, dependency injection (manual), and hosts the main composable. ```kotlin @Composable fun MyActivityPubApp() { // Setup dependencies val repository = MastodonRepository(apiService) val viewModel: TimelineViewModel = viewModel { TimelineViewModel(repository) } // Observe state val uiState by viewModel.uiState.collectAsState() // Render UI based on state when (uiState) { is TimelineUiState.Loading -> LoadingUI() is TimelineUiState.Success -> TimelineUI(statuses) is TimelineUiState.Error -> ErrorUI(message) } } ``` #### StatusCard.kt Reusable composable component for displaying individual status posts. **Features**: - User avatar and information - HTML content rendering - Media attachment display - Interaction buttons (reply, boost, favorite) - Timestamp formatting ### 2. ViewModel Layer **Location**: `ui/viewmodel/` **Responsibilities**: - Manage UI state - Handle business logic - Coordinate data operations - Expose data streams to UI - Survive configuration changes **Key Components**: #### TimelineViewModel.kt Manages the timeline screen state and operations. **State Management**: ```kotlin sealed class TimelineUiState { object Loading : TimelineUiState() data class Success(val statuses: List) : TimelineUiState() data class Error(val message: String) : TimelineUiState() } ``` **Flow**: 1. UI subscribes to `uiState` StateFlow 2. ViewModel fetches data from repository 3. ViewModel updates state based on result 4. UI recomposes with new state **Benefits**: - Survives configuration changes (screen rotation) - Separates UI logic from business logic - Makes testing easier - Provides lifecycle awareness ### 3. Repository Layer **Location**: `data/repository/` **Responsibilities**: - Abstract data sources - Provide clean API to ViewModel - Handle data operations - Implement error handling - Coordinate multiple data sources (if needed) **Key Components**: #### MastodonRepository.kt Provides data operations for Mastodon API. **Methods**: - `getPublicTimeline()`: Fetch public timeline - `getInstanceInfo()`: Get instance information **Pattern**: ```kotlin suspend fun getPublicTimeline(limit: Int, local: Boolean): Result> { return withContext(Dispatchers.IO) { try { val response = apiService.getPublicTimeline(limit, local) if (response.isSuccessful) { Result.success(response.body() ?: emptyList()) } else { Result.failure(Exception("Error: ${response.code()}")) } } catch (e: Exception) { Result.failure(e) } } } ``` **Benefits**: - Single source of truth - Easy to test with mock data - Can add caching layer - Switches between local/remote data ### 4. Data Source Layer **Location**: `data/api/`, `data/models/` **Responsibilities**: - Define API endpoints - Make network requests - Parse JSON responses - Define data models **Key Components**: #### MastodonApiService.kt Retrofit interface defining API endpoints. ```kotlin interface MastodonApiService { @GET("api/v1/timelines/public") suspend fun getPublicTimeline( @Query("limit") limit: Int = 20, @Query("local") local: Boolean = false ): Response> } ``` #### Data Models **Status.kt**: Represents a post/toot **Account.kt**: Represents a user account **MediaAttachment.kt**: Represents media files **Instance.kt**: Represents server instance info ## Data Flow ### Loading Timeline Example ``` 1. User opens app ↓ 2. MainActivity creates ViewModel ↓ 3. ViewModel initializes and calls loadTimeline() ↓ 4. ViewModel sets state to Loading ↓ 5. UI shows loading indicator ↓ 6. ViewModel calls repository.getPublicTimeline() ↓ 7. Repository calls apiService.getPublicTimeline() ↓ 8. Retrofit makes HTTP request to Mastodon API ↓ 9. API returns JSON response ↓ 10. Gson parses JSON to List ↓ 11. Repository returns Result.success(statuses) ↓ 12. ViewModel updates state to Success(statuses) ↓ 13. UI recomposes with status list ↓ 14. StatusCard components render each status ``` ### Error Handling Flow ``` Network Error occurs ↓ Repository catches exception ↓ Returns Result.failure(exception) ↓ ViewModel updates state to Error(message) ↓ UI shows error message and retry button ↓ User clicks retry ↓ Flow starts again from step 3 ``` ## State Management ### UI State Pattern The app uses **unidirectional data flow** with sealed classes for state: ```kotlin sealed class TimelineUiState { object Loading : TimelineUiState() data class Success(val statuses: List) : TimelineUiState() data class Error(val message: String) : TimelineUiState() } ``` **Benefits**: - Type-safe state representation - Exhaustive when expressions - Clear state transitions - Easy to add new states ### StateFlow vs LiveData The app uses **StateFlow** instead of LiveData because: - Better Kotlin coroutines integration - More powerful operators - Lifecycle-aware collection in Compose - Can be used outside Android ## Dependency Management ### Current Implementation The app uses **manual dependency injection** in `MainActivity`: ```kotlin val client = OkHttpClient.Builder() .addInterceptor(loggingInterceptor) .build() val retrofit = Retrofit.Builder() .baseUrl("https://mastodon.social/") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() val apiService = retrofit.create(MastodonApiService::class.java) val repository = MastodonRepository(apiService) val viewModel = TimelineViewModel(repository) ``` ### Future Improvements For larger apps, consider: - **Hilt**: Dependency injection framework - **Koin**: Lightweight DI for Kotlin - **Manual DI with modules**: Create separate DI container classes ## Threading Model ### Coroutines and Dispatchers The app uses Kotlin coroutines for async operations: **Dispatchers.Main**: UI operations ```kotlin viewModelScope.launch { // Main by default _uiState.value = TimelineUiState.Loading } ``` **Dispatchers.IO**: Network and disk I/O ```kotlin withContext(Dispatchers.IO) { apiService.getPublicTimeline() } ``` **Dispatcher.Default**: CPU-intensive work ```kotlin withContext(Dispatchers.Default) { // Heavy computation } ``` ## Error Handling Strategy ### Result Pattern The repository uses Kotlin's `Result` type: ```kotlin suspend fun getPublicTimeline(): Result> { return try { Result.success(data) } catch (e: Exception) { Result.failure(e) } } ``` **Consumption in ViewModel**: ```kotlin repository.getPublicTimeline().fold( onSuccess = { data -> /* handle success */ }, onFailure = { error -> /* handle error */ } ) ``` ### Error Types 1. **Network Errors**: No internet, timeout 2. **HTTP Errors**: 4xx, 5xx status codes 3. **Parse Errors**: Invalid JSON 4. **Unknown Errors**: Unexpected exceptions ## Testing Strategy ### Unit Testing **Repository Layer**: ```kotlin @Test fun `getPublicTimeline returns success with valid data`() = runTest { val mockApi = mock() val repository = MastodonRepository(mockApi) // Test implementation } ``` **ViewModel Layer**: ```kotlin @Test fun `loadTimeline updates state to Success`() = runTest { val mockRepository = mock() val viewModel = TimelineViewModel(mockRepository) // Test implementation } ``` ### Integration Testing Test complete flows from ViewModel to Repository to API. ### UI Testing Use Compose Testing to test UI components: ```kotlin @Test fun statusCard_displays_content() { composeTestRule.setContent { StatusCard(status = testStatus) } composeTestRule .onNodeWithText("Test content") .assertIsDisplayed() } ``` ## Performance Considerations ### Image Loading **Coil** library handles: - Async loading - Caching (memory + disk) - Placeholder/error images - Automatic lifecycle management ### List Performance **LazyColumn** provides: - Lazy loading (only visible items rendered) - Recycling (item reuse) - Efficient scrolling ### Memory Management - ViewModels survive configuration changes - Images are cached efficiently - Lists use lazy loading ## Security Considerations 1. **HTTPS Only**: All API calls use HTTPS 2. **No Cleartext Traffic**: Disabled in manifest 3. **Input Validation**: Validate all user input 4. **Rate Limiting**: Respect API rate limits 5. **Error Messages**: Don't expose sensitive info in errors ## Future Enhancements ### Recommended Additions 1. **Authentication**: OAuth 2.0 implementation 2. **Database**: Room for offline caching 3. **Pagination**: Implement infinite scroll 4. **Deep Linking**: Handle Mastodon URLs 5. **Search**: Add search functionality 6. **Notifications**: Push notifications support 7. **Multiple Accounts**: Account switching 8. **Dark Theme**: Complete theme support 9. **Accessibility**: Enhanced accessibility features 10. **Testing**: Comprehensive test coverage ### Architecture Evolution As the app grows: - Move to multi-module architecture - Implement Clean Architecture layers - Add use cases/interactors - Separate feature modules - Add shared UI components module ## References - [Guide to app architecture (Android)](https://developer.android.com/topic/architecture) - [ViewModel Overview](https://developer.android.com/topic/libraries/architecture/viewmodel) - [Repository Pattern](https://developer.android.com/codelabs/basic-android-kotlin-training-repository-pattern) - [Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) - [Jetpack Compose Architecture](https://developer.android.com/jetpack/compose/architecture)