How to Build a Weather App in Kotlin Using MVVM, Retrofit, and Room

Table of Contents

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

Kotlin
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:

Kotlin
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

Kotlin
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

Kotlin
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:

JSON
{
  "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.

Kotlin
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

Kotlin
dependencies {
    implementation 'androidx.room:room-runtime:2.4.2'
    kapt 'androidx.room:room-compiler:2.4.2'
}

Create Entity & DAO

Kotlin
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

Kotlin
@Database(entities = [WeatherEntity::class], version = 1)
abstract class WeatherDatabase : RoomDatabase() {
    abstract fun weatherDao(): WeatherDao
}

Creating Repository

The repository will handle data fetching.

Kotlin
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

Kotlin
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

Kotlin
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

XML
<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..!

Skill Up: Software & AI Updates!

Receive our latest insights and updates directly to your inbox

Related Posts

error: Content is protected !!