Skip to main content

Welcome Contributors!

Thank you for your interest in contributing to TecMeli! This guide will help you get started with contributing to the project.
TecMeli follows Senior-level Android development standards with a focus on Clean Architecture, SOLID principles, and comprehensive testing.

Getting Started

1

Fork the Repository

Fork the repository to your GitHub account and clone it locally:
git clone https://github.com/your-username/tecmeli.git
cd tecmeli
2

Set Up Development Environment

Follow the Building Guide to set up your environment and configure API credentials.
3

Create a Feature Branch

Create a new branch for your changes:
git checkout -b feature/your-feature-name
Use descriptive branch names:
  • feature/ - New features
  • fix/ - Bug fixes
  • refactor/ - Code refactoring
  • docs/ - Documentation updates
  • test/ - Test additions or improvements

Code Standards

Architecture Principles

TecMeli follows Clean Architecture with clear layer separation:
┌─────────────────────────────────────────────┐
│  UI Layer (Compose + ViewModels)           │
│  - Screens, Components                      │
│  - StateFlow consumption                    │
└─────────────────────────────────────────────┘
              ↓ depends on
┌─────────────────────────────────────────────┐
│  Domain Layer (Pure Kotlin)                 │
│  - Use Cases                                │
│  - Domain Models                            │
│  - Repository Interfaces                    │
└─────────────────────────────────────────────┘
              ↓ depends on
┌─────────────────────────────────────────────┐
│  Data Layer (Repositories)                  │
│  - Repository Implementations               │
│  - API Services, DTOs                       │
│  - Data Mappers (DTO → Domain)              │
└─────────────────────────────────────────────┘
Never expose DTOs or Response objects to the UI layer. Always map to domain models.

SOLID Principles

All code contributions should follow SOLID principles:
Each class should have one, and only one, reason to change.Good Example:
// Each class has a single responsibility
class GetProductsUseCase(private val repository: ProductRepository) {
    suspend operator fun invoke(query: String): Result<List<Product>> {
        if (query.isBlank()) return Result.success(emptyList())
        return repository.searchProducts(query)
    }
}
Bad Example:
// ❌ ViewModel doing network calls and mapping
class HomeViewModel {
    fun searchProducts(query: String) {
        val response = api.search(query)  // ❌ Direct API call
        val products = response.map { ... }  // ❌ Mapping in ViewModel
    }
}
Classes should be open for extension but closed for modification.Good Example:
// Abstract base that can be extended
abstract class BaseRepository {
    protected suspend fun <T> safeApiCall(
        apiCall: suspend () -> Response<T>
    ): Result<T> { /* ... */ }
}
Derived classes must be substitutable for their base classes.Good Example:
interface ProductRepository {
    suspend fun searchProducts(query: String): Result<List<Product>>
}

class ProductRepositoryImpl(private val api: MeliApi) : ProductRepository {
    override suspend fun searchProducts(query: String): Result<List<Product>> {
        // Implementation maintains the contract
    }
}
Clients should not be forced to depend on interfaces they don’t use.Good Example:
// Separate focused interfaces
interface ProductReader {
    suspend fun getProduct(id: String): Result<Product>
}

interface ProductSearcher {
    suspend fun searchProducts(query: String): Result<List<Product>>
}
Depend on abstractions, not concretions.Good Example:
// ViewModel depends on abstraction (interface)
class HomeViewModel @Inject constructor(
    private val getProductsUseCase: GetProductsUseCase  // ✓ Use case abstraction
) : ViewModel()

// ❌ Bad: Depending on concrete implementation
class HomeViewModel(
    private val repository: ProductRepositoryImpl  // ❌ Concrete class
)

Kotlin Coding Style

Follow the official Kotlin Coding Conventions:
// Classes and objects: PascalCase
class ProductRepository
object NetworkModule

// Functions and variables: camelCase
fun searchProducts()
val productList = emptyList()

// Constants: SCREAMING_SNAKE_CASE
const val BASE_URL = "https://api.mercadolibre.com"

// Private properties: prefix with underscore (for backing properties)
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()

State Management

Use StateFlow for reactive state management:
class HomeViewModel @Inject constructor(
    private val getProductsUseCase: GetProductsUseCase
) : ViewModel() {

    private val _uiState = MutableStateFlow<UiState<List<Product>>>(UiState.Loading)
    val uiState: StateFlow<UiState<List<Product>>> = _uiState.asStateFlow()

    fun searchProducts(query: String) {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            val result = getProductsUseCase(query)
            _uiState.value = result.fold(
                onSuccess = { if (it.isEmpty()) UiState.Empty else UiState.Success(it) },
                onFailure = { UiState.Error(it.message ?: "Unknown error") }
            )
        }
    }
}
Always expose immutable StateFlow to the UI and keep MutableStateFlow private.

Error Handling

Use Result type for operations that can fail:
override suspend fun searchProducts(query: String): Result<List<Product>> {
    return try {
        val response = api.searchProducts(
            status = "active",
            siteId = "MLA",
            query = query
        )
        if (response.isSuccessful && response.body() != null) {
            val products = response.body()!!.results.map { it.toDomain() }
            Result.success(products)
        } else {
            Result.failure(Exception("API Error: ${response.code()}"))
        }
    } catch (e: Exception) {
        Result.failure(e)
    }
}

Testing Requirements

All code contributions must include tests. PRs without tests will not be merged.

Test Coverage Expectations

  • Repository Layer: 100% - Test all data operations
  • Domain Layer: 100% - Test all use cases
  • ViewModel Layer: 80%+ - Test all state transitions

Writing Tests

Follow the testing patterns established in the codebase:
1

Unit Tests for All Layers

Create unit tests for repositories, use cases, and ViewModels:
class ProductRepositoryImplTest {
    private val api: MeliApi = mockk()
    private val repository = ProductRepositoryImpl(api)

    @Test
    fun `searchProducts returns success when api succeeds`() = runTest {
        // Test implementation
    }
}
2

Use MockK for Mocking

Use MockK instead of Mockito for Kotlin-friendly mocking:
coEvery { api.searchProducts(any(), any(), "query") } returns Response.success(dto)
3

Test Coroutines Properly

Use runTest and StandardTestDispatcher:
@OptIn(ExperimentalCoroutinesApi::class)
class HomeViewModelTest {
    private val testDispatcher = StandardTestDispatcher()

    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
    }
}
4

Run Tests Before Committing

Ensure all tests pass:
./gradlew test
./gradlew jacocoTestReport
See the Testing Guide for detailed examples.

Dependency Injection

Use Hilt for dependency injection:
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideMeliApi(
        okHttpClient: OkHttpClient
    ): MeliApi {
        return Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(MeliApi::class.java)
    }
}

Commit Guidelines

Write clear, descriptive commit messages:

Commit Message Format

<type>: <subject>

<body>

<footer>

Types

  • feat: - New feature
  • fix: - Bug fix
  • refactor: - Code refactoring
  • test: - Adding or updating tests
  • docs: - Documentation changes
  • style: - Code style changes (formatting, etc.)
  • perf: - Performance improvements
  • chore: - Maintenance tasks

Examples

feat: add product filtering by category

- Implement category filter in ProductRepository
- Add FilterProductsUseCase
- Update HomeViewModel to support filters
- Add UI components for category selection

Closes #123

Pull Request Process

1

Update Your Branch

Before submitting, sync with the main branch:
git checkout main
git pull upstream main
git checkout feature/your-feature
git rebase main
2

Run All Checks

Ensure your code passes all checks:
# Run tests
./gradlew test

# Generate coverage report
./gradlew jacocoTestReport

# Check for build errors
./gradlew assembleDebug
3

Create Pull Request

Push your branch and create a PR:
git push origin feature/your-feature
Include in your PR description:
  • What changes were made
  • Why the changes are necessary
  • How to test the changes
  • Screenshots (for UI changes)
  • Test coverage report
4

Address Review Comments

Respond to review comments and make requested changes:
# Make changes
git add .
git commit -m "refactor: address review comments"
git push origin feature/your-feature

PR Template

## Description
Brief description of the changes

## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Refactoring
- [ ] Documentation update

## Testing
- [ ] Unit tests added/updated
- [ ] All tests pass locally
- [ ] Test coverage maintained or improved

## Checklist
- [ ] Code follows the project's coding standards
- [ ] Self-review performed
- [ ] Documentation updated
- [ ] No new warnings introduced
- [ ] Follows SOLID principles
- [ ] Clean Architecture layers respected

## Related Issues
Closes #<issue_number>

Code Review Criteria

Your PR will be reviewed for:

Architecture

  • Proper layer separation
  • No DTO leakage to UI
  • Correct dependency direction

Code Quality

  • SOLID principles
  • No code duplication
  • Clear naming
  • Proper error handling

Testing

  • Test coverage > 80%
  • All test cases pass
  • Edge cases covered

Documentation

  • KDoc for public APIs
  • README updates if needed
  • Clear commit messages

Common Mistakes to Avoid

Avoid these common pitfalls:
Wrong:
// ❌ Never expose DTOs to ViewModels
fun getProducts(): Flow<ProductDto>
Correct:
// ✓ Always map to domain models
fun getProducts(): Flow<Product>
Wrong:
// ❌ ViewModel should not call API directly
class HomeViewModel(private val api: MeliApi)
Correct:
// ✓ Use use cases or repositories
class HomeViewModel(private val getProductsUseCase: GetProductsUseCase)
Wrong:
// ❌ Complex logic in ViewModel
fun searchProducts(query: String) {
    if (query.length < 3 || query.contains("invalid")) {
        // Complex validation logic
    }
}
Correct:
// ✓ Move validation to use case
class GetProductsUseCase {
    suspend operator fun invoke(query: String): Result<List<Product>> {
        // Validation logic here
    }
}
Wrong:
// ❌ Exposing mutable state
val uiState: MutableStateFlow<UiState>
Correct:
// ✓ Expose immutable state
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()

Documentation Standards

When adding new features, update the documentation:
  • Code Comments: Use KDoc for public APIs
  • README: Update if architecture changes
  • Migration Guides: For breaking changes
  • API Documentation: For new endpoints
Example KDoc:
/**
 * Repository for managing product data from Mercado Libre API.
 *
 * This repository handles product search and detail retrieval,
 * including DTO to domain model mapping and error handling.
 *
 * @property api The Mercado Libre API service
 */
class ProductRepositoryImpl @Inject constructor(
    private val api: MeliApi
) : ProductRepository {

    /**
     * Searches for products matching the given query.
     *
     * @param query Search term to filter products. If blank, returns empty list.
     * @return Result containing list of products or error
     */
    override suspend fun searchProducts(query: String): Result<List<Product>> {
        // Implementation
    }
}

Getting Help

Discord Community

Join our Discord for real-time help

GitHub Discussions

Ask questions in GitHub Discussions

Issue Tracker

Report bugs or request features

Documentation

Browse the full documentation

Recognition

Contributors will be recognized in:
  • The project’s README
  • Release notes
  • GitHub contributor list
Thank you for contributing to TecMeli! Your efforts help make this project better for everyone.

Build docs developers (and LLMs) love