Modern Android development follows the MVVM (Model-View-ViewModel) architecture pattern. It separates concerns and makes apps testable and maintainable.
// Model - Data layer
data class User(
val id: Int,
val name: String,
val email: String
)
class UserRepository(private val api: UserApi) {
suspend fun getUsers(): List<User> = api.fetchUsers()
}
// ViewModel - Presentation logic
class UserViewModel(private val repo: UserRepository) : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
fun loadUsers() {
viewModelScope.launch {
try {
_users.value = repo.getUsers()
} catch (e: Exception) {
_users.value = emptyList()
}
}
}
}
// View - UI layer (Activity/Fragment)
class UserActivity : AppCompatActivity() {
private lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_users)
val repo = UserRepository(UserApi())
viewModel = ViewModelProvider(this, UserViewModelFactory(repo))
.get(UserViewModel::class.java)
viewModel.users.observe(this) { users ->
// Update RecyclerView adapter
}
viewModel.loadUsers()
}
}
Android Jetpack is a suite of libraries to help you follow best practices:
// Domain Layer - Business logic
interface UserRepository {
suspend fun getUsers(): Result<List<User>>
}
class GetUsersUseCase(private val repo: UserRepository) {
suspend operator fun invoke(): Result<List<User>> = repo.getUsers()
}
// Data Layer - Implementations
class UserRepositoryImpl(
private val api: UserApi,
private val dao: UserDao
) : UserRepository {
override suspend fun getUsers(): Result<List<User>> = runCatching {
val cached = dao.getAllUsers()
if (cached.isNotEmpty()) cached
else api.fetchUsers().also { dao.insertAll(it) }
}
}
// Presentation Layer
@HiltViewModel
class UserViewModel @Inject constructor(
private val getUsers: GetUsersUseCase
) : ViewModel() {
val uiState: StateFlow<UiState<List<User>>> = ...
}
// app/build.gradle.kts
plugins { id("com.google.dagger.hilt.android") }
dependencies {
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-compiler:2.48")
}
// Application class
@HiltAndroidApp
class App : Application()
// Activity
@AndroidEntryPoint
class MainActivity : AppCompatActivity()
<!-- res/navigation/nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
app:startDestination="@id/homeFragment">
<fragment android:id="@+id/homeFragment"
android:name=".HomeFragment">
<action android:id="@+id/to_detail"
app:destination="@id/detailFragment" />
</fragment>
<fragment android:id="@+id/detailFragment"
android:name=".DetailFragment" />
</navigation>
// In code
findNavController().navigate(R.id.to_detail, bundleOf("id" to 123))