MVVM Architecture
Ejemplo completo de arquitectura MVVM con Clean Architecture demostrando las mejores prácticas de desarrollo Android
Descripción del Proyecto
Este proyecto demuestra una implementación completa de la arquitectura MVVM (Model-View-ViewModel) combinada con principios de Clean Architecture. Sirve como ejemplo de las mejores prácticas recomendadas por Google para el desarrollo de aplicaciones Android escalables y mantenibles.
La aplicación incluye separación clara de responsabilidades, inyección de dependencias, testing unitario, y patrones de diseño que facilitan el desarrollo en equipo y la evolución del código a largo plazo.
Capturas de Pantalla
Lista principal con datos
Estado de carga con indicador
Vista de detalle de elemento
Tecnologías Utilizadas
MVVM Pattern
Separación de responsabilidades
Clean Architecture
Arquitectura por capas
Observer Pattern
Comunicación reactiva
Kotlin
Lenguaje moderno
LiveData
Datos observables
ViewModel
Gestión de estado UI
Estructura de la Arquitectura
Capas de Clean Architecture
// Capa de Dominio - Use Cases
class GetUserListUseCase(
private val userRepository: UserRepository
) {
suspend operator fun invoke(): Result<List<User>> {
return try {
val users = userRepository.getUsers()
Result.success(users)
} catch (e: Exception) {
Result.failure(e)
}
}
}
// Capa de Datos - Repository Implementation
class UserRepositoryImpl(
private val remoteDataSource: UserRemoteDataSource,
private val localDataSource: UserLocalDataSource
) : UserRepository {
override suspend fun getUsers(): List<User> {
return try {
val remoteUsers = remoteDataSource.fetchUsers()
localDataSource.saveUsers(remoteUsers)
remoteUsers
} catch (e: Exception) {
localDataSource.getCachedUsers()
}
}
}
ViewModel con LiveData
class MainViewModel(
private val getUserListUseCase: GetUserListUseCase
) : ViewModel() {
private val _uiState = MutableLiveData<UiState<List<User>>>()
val uiState: LiveData<UiState<List<User>>> = _uiState
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
fun loadUsers() {
viewModelScope.launch {
_isLoading.value = true
_uiState.value = UiState.Loading
getUserListUseCase()
.onSuccess { users ->
_uiState.value = UiState.Success(users)
}
.onFailure { error ->
_uiState.value = UiState.Error(error.message ?: "Unknown error")
}
_isLoading.value = false
}
}
fun refreshUsers() {
loadUsers()
}
}
Fragment como Vista
class MainFragment : Fragment() {
private var _binding: FragmentMainBinding? = null
private val binding get() = _binding!!
private val viewModel: MainViewModel by viewModels {
MainViewModelFactory(
getUserListUseCase = GetUserListUseCase(
userRepository = UserRepositoryImpl(
remoteDataSource = UserRemoteDataSourceImpl(),
localDataSource = UserLocalDataSourceImpl()
)
)
)
}
private lateinit var userAdapter: UserAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMainBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
observeViewModel()
setupSwipeRefresh()
viewModel.loadUsers()
}
private fun observeViewModel() {
viewModel.uiState.observe(viewLifecycleOwner) { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> showUsers(state.data)
is UiState.Error -> showError(state.message)
}
}
viewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
binding.swipeRefresh.isRefreshing = isLoading
}
}
}