0
0
Android Kotlinmobile~20 mins

Repository pattern in depth in Android Kotlin - Mini App: Build & Ship

Choose your learning style9 modes available
Build: User List Screen
Displays a list of users fetched via a repository that abstracts data sources.
Target UI
-------------------------
| User List             |
|-----------------------|
| - Loading...          |
|                       |
|-----------------------|
| [Refresh Button]      |
-------------------------
Create a UserRepository interface with a method to get users.
Implement a UserRepositoryImpl that fetches users from a fake data source.
Create a ViewModel that uses the repository to get users.
Display the list of users in a RecyclerView.
Add a Refresh button to reload users from the repository.
Starter Code
Android Kotlin
package com.example.repositorypattern

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

// TODO: Define User data class

// TODO: Define UserRepository interface

// TODO: Implement UserRepositoryImpl with fake data

class UserViewModel(private val repository: UserRepository) : ViewModel() {
    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users: StateFlow<List<User>> = _users

    fun loadUsers() {
        viewModelScope.launch {
            // TODO: Load users from repository and update _users
        }
    }
}

// TODO: Create UserListActivity with RecyclerView and Refresh button
// TODO: Connect ViewModel and update UI accordingly
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
Solution
Android Kotlin
package com.example.repositorypattern

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.viewModels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.widget.Button
import android.widget.TextView
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

// Data class representing a User
data class User(val id: Int, val name: String)

// Repository interface
interface UserRepository {
    suspend fun getUsers(): List<User>
}

// Implementation of UserRepository with fake data
class UserRepositoryImpl : UserRepository {
    override suspend fun getUsers(): List<User> {
        // Simulate network or database delay
        kotlinx.coroutines.delay(500)
        return listOf(
            User(1, "Alice"),
            User(2, "Bob"),
            User(3, "Charlie")
        )
    }
}

// ViewModel using the repository
class UserViewModel(private val repository: UserRepository) : ViewModel() {
    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users: StateFlow<List<User>> = _users

    fun loadUsers() {
        viewModelScope.launch {
            val result = repository.getUsers()
            _users.value = result
        }
    }
}

// Factory to create UserViewModel with repository
class UserViewModelFactory(private val repository: UserRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return UserViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

// Simple RecyclerView Adapter for User list
class UserAdapter(private var users: List<User>) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
    class UserViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)

    override fun onCreateViewHolder(parent: android.view.ViewGroup, viewType: Int): UserViewHolder {
        val textView = TextView(parent.context).apply {
            textSize = 20f
            setPadding(16, 16, 16, 16)
        }
        return UserViewHolder(textView)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        holder.textView.text = users[position].name
    }

    override fun getItemCount() = users.size

    fun updateUsers(newUsers: List<User>) {
        users = newUsers
        notifyDataSetChanged()
    }
}

// Activity displaying the user list
class UserListActivity : ComponentActivity() {
    private val repository = UserRepositoryImpl()
    private val viewModel: UserViewModel by viewModels { UserViewModelFactory(repository) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Root layout
        val rootLayout = android.widget.LinearLayout(this).apply {
            orientation = android.widget.LinearLayout.VERTICAL
            setPadding(16, 16, 16, 16)
        }

        // RecyclerView for users
        val recyclerView = RecyclerView(this).apply {
            layoutManager = LinearLayoutManager(this@UserListActivity)
        }

        val adapter = UserAdapter(emptyList())
        recyclerView.adapter = adapter

        // Refresh button
        val refreshButton = Button(this).apply {
            text = "Refresh"
            setOnClickListener {
                viewModel.loadUsers()
            }
        }

        rootLayout.addView(recyclerView, android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f)
        rootLayout.addView(refreshButton, android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT)

        setContentView(rootLayout)

        // Observe users from ViewModel
        viewModel.users
            .collectLatest { users ->
                adapter.updateUsers(users)
            }

        // Load users initially
        viewModel.loadUsers()
    }
}

This solution shows the repository pattern by defining a UserRepository interface that abstracts data fetching. The UserRepositoryImpl class simulates fetching users from a data source.

The UserViewModel uses this repository to load users asynchronously and exposes a StateFlow to the UI.

The UserListActivity sets up a simple UI with a RecyclerView to display users and a Refresh button to reload data. It observes the ViewModel's user list and updates the UI accordingly.

This pattern helps separate data logic from UI, making the app easier to maintain and test.

Final Result
Completed Screen
-------------------------
| User List             |
|-----------------------|
| Alice                 |
| Bob                   |
| Charlie               |
|                       |
|-----------------------|
| [Refresh Button]      |
-------------------------
When the screen opens, it shows the list of users: Alice, Bob, Charlie.
Tapping the Refresh button reloads the user list from the repository and updates the display.
Stretch Goal
Add a loading indicator that shows while users are being fetched.
💡 Hint
Use a MutableStateFlow<Boolean> in ViewModel to track loading state and show/hide a ProgressBar in the UI.