/** * RetrofitNetworkApp — fetches random dog images from Dog CEO API and shows them * in a LazyVerticalGrid. Demonstrates Retrofit, kotlinx.serialization, Coil, and * a Loading/Success/Error UI-state pattern. */ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import retrofit2.Retrofit import retrofit2.http.GET import java.io.IOException @Serializable data class DogResponse(val message: List, val status: String) interface DogApi { @GET("api/breeds/image/random/20") suspend fun getRandomDogs(): DogResponse } // Retrofit client (wire `asConverterFactory` in a real app module). object DogClient { private val json = Json { ignoreUnknownKeys = true } // val api: DogApi = Retrofit.Builder() // .baseUrl("https://dog.ceo/") // .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) // .build() // .create(DogApi::class.java) } sealed interface DogUiState { object Loading : DogUiState data class Success(val urls: List) : DogUiState data class Error(val message: String) : DogUiState } class DogViewModel(private val api: DogApi) : ViewModel() { var state by mutableStateOf(DogUiState.Loading) private set init { load() } fun load() = viewModelScope.launch { state = DogUiState.Loading state = try { DogUiState.Success(api.getRandomDogs().message) } catch (e: IOException) { DogUiState.Error("Network error: ${e.message}") } catch (e: Throwable) { DogUiState.Error("Unexpected: ${e.message}") } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun DogExplorerScreen(vm: DogViewModel = viewModel()) { Scaffold( topBar = { TopAppBar( title = { Text("Dog Explorer") }, actions = { TextButton(onClick = vm::load) { Text("Refresh") } } ) } ) { padding -> Box( modifier = Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center ) { when (val s = vm.state) { DogUiState.Loading -> CircularProgressIndicator() is DogUiState.Error -> Column(horizontalAlignment = Alignment.CenterHorizontally) { Text(s.message) Spacer(Modifier.height(8.dp)) Button(onClick = vm::load) { Text("Retry") } } is DogUiState.Success -> LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 140.dp), contentPadding = PaddingValues(8.dp), horizontalArrangement = Arrangement.spacedBy(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp) ) { items(s.urls) { url -> AsyncImage( model = url, contentDescription = "Dog photo", contentScale = ContentScale.Crop, modifier = Modifier.size(140.dp) ) } } } } } }