Android Architecture series · Part 7 of 9

Data, Network and Configuration Changes

Khairul Alam·October 2026·~13 min read
RoomRetrofitgRPCMicroservicesViewModel

This part fills the Data box (Room, Retrofit, gRPC) and then shows what happens when rotation, language, font or window size changes, and why state must not live in the Activity.

In 60 seconds
  • Local: Room for tables, DataStore for settings.
  • Remote: Retrofit + OkHttp; gRPC for contract-first and streaming.
  • Microservices: independent teams; use a gateway so the phone calls one API.
  • Config changes: Activity is recreated; keep state in ViewModel, SavedStateHandle or storage.
Contents
  1. Local data with Room
  2. Remote data with Retrofit
  3. What is gRPC
  4. How microservices help
  5. Surviving configuration changes

1. Local data with Room

Room is Jetpack’s layer over SQLite. You describe tables as classes and queries as functions; Room checks the SQL at compile time. It belongs to the Data box, behind your repository.

@Entity(tableName = "profile")
data class ProfileEntity(@PrimaryKey val id: String, val name: String, val active: Boolean)

@Dao
interface ProfileDao {
    @Query("SELECT * FROM profile WHERE id = :id")
    suspend fun find(id: String): ProfileEntity?

    @Query("SELECT * FROM profile WHERE id = :id")
    fun observe(id: String): Flow<ProfileEntity?>         // emits again when data changes

    @Upsert suspend fun save(entity: ProfileEntity)
}

@Database(entities = [ProfileEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() { abstract fun profileDao(): ProfileDao }

For small key-value settings (theme, language, onboarding done) use DataStore instead of SharedPreferences. Keep ProfileEntity (database shape) separate from Profile (what the app uses) and convert with a small mapper, so a database change does not ripple into the UI.

2. Remote data with Retrofit

Retrofit turns a REST API into a Kotlin interface, usually on top of OkHttp (the HTTP client) and a JSON converter such as kotlinx.serialization or Moshi.

interface ProfileApi {
    @GET("profiles/{id}")
    suspend fun profile(@Path("id") id: String): ProfileDto
}

val api = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .client(OkHttpClient.Builder().build())
    .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
    .build()
    .create(ProfileApi::class.java)

Together with the repository from Part 2 you get the classic offline-first pattern: the database is the single source of truth. The repository refreshes from the network into the database, and the screen observes the database.

ScreenobservesRoomsource of truthRepositoryrefresh()RetrofitnetworkArrows show who feeds whom: network fills the database, the database feeds the screen.
The user sees saved data instantly, and fresh data appears when the refresh finishes.

3. What is gRPC?

gRPC is a way for two programs to call each other’s functions over the network. Instead of REST + JSON (“send text to a URL”), you write a contract in a .proto file, and tools generate the client and server code in your language.

// profile.proto
syntax = "proto3";
service ProfileService {
  rpc GetProfile (ProfileRequest) returns (ProfileReply);
  rpc WatchProfile (ProfileRequest) returns (stream ProfileReply);   // streaming
}
message ProfileRequest { string id = 1; }
message ProfileReply   { string id = 1; string name = 2; bool active = 3; }
REST + JSONgRPC + protobuf
FormatText, human-readableBinary, smaller and faster to parse
ContractOften docs or OpenAPIThe .proto file is the contract; code is generated
TransportHTTP/1.1 or 2HTTP/2, supports streaming both ways
Browser/debugEasy (curl, browser)Needs tooling

For Android: gRPC is a good fit when the backend already uses it or you need streaming. Architecturally nothing changes: the generated client sits behind a RemoteRepository, and the rest of the app never knows. The same protobuf messages can also be stored locally with Proto DataStore.

4. How microservices help

A microservice backend is split into many small services (accounts, orders, payments), each with its own data and its own deploys, instead of one giant server. For a mobile team this matters in three ways:

Do not call ten services from the phone. Put an API gateway or a “backend for frontend” in front that combines them. Fewer round trips save battery and data, and the app talks to one tidy API.

5. Surviving configuration changes

By default, when the device configuration changes, Android destroys and recreates the Activity so it can load matching resources. These changes include:

The recreated Activity starts with empty fields. This is where architecture earns its keep: state must live outside the Activity.

Where state livesRotation / language / font / size changeProcess death (system kills app in background)
Activity / View fieldsLostLost
ViewModel (MVVM) or retained presenterSurvivesLost
SavedStateHandle / onSaveInstanceStateSurvivesSurvives (small data only)
Room / DataStore / filesSurvivesSurvives
class FormViewModel(private val saved: SavedStateHandle) : ViewModel() {
    // typed text survives rotation AND process death
    val draft: StateFlow<String> = saved.getStateFlow("draft", "")
    fun onDraftChanged(text: String) { saved["draft"] = text }
}

What each architecture does

Language, font and size specifically

Test it

Turn on “Don’t keep activities” in Developer options, rotate the phone, change the language and font size. If the screen keeps its state, your architecture works.

← Back to all writing