Skip to content

Instantly share code, notes, and snippets.

@root-ansh
Created September 21, 2025 07:50
Show Gist options
  • Select an option

  • Save root-ansh/455d2dceb0b63d4a330524e53749421d to your computer and use it in GitHub Desktop.

Select an option

Save root-ansh/455d2dceb0b63d4a330524e53749421d to your computer and use it in GitHub Desktop.
AnimatedContent
@HiltViewModel
class TestViewModel @Inject constructor(
): ViewModel(){
data class Entry(val value: String, val id: Long)
data class MyScreenState(val list:List<Entry> = listOf())
sealed interface MyScreenIntent{ data object StartEmission: MyScreenIntent }
private val _uiState = MutableStateFlow(MyScreenState())
val uiState: StateFlow<MyScreenState> = _uiState.asStateFlow()
fun onIntent(intent: MyScreenIntent){
viewModelScope.launch {
when(intent){
MyScreenIntent.StartEmission -> {
randomLetterFlow().collect {
list-> _uiState.update { it.copy(list) }
}
}
}
}
}
private var data = mutableListOf<Entry>()
private var sendEmpty = false
private fun randomLetterFlow(): Flow<List<Entry>> = flow {
fun Int.toAZLetter(): Char {
val normalized = ((this % 26) + 26) % 26
return 'A' + normalized
}
while (currentCoroutineContext().isActive) {
if (!sendEmpty){
emit(data.toList())
sendEmpty = true
}
else{
val char = (0..25).random().toAZLetter().toString()
data.add(Entry(char, System.currentTimeMillis()))
emit(data.sortedBy { it.value }.toList())
}
delay(1000)
}
}
}
@Composable
fun TestAnimatedContent(
myViewModel: TestViewModel = hiltViewModel<TestViewModel>()
) {
val uiState by myViewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(Unit) { myViewModel.onIntent(MyScreenIntent.StartEmission) }
MyScreen(uiState)
}
@Composable
fun MyScreen(state: TestViewModel.MyScreenState) {
Column(Modifier.fillMaxSize().background(Color(0xFFFFD180))) {
Text(
modifier = Modifier.fillMaxWidth().background(Color(0xFFE5BB74)).padding(start = 8.dp, end = 8.dp, top = 32.dp, bottom = 16.dp),
text = "Testing Animated Content",
fontSize = 18.sp
)
// CORRECT : the transition animation from empty to filled only happens once
AnimatedContent(state.list.isEmpty()) { it ->
if(it) MyScreenEmpty()
else MyScreenFilled(state.list)
}
// WRONG : causes flickr
// AnimatedContent(state.list) { it ->
// if(it.isEmpty()) MyScreenEmpty()
// else MyScreenFilled(state.list)
// }
}
}
@Composable
fun MyScreenEmpty() {
val infiniteTransition = rememberInfiniteTransition(label = "iconScale")
val scale by infiniteTransition.animateFloat(
initialValue = 0.8f,
targetValue = 1.2f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 500, easing = LinearOutSlowInEasing),
repeatMode = RepeatMode.Reverse
),
label = "scaleAnimation"
)
Column (
modifier = Modifier.fillMaxSize().background(Color(0xFFFFD180)),
horizontalAlignment = Alignment.CenterHorizontally) {
Spacer(Modifier.size(48.dp))
Icon(
imageVector = Icons.Default.Warning,
contentDescription = "",
modifier = Modifier.size(200.dp).graphicsLayer {
scaleX = scale
scaleY = scale
},
tint = Color(0xFFB67200)
)
Text(
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 16.dp),
text = "No Entries Found",
fontSize = 36.sp,
textAlign = TextAlign.Center,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.ExtraBold
)
}
}
@Composable
fun MyScreenFilled(list: List<TestViewModel.Entry>) {
LazyVerticalGrid(
modifier = Modifier.fillMaxSize().background(Color(0xFFFFD180)),
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(8.dp)
) {
items(items = list, key = {it.id}){ value ->
Card(
modifier = Modifier
.animateItem(
fadeInSpec = spring(stiffness = Spring.StiffnessMediumLow),
placementSpec = spring(
stiffness = Spring.StiffnessLow,
visibilityThreshold = IntOffset.VisibilityThreshold
),
fadeOutSpec = spring(stiffness = Spring.StiffnessMediumLow)
)
.padding(8.dp),
elevation = CardDefaults.cardElevation(4.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFFB67200)),
shape = RoundedCornerShape(8.dp)
) {
Text(
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 16.dp),
text = value.value,
fontSize = 54.sp,
color = Color(0xFFFFEAC5),
textAlign = TextAlign.Center,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.ExtraBold
)
}
}
}
}
@Preview
@Composable
fun Preview1() {
MyScreen(TestViewModel.MyScreenState(listOf("A", "B", "C").map { TestViewModel.Entry(it,it.toLong()) }))
}
@Preview
@Composable
fun Preview2() {
MyScreenEmpty()
}
@Preview
@Composable
fun Preview3() {
MyScreenFilled((listOf("A", "B", "C").map { TestViewModel.Entry(it,it.toLong()) }))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment