0
0
Android Kotlinmobile~20 mins

Repository testing with fakes in Android Kotlin - Mini App: Build & Ship

Choose your learning style9 modes available
Build: Repository Test Screen
This screen is for testing a repository using a fake data source to verify data fetching logic without real database or network.
Target UI
-------------------------
| Repository Test Screen |
|-----------------------|
| Data:                 |
| [Loading...]          |
|                       |
| [Fetch Data Button]   |
-------------------------
Create a Repository interface with a method to fetch a list of strings.
Implement a FakeRepository that returns a fixed list of strings.
Create a ViewModel that uses the Repository to fetch data asynchronously.
Build a simple UI with a TextView to show data or loading state.
Add a Button to trigger data fetching from the ViewModel.
Show fetched data in the TextView after fetching completes.
Starter Code
Android Kotlin
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

// TODO: Define Repository interface here

// TODO: Implement FakeRepository here

class RepositoryViewModel(private val repository: Repository) : ViewModel() {
    private val _data = MutableStateFlow<List<String>?>(null)
    val data: StateFlow<List<String>?> = _data

    fun fetchData() {
        viewModelScope.launch {
            // TODO: Fetch data from repository and update _data
        }
    }
}

// TODO: Build a Composable or Activity UI with a TextView and Button
// TODO: Connect ViewModel to UI and show loading and data states
Task 1
Task 2
Task 3
Task 4
Task 5
Solution
Android Kotlin
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import androidx.lifecycle.viewModelScope

interface Repository {
    suspend fun fetchItems(): List<String>
}

class FakeRepository : Repository {
    override suspend fun fetchItems(): List<String> {
        return listOf("Apple", "Banana", "Cherry")
    }
}

class RepositoryViewModel(private val repository: Repository) : ViewModel() {
    private val _data = MutableStateFlow<List<String>?>(null)
    val data: StateFlow<List<String>?> = _data

    fun fetchData() {
        viewModelScope.launch {
            _data.value = null // show loading
            val items = repository.fetchItems()
            _data.value = items
        }
    }
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: android.os.Bundle?) {
        super.onCreate(savedInstanceState)
        val repo = FakeRepository()
        val factory = object : ViewModelProvider.Factory {
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                @Suppress("UNCHECKED_CAST")
                return RepositoryViewModel(repo) as T
            }
        }
        setContent {
            MaterialTheme {
                Surface(modifier = Modifier.padding(16.dp)) {
                    val viewModel: RepositoryViewModel = viewModel(factory = factory)
                    RepositoryTestScreen(viewModel)
                }
            }
        }
    }
}

@Composable
fun RepositoryTestScreen(viewModel: RepositoryViewModel) {
    val data by viewModel.data.collectAsState()
    Column {
        Text(text = "Data:")
        Text(text = data?.joinToString(", ") ?: "Loading...")
        Button(onClick = { viewModel.fetchData() }, modifier = Modifier.padding(top = 16.dp)) {
            Text("Fetch Data")
        }
    }
}

This solution defines a Repository interface with a suspend function fetchItems(). The FakeRepository returns a fixed list of fruits.

The RepositoryViewModel holds a MutableStateFlow to track the data list or loading state. The fetchData() function launches a coroutine to fetch data asynchronously and update the state.

The MainActivity sets up the UI with Jetpack Compose. It creates the ViewModel with the fake repository and shows a simple screen with a Text showing "Loading..." initially or the fetched data, plus a button to trigger fetching.

This approach allows testing repository logic without real network or database, making tests fast and reliable.

Final Result
Completed Screen
-------------------------
| Repository Test Screen |
|-----------------------|
| Data:                 |
| Apple, Banana, Cherry  |
|                       |
| [Fetch Data Button]   |
-------------------------
When user opens screen, Text shows 'Loading...' because no data fetched yet.
When user taps 'Fetch Data' button, Text updates to 'Loading...' briefly, then shows 'Apple, Banana, Cherry'.
Stretch Goal
Add a loading indicator (e.g., a progress bar or spinner) visible while data is loading.
💡 Hint
Use a Boolean state to track loading and show a CircularProgressIndicator in Compose when loading is true.