Created
May 30, 2024 00:03
-
-
Save ioannisa/924e256e10bbb40148f96cc5c8c03daf to your computer and use it in GitHub Desktop.
New type-safety approach using DataClasses to pass Parcelables
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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