CPS251 Android Development by Scott Shaper

Building The WeatherRepository

The WeatherRepository is responsible for fetching weather data from the network and caching it locally. It is also responsible for returning the cached data if the user is offline, that is not modeled in this example. In a real world application, you would likely have a more complex caching strategy, with different rules for when to fetch from the network and when to use the cached data.

class WeatherRepository(private val weatherApiService: WeatherApiService) {

    // You would typically get the API key from a more secure location
    // or through dependency injection in a real app.
    private val API_KEY = "80d537a4b4cd7a3b10a3c65a70316965"

    suspend fun getCurrentWeather(zipcode: String): Result {
        return try {
            val response = weatherApiService.getCurrentWeather(
                zip = "$zipcode,us",
                appId = API_KEY
            )
            Result.success(response)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

Code Explanation

The WeatherRepository is a crucial component in the clean architecture pattern. It acts as an abstraction layer between the data source (in this case, the network API) and the ViewModel. The Repository pattern ensures that the ViewModel doesn't need to know the details of how data is fetched—whether it comes from a network API, a local database, or a cache.

Dependency Injection

API Key Management

getCurrentWeather Function

The getCurrentWeather function is the main entry point for fetching weather data. It encapsulates all the network request logic and error handling:

Benefits of the Repository Pattern

Learning Aids

Tips for Success

  • Keep your Repository focused on data operations—fetching, caching, and transforming data. Avoid UI-related logic or business logic.
  • Always use suspend functions for network operations in Repositories. This ensures proper integration with coroutines.
  • Return Result types from Repository methods rather than throwing exceptions. This provides a functional approach to error handling that integrates well with coroutines.
  • Inject dependencies (like API services) through the constructor. This makes the Repository testable and follows dependency injection principles.
  • Keep API keys and other sensitive data encapsulated within the Repository. Never expose them to the ViewModel or UI layers.
  • Consider making the Repository an interface if you need multiple implementations (e.g., a real repository and a mock repository for testing).

Common Mistakes to Avoid

  • Hardcoding API keys directly in the code. In production, use secure storage methods like local.properties or environment variables.
  • Throwing exceptions from Repository methods instead of returning Result types. This can make error handling more complex and error-prone.
  • Mixing business logic or UI logic into the Repository. The Repository should only handle data operations.
  • Making the Repository depend on Android-specific components (like Context) unless absolutely necessary. Keep it platform-agnostic for better testability.
  • Not handling exceptions properly. Always catch exceptions in Repository methods and convert them to Result.failure().
  • Not making Repository functions suspend functions when they perform network operations. This can block threads and cause performance issues.
  • Exposing mutable state or complex data structures from the Repository. Keep the Repository interface simple and focused on data fetching.

Best Practices

  • Use the Repository pattern to abstract data sources. This provides a clean separation between the data layer and the business logic layer.
  • Always return Result types from Repository methods that can fail. This enables functional error handling and makes error states explicit.
  • Use suspend functions for all asynchronous operations in Repositories. This ensures proper integration with coroutines and viewModelScope.
  • Keep the Repository interface simple and focused. A Repository method should do one thing: fetch data, cache data, or transform data.
  • Encapsulate sensitive information (like API keys) within the Repository. Never expose them to other layers of the application.
  • Make the Repository easily testable by injecting dependencies through the constructor. This allows you to provide mock dependencies during testing.
  • Consider implementing multiple data sources within a single Repository (e.g., network and local database) to create a single source of truth for your data.
  • Handle all exceptions within the Repository and convert them to appropriate Result.failure() responses. This prevents exceptions from propagating to the ViewModel layer.
  • In production applications, implement caching strategies within the Repository to improve performance and enable offline functionality.