View (Activity/Fragment)
↓
ViewModel
↓
Repository
↓
Data Source (API/Database)
data class User(
val id: Int,
val name: String,
val email: String
)
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
class UserRepository(
private val api: ApiService,
private val dao: UserDao
) {
suspend fun getUsers(): Result<List<User>> {
return try {
val users = api.getUsers()
dao.insertAll(users)
Result.success(users)
} catch (e: Exception) {
val cachedUsers = dao.getAll()
if (cachedUsers.isNotEmpty()) {
Result.success(cachedUsers)
} else {
Result.failure(e)
}
}
}
suspend fun getUserById(id: Int): User? {
return dao.getById(id) ?: api.getUserById(id)
}
suspend fun createUser(user: User): Result<User> {
return try {
val newUser = api.createUser(user)
dao.insert(newUser)
Result.success(newUser)
} catch (e: Exception) {
Result.failure(e)
}
}
}
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<UiState<List<User>>>(UiState.Loading)
val uiState: StateFlow<UiState<List<User>>> = _uiState.asStateFlow()
private val _selectedUser = MutableStateFlow<User?>(null)
val selectedUser: StateFlow<User?> = _selectedUser.asStateFlow()
init {
loadUsers()
}
fun loadUsers() {
viewModelScope.launch {
_uiState.value = UiState.Loading
repository.getUsers()
.onSuccess { users ->
_uiState.value = UiState.Success(users)
}
.onFailure { error ->
_uiState.value = UiState.Error(error.message ?: "未知错误")
}
}
}
fun selectUser(userId: Int) {
viewModelScope.launch {
val user = repository.getUserById(userId)
_selectedUser.value = user
}
}
fun createUser(name: String, email: String) {
viewModelScope.launch {
val user = User(0, name, email)
repository.createUser(user)
.onSuccess {
loadUsers()
}
.onFailure {
_uiState.value = UiState.Error("创建失败")
}
}
}
}
@AndroidEntryPoint
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
private lateinit var binding: ActivityUserBinding
private val adapter = UserAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUserBinding.inflate(layoutInflater)
setContentView(binding.root)
setupRecyclerView()
observeUiState()
setupListeners()
}
private fun setupRecyclerView() {
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(this@UserActivity)
adapter = this@UserActivity.adapter
}
}
private fun observeUiState() {
lifecycleScope.launch {
viewModel.uiState.collect { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> showUsers(state.data)
is UiState.Error -> showError(state.message)
}
}
}
}
private fun setupListeners() {
binding.btnRefresh.setOnClickListener {
viewModel.loadUsers()
}
adapter.onItemClick = { user ->
viewModel.selectUser(user.id)
}
}
private fun showLoading() {
binding.progressBar.visibility = View.VISIBLE
binding.recyclerView.visibility = View.GONE
binding.errorView.visibility = View.GONE
}
private fun showUsers(users: List<User>) {
binding.progressBar.visibility = View.GONE
binding.recyclerView.visibility = View.VISIBLE
binding.errorView.visibility = View.GONE
adapter.submitList(users)
}
private fun showError(message: String) {
binding.progressBar.visibility = View.GONE
binding.recyclerView.visibility = View.GONE
binding.errorView.visibility = View.VISIBLE
binding.errorText.text = message
}
}
class UserAdapter : ListAdapter<User, UserAdapter.ViewHolder>(UserDiffCallback()) {
var onItemClick: ((User) -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemUserBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
inner class ViewHolder(
private val binding: ItemUserBinding
) : RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setOnClickListener {
val position = bindingAdapterPosition
if (position != RecyclerView.NO_POSITION) {
onItemClick?.invoke(getItem(position))
}
}
}
fun bind(user: User) {
binding.apply {
tvName.text = user.name
tvEmail.text = user.email
}
}
}
class UserDiffCallback : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}
}
}
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"app_database"
).build()
}
@Provides
fun provideUserDao(database: AppDatabase): UserDao {
return database.userDao()
}
@Provides
fun provideUserRepository(
api: ApiService,
dao: UserDao
): UserRepository {
return UserRepository(api, dao)
}
}
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
}
class UserRepository(
private val api: ApiService,
private val dao: UserDao
) {
fun getUsers(): Flow<List<User>> = flow {
emit(dao.getAll())
try {
val users = api.getUsers()
dao.insertAll(users)
emit(users)
} catch (e: Exception) {
}
}
}
sealed class Resource<T> {
data class Success<T>(val data: T) : Resource<T>()
data class Error<T>(val message: String, val data: T? = null) : Resource<T>()
class Loading<T> : Resource<T>()
}
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
private val _users = MutableStateFlow<Resource<List<User>>>(Resource.Loading())
val users: StateFlow<Resource<List<User>>> = _users
fun loadUsers() {
viewModelScope.launch {
_users.value = Resource.Loading()
try {
val result = repository.getUsers()
_users.value = Resource.Success(result)
} catch (e: Exception) {
_users.value = Resource.Error(e.message ?: "未知错误")
}
}
}
}
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
val users: Flow<PagingData<User>> = Pager(
config = PagingConfig(pageSize = 20),
pagingSourceFactory = { repository.getUserPagingSource() }
).flow.cachedIn(viewModelScope)
}