Building an Android app that fetches live weather data is a great way to get hands-on with networking, local storage, and clean architecture principles. In this guide, we’ll build a Weather App in Kotlin using MVVM, Retrofit for API calls, and Room for local caching. It’s a solid project for interviews or real-world use — especially if you’re still working with older setups and haven’t yet moved to Clean + MVVM Architecture, and Jetpack Compose.
Technical Disclaimer:
The code snippets shared here are from an older codebase, so things like the API or the implementation might be different now. If you’re working with the latest tech stack, you’ll probably need to make a few tweaks to the code at different levels.
Weather App Project Overview
Our Weather App will:
- Fetch weather data from OpenWeatherMap API.
- Use MVVM (Model-View-ViewModel) architecture for clean code separation.
- Implement Retrofit for API communication.
- Store data locally using Room Database for offline access.
- Display a list of weather forecasts with RecyclerView.
- Ensure smooth UI updates using LiveData.
Project Structure
WeatherApp/
│── api/
│ ├── WeatherService.kt
│ ├── ApiClient.kt
│── model/
│ ├── WeatherResponse.kt
│── repository/
│ ├── WeatherRepository.kt
│── viewmodel/
│ ├── WeatherViewModel.kt
│── view/
│ ├── MainActivity.kt
│ ├── WeatherAdapter.kt
│── database/
│ ├── WeatherDao.kt
│ ├── WeatherDatabase.kt
│── utils/
│ ├── Constants.kt
│── res/
│ ├── layout/
│ │ ├── activity_main.xml
│ │ ├── item_weather.xml
Setting Up Retrofit for API Calls
First, we need to integrate Retrofit to fetch weather data from OpenWeatherMap.
Add Dependencies
Add these dependencies to your build.gradle (Module: app)
file:
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
}
Create API Interface
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query
interface WeatherService {
@GET("weather")
suspend fun getWeather(
@Query("q") city: String,
@Query("appid") apiKey: String,
@Query("units") units: String = "metric"
): WeatherResponse
}
Create Retrofit Client
object ApiClient {
private const val BASE_URL = "https://api.openweathermap.org/data/2.5/"
val instance: WeatherService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(WeatherService::class.java)
}
}
This setup allows us to fetch weather data efficiently.
Note:- An API key is mandatory. Make sure to create a valid API key — if you use an invalid one or skip it entirely, the API will throw an error response like this:
{
"cod": 401,
"message": "Invalid API key. Please see https://openweathermap.org/faq#error401 for more info."
}
Creating Data Models
The API response will be mapped to data classes.
data class WeatherResponse(
val name: String,
val main: Main,
val weather: List<Weather>
)
data class Main(
val temp: Double
)
data class Weather(
val description: String
)
Implementing Room Database
To cache weather data, let’s create a Room Database.
Add Dependencies
dependencies {
implementation 'androidx.room:room-runtime:2.4.2'
kapt 'androidx.room:room-compiler:2.4.2'
}
Create Entity & DAO
import androidx.room.*
@Entity
data class WeatherEntity(
@PrimaryKey val city: String,
val temperature: Double,
val description: String
)
@Dao
interface WeatherDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertWeather(weather: WeatherEntity)
@Query("SELECT * FROM WeatherEntity WHERE city = :city")
suspend fun getWeather(city: String): WeatherEntity?
}
Create Database
@Database(entities = [WeatherEntity::class], version = 1)
abstract class WeatherDatabase : RoomDatabase() {
abstract fun weatherDao(): WeatherDao
}
Creating Repository
The repository will handle data fetching.
class WeatherRepository(private val api: WeatherService, private val dao: WeatherDao) {
suspend fun getWeather(city: String, apiKey: String): WeatherEntity {
val response = api.getWeather(city, apiKey)
val weatherEntity = WeatherEntity(
city = response.name,
temperature = response.main.temp,
description = response.weather.first().description
)
dao.insertWeather(weatherEntity)
return weatherEntity
}
}
Implementing ViewModel
class WeatherViewModel(private val repository: WeatherRepository) : ViewModel() {
private val _weather = MutableLiveData<WeatherEntity>()
val weather: LiveData<WeatherEntity> get() = _weather
fun fetchWeather(city: String, apiKey: String) {
viewModelScope.launch {
val data = repository.getWeather(city, apiKey)
_weather.postValue(data)
}
}
}
Creating UI with RecyclerView
Adapter Class
class WeatherAdapter(private val weatherList: List<WeatherEntity>) : RecyclerView.Adapter<WeatherAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_weather, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val weather = weatherList[position]
holder.city.text = weather.city
holder.temp.text = "${weather.temperature}°C"
holder.desc.text = weather.description
}
}
Activity Layout
<RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Conclusion
This tutorial covers API integration, MVVM architecture, and local caching. By following these steps, you can build a solid Android app that fetches and stores weather data efficiently.
If you want to take it further, you can experiment with adding new features such as real-time updates, notifications, or even integrating Jetpack Compose for a more modern UI approach. The foundation is now set for you to keep exploring and improving your Android development skills..!