Skip to content

Instantly share code, notes, and snippets.

@ioannisa
Created May 30, 2024 00:03
Show Gist options
  • Save ioannisa/924e256e10bbb40148f96cc5c8c03daf to your computer and use it in GitHub Desktop.
Save ioannisa/924e256e10bbb40148f96cc5c8c03daf to your computer and use it in GitHub Desktop.
New type-safety approach using DataClasses to pass Parcelables
package eu.anifantakis.composeapp
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import coil.compose.rememberAsyncImagePainter
import eu.anifantakis.composeapp.ui.theme.ComposeAppTheme
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.net.URLDecoder
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import kotlin.reflect.typeOf
// define a simple data class to hold Person information
@Serializable
@Parcelize
data class Person(
val id: Int,
val section: Int,
val name: String,
val imageUrl: String,
val landingPage: String
): Parcelable
val personType = object : NavType<Person>(
isNullableAllowed = false
) {
override fun get(bundle: Bundle, key: String): Person? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
bundle.getParcelable(key, Person::class.java)
} else {
@Suppress("DEPRECATION") // Suppress the deprecation warning
bundle.getParcelable(key)
}
}
override fun parseValue(value: String): Person {
return Json.decodeFromString<Person>(value)
}
override fun serializeAsValue(value: Person): String {
return Json.encodeToString(value)
}
override fun put(bundle: Bundle, key: String, value: Person) {
bundle.putParcelable(key, value)
}
override val name: String = Person::class.java.name
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val persons = arrayListOf<Person>() // create an array list of persons
var section = 1
for (i in 1..200){ // create 200 Person instances in that list
if (i%15 == 0){
section++
}
persons.add(
Person(
id = i,
section = section, // all persons are assigned section
name = "Ioannis Anifantakis",
imageUrl = URLEncoder.encode("https://anifantakis.eu/wp-content/uploads/2021/05/ioannis-anifantakis-firebase-small.jpg", StandardCharsets.UTF_8.toString()),
landingPage = URLEncoder.encode("https://anifantakis.eu", StandardCharsets.UTF_8.toString())
)
)
}
setContent {
ComposeAppTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colorScheme.background) {
NavigationNew(persons)
}
}
}
}
}
@Composable
fun NavigationOld(persons: List<Person>) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "main_screen" ) {
composable("main_screen") {
MainScreen(persons, navController)
}
composable("detail_screen") {
val person = navController.previousBackStackEntry?.savedStateHandle?.get<Person>("person")
person?.let {
DetailScreen(navController, it)
}
}
}
}
@Composable
fun NavigationNew(persons: List<Person>) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Routes.MainScreenNav ) {
composable<Routes.MainScreenNav> {
MainScreen(persons, navController)
}
composable<Routes.DetailScreenNav>(
typeMap = mapOf(typeOf<Person>() to personType)
) {
val person = it.toRoute<Routes.DetailScreenNav>().person
DetailScreen(navController, person)
}
}
}
sealed interface Routes {
@Serializable
object MainScreenNav
@Serializable
data class DetailScreenNav(
val person: Person
)
}
@Composable
fun ImageLoader(imageUrl: String){
Image(
painter = rememberAsyncImagePainter(imageUrl),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(120.dp)
)
}
@Composable
fun ListItem(person: Person, navController: NavController){
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.clickable {
// navigation old
//navController.currentBackStackEntry?.savedStateHandle?.set("person", person)
//navController.navigate("detail_screen")
// navigation new
navController.navigate(DetailScreenNav(person))
},
elevation = CardDefaults.cardElevation(),
) {
Row{
ImageLoader(URLDecoder.decode(person.imageUrl, StandardCharsets.UTF_8.toString()))
Spacer(modifier = Modifier.width(8.dp))
Text(
person.name+' '+person.id,
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(8.dp)
)
}
}
}
@Composable
fun DetailScreen(navController: NavController, person: Person) {
Card (
modifier = Modifier.padding(12.dp),
elevation = CardDefaults.cardElevation(),
){
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text("section ${person.section}", style = MaterialTheme.typography.headlineMedium)
Text("${person.name} ${person.id}", style = MaterialTheme.typography.headlineSmall)
Spacer(modifier = Modifier.height(8.dp))
ImageLoader(person.imageUrl)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { navController.popBackStack() }) {
Text("Go Back")
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MainScreen(persons: List<Person>, navController: NavController){
val grouped = persons.groupBy{it.section}
LazyColumn(){
grouped.forEach { (section, sectionPersons) ->
stickyHeader {
Text(
text = "SECTION: $section",
color = Color.White,
modifier = Modifier
.background(color = Color.Black)
.padding(8.dp)
.fillMaxWidth()
)
}
items(
items = sectionPersons,
key = { it.id },
itemContent = {
ListItem(it, navController)
}
)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment