Skip to content

Instantly share code, notes, and snippets.

@necatisozer
Last active April 26, 2024 16:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save necatisozer/8ba4c614f31f2bcc2e4cf8b945d74c8a to your computer and use it in GitHub Desktop.
Save necatisozer/8ba4c614f31f2bcc2e4cf8b945d74c8a to your computer and use it in GitHub Desktop.
Movie App
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import coil3.compose.AsyncImage
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
@Preview
fun App() {
MaterialTheme {
val navController = rememberNavController()
NavHost(navController, "movies") {
composable("movies") {
val viewModel = viewModel { MoviesViewModel() }
val uiState by viewModel.uiState.collectAsState()
LaunchedEffect(viewModel) {
viewModel.updateMovies()
}
MoviesScreen(uiState)
}
}
}
}
@Composable
private fun MoviesScreen(uiState: MoviesUiState, modifier: Modifier = Modifier) {
LazyVerticalGrid(
columns = GridCells.Adaptive(180.dp),
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier.fillMaxSize()
) {
items(uiState.movies) { movie ->
MovieCell(movie)
}
}
}
@Composable
private fun MovieCell(movie: Movie, modifier: Modifier = Modifier) {
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier) {
AsyncImage(
movie.poster,
contentDescription = movie.title,
modifier = Modifier.fillMaxWidth().aspectRatio(2 / 3f).clip(RoundedCornerShape(16.dp))
)
Text(movie.title, textAlign = TextAlign.Center, style = MaterialTheme.typography.h6)
}
}
plugins {
alias(libs.plugins.kotlinxSerialization)
}
kotlin {
sourceSets {
androidMain.dependencies {
implementation(libs.ktor.client.okhttp)
}
commonMain.dependencies {
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.coil.compose)
implementation(libs.coil.network.ktor)
}
desktopMain.dependencies {
implementation(libs.kotlinx.coroutines.swing)
implementation(libs.ktor.client.okhttp)
}
iosMain.dependencies {
implementation(libs.ktor.client.darwin)
}
}
}
[versions]
compose-plugin = "1.6.10-beta02"
kotlinx-coroutines = "1.8.0"
ktor = "2.3.8"
androidx-lifecycle = "2.8.0-beta01"
androidx-navigation = "2.8.0-alpha02"
coil = "3.0.0-alpha06"
[libraries]
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor", version.ref = "coil" }
[plugins]
kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
import kotlinx.serialization.Serializable
@Serializable
data class Movie(
val id: Int,
val poster: String,
val title: String
)
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class MoviesViewModel : ViewModel() {
private val _uiState = MutableStateFlow(MoviesUiState())
val uiState = _uiState.asStateFlow()
private val httpClient = HttpClient {
install(ContentNegotiation) {
json()
}
}
fun updateMovies() {
viewModelScope.launch {
val movies = fetchMovies()
_uiState.update { it.copy(movies = movies) }
}
}
private suspend fun fetchMovies() = httpClient
.get("https://my-json-server.typicode.com/necatisozer/movieapi/popular")
.body<List<Movie>>()
override fun onCleared() {
httpClient.close()
super.onCleared()
}
}
data class MoviesUiState(val movies: List<Movie> = emptyList())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment