# Stories by Denis Rudenko on Medium

Stories by Denys Rudenko on Medium

This is one page of public article previews, not the complete archive. Follow Next page to continue. Summaries are not the original full articles.

## Performance in Jetpack Compose

DevFeed: [Performance in Jetpack Compose](<https://devfeed.tech/articles/performance-in-jetpack-compose-25901.md>)

Original publisher: [Read original article](<https://skyyo.medium.com/performance-in-jetpack-compose-9a85ce02f8f9?source=rss-56174fa84bcc------2>)

Author: Denys Rudenko

Published: 2022-10-03T16:41:03Z

Content type: tutorial

Language: en

Sources: [Stories by Denis Rudenko on Medium](<https://devfeed.tech/sources/stories-by-denis-rudenko-on-medium.md>)

Topics: [Jetpack Compose](<https://devfeed.tech/topics/jetpack-compose.md>), [ui](<https://devfeed.tech/topics/ui.md>), [Code](<https://devfeed.tech/topics/code.md>)

Tags: [android](<https://devfeed.tech/tags/android.md>), [android-app-development](<https://devfeed.tech/tags/android-app-development.md>), [article](<https://devfeed.tech/tags/article.md>), [compiler](<https://devfeed.tech/tags/compiler.md>), [compose](<https://devfeed.tech/tags/compose.md>), [false-positive](<https://devfeed.tech/tags/false-positive.md>), [jetpack-compose](<https://devfeed.tech/tags/jetpack-compose.md>), [lambda](<https://devfeed.tech/tags/lambda.md>), [modifiers](<https://devfeed.tech/tags/modifiers.md>), [performance](<https://devfeed.tech/tags/performance.md>), [recomposition](<https://devfeed.tech/tags/recomposition.md>), [scopes](<https://devfeed.tech/tags/scopes.md>), [skip](<https://devfeed.tech/tags/skip.md>), [ui](<https://devfeed.tech/tags/ui.md>), [val](<https://devfeed.tech/tags/val.md>), [var](<https://devfeed.tech/tags/var.md>), [variables](<https://devfeed.tech/tags/variables.md>)

### AI overview

A practical guide to improving Jetpack Compose performance through stable parameters, skippable composables, optimized recompositions, remembered lambdas, and compiler and layout-inspector metrics.

### Source excerpt

Article tells about my research on how to write efficient Compose code. It consists of 6 sections and a TL;DR/Summary in the end. We will cover: Optimising recompositions When should you use @Immutable and @Stable annotations; Unstable classes, variables, lambdas; Non-restartable & skippable composables; Lambda modifiers; Passing lambdas providing required fields instead of fields in composables; Inlined composables; When you should use remember { }. We'll be using Compose Compiler Metrics and layout inspector tools to know: - If a class is stable or not; - If the composable function is skippable/restartable; - Amount of skipped recompositions. 1. Unstable objects on UI layer. To understand why we should care about stability, let us peek into a very important metric called skippability. It allows compose runtime to skip recomposition of a composable when all the parameters it uses are considered stable. What is considered stable by the compiler? - All primitive value types: Boolean, Int, Long, Float, Char, etc. - Strings - Lambdas (not always, we will get to it later) We want composable functions to use stable params to become skippable. 1) Don't use var when seeking stability. Fields declared as var are considered unstable: https://medium.com/media/724da23ba38eaa9d3312318e7e8038b9/hrefhttps://medium.com/media/f8dd8a39bce60e48b5ff8bdd4a02b9ef/href UserDetails composable will be recomposed even if the user never gets modified. Using val instead of var in User class will fix this issue. 2) Not all lambdas are considered stable. Let's look at the following examples: https://medium.com/media/fbbff417d165a501b21a3e5d2c6f087f/hrefhttps://medium.com/media/c6aaae01fda67c633edcdf7bb351e620/href Since the lambdas capture outside scopes, they won't be automatically inferred as stable and reused as expected. If the lambda requires access to external variables, the compiler will add those variables as fields, which are passed into the constructor of the lambda. We go a 2 ways of

## Video playback in LazyColumn in Jetpack Compose

DevFeed: [Video playback in LazyColumn in Jetpack Compose](<https://devfeed.tech/articles/video-playback-in-lazycolumn-in-jetpack-compose-25900.md>)

Original publisher: [Read original article](<https://proandroiddev.com/video-playback-in-lazycolumn-in-jetpack-compose-df355097f26e?source=rss-56174fa84bcc------2>)

Author: Denys Rudenko

Published: 2021-10-05T23:51:23Z

Content type: tutorial

Language: en

Sources: [Stories by Denis Rudenko on Medium](<https://devfeed.tech/sources/stories-by-denis-rudenko-on-medium.md>)

Topics: [Jetpack Compose](<https://devfeed.tech/topics/jetpack-compose.md>), [ExoPlayer](<https://devfeed.tech/topics/exoplayer.md>), [Playback](<https://devfeed.tech/topics/playback.md>)

Tags: [android](<https://devfeed.tech/tags/android.md>), [android-app-development](<https://devfeed.tech/tags/android-app-development.md>), [androiddev](<https://devfeed.tech/tags/androiddev.md>), [article](<https://devfeed.tech/tags/article.md>), [compose](<https://devfeed.tech/tags/compose.md>), [exoplayer](<https://devfeed.tech/tags/exoplayer.md>), [image](<https://devfeed.tech/tags/image.md>), [jetpack](<https://devfeed.tech/tags/jetpack.md>), [jetpack-compose](<https://devfeed.tech/tags/jetpack-compose.md>), [playback](<https://devfeed.tech/tags/playback.md>), [state](<https://devfeed.tech/tags/state.md>), [video](<https://devfeed.tech/tags/video.md>), [viewmodel](<https://devfeed.tech/tags/viewmodel.md>)

### AI overview

A tutorial on implementing manual and automatic video playback in a Jetpack Compose LazyColumn using ExoPlayer. It covers preserving playback positions, pausing videos when cards are not visible, and responding to the application lifecycle.

### Source excerpt

Article will cover both manual & auto-playback of videos in an efficient way, storing/restoring last played video position, pausing playback if video card is not visible to the user and handling application lifecycle. ExoPlayer will be used for video playback, and these videos as a test data set. Coil will be used for displaying the video thumbnails. Video shows manual play/pause & auto-playback versions side to side. https://medium.com/media/98ab3ea482f78b96bf44de2ce54c7168/hrefStep 1: VideosScreen Let's start implementing all of those features step by step. First we create a composable which will contain the exoPlayer instance, list of videos and playingItemIndex: https://medium.com/media/2440139ad1f185b8d1f1446f844530b9/href Very important part here is using keys for the lazyColumn. In short: we provide a key which enables item state to be consistent across data-set changes isPlaying -- we can always know if video is playing or not. If it's playing -- playingItemIndex will represent the position of the item in the list, if nothing is playing -- null. We need this field to know if we should show play/pause icon, and thumbnail image or playerView. VideoCard -- composable which exposes click event on play/pause icon. Click will be handled inside the viewModel. Notice that we're passing current playback position from exoPlayer as well as the index of clicked item. We'll use it to save & restore the playback position for each video. https://medium.com/media/518600644bbec8f225a0c4b933adccd1/href Whenever the play/pause icon is clicked, we're checking for three scenarios: currentlyPlayingIndex is null -- video is not playing at the moment, so we are assigning the videoIndex to currentlyPlayingIndex. currentlyPlayingIndex is the same as videoIndex clicked -- this means that the same video is already playing, and we want to pause the playback. That's why we assign null to currentlyPlayingIndex. To store the last played video position we're mutating list, and saving the position

## Input validation in Jetpack Compose

DevFeed: [Input validation in Jetpack Compose](<https://devfeed.tech/articles/input-validation-in-jetpack-compose-25897.md>)

Original publisher: [Read original article](<https://proandroiddev.com/input-validation-in-jetpack-compose-e99c18b44fe3?source=rss-56174fa84bcc------2>)

Author: Denys Rudenko

Published: 2021-08-22T16:42:02Z

Content type: tutorial

Language: en

Sources: [Stories by Denis Rudenko on Medium](<https://devfeed.tech/sources/stories-by-denis-rudenko-on-medium.md>)

Topics: [Jetpack Compose](<https://devfeed.tech/topics/jetpack-compose.md>), [Dagger](<https://devfeed.tech/topics/dagger.md>), [Processes](<https://devfeed.tech/topics/processes.md>), [User experience (UX)](<https://devfeed.tech/topics/ux.md>)

Tags: [android](<https://devfeed.tech/tags/android.md>), [android-app-development](<https://devfeed.tech/tags/android-app-development.md>), [androiddev](<https://devfeed.tech/tags/androiddev.md>), [androiddevweekly](<https://devfeed.tech/tags/androiddevweekly.md>), [dagger](<https://devfeed.tech/tags/dagger.md>), [jetpack-compose](<https://devfeed.tech/tags/jetpack-compose.md>), [savedstatehandle](<https://devfeed.tech/tags/savedstatehandle.md>), [stateflow](<https://devfeed.tech/tags/stateflow.md>), [ux](<https://devfeed.tech/tags/ux.md>), [validation](<https://devfeed.tech/tags/validation.md>), [viewmodel](<https://devfeed.tech/tags/viewmodel.md>)

### AI overview

This tutorial demonstrates manual and automatic input validation in Jetpack Compose. It uses composables, ViewModels, Dagger Hilt, StateFlow, and savedStateHandle to preserve input state after process death, validate fields, control button state, and emit success or error events. It also discusses restoring focus and improving keyboard-related user experience.

### Source excerpt

This post will not only focus on manual & auto validation of TextField inputs, but also on how to provide a nice UX by handling process death, including highlighting the last focused field upon restoration. https://medium.com/media/7d776a0ec6ff89810e194adf54680973/hrefSetup We'll use composables, viewModels & Dagger Hilt in these samples. Part 1: InputWrapper Let's create a wrapper for inputs, so it can contain both input value and errorId, if there is a validation error. Class is parcelable, so we can save it inside savedStateHandle later. https://medium.com/media/6c74ba5b4f958d63646a819643b45620/hrefPart 2: ViewModel (simplified version)https://medium.com/media/39bf975a63461cd00f68b8360c6a8f6b/href We're getting a stateFlow from the savedStateHandle using .getStateFlow extension. Using savedStateHandle here allows us to retain the latest data after process death happens. You can also use getLiveData, it's a matter of personal preferences unless you plan to perform observable.switchMap/flatMapLatest type of operations, since there is a subtle difference between stateFlow & liveData in such scenarios described here & change proposal created. areInputsValid stateFlow is combining name & creditCardNumber flows into a single one. The results it returns depend on whether input fields are valid or not. It's invoked whenever there is new emition to name or creditCardNumber flows. We'll use it's value to control buttons enabled/disabled state in our composable. _events will be used for one time events emission to the view layer. ScreenEvent is a sealed class representing all of our one time events. onNameEntered & onCardNumberEntered will be invoked when user enters symbols inside the TextField. We find out whether the input is valid by using an InputValidator object. It's returning either a string resource id containing error message, or a null. Latter means that input is valid. We store the new value & errorId using: https://medium.com/media/d9ad9c61328481f3318c143e6daf3

## Real-time lifecycle-aware updates in Jetpack Compose

DevFeed: [Real-time lifecycle-aware updates in Jetpack Compose](<https://devfeed.tech/articles/real-time-lifecycle-aware-updates-in-jetpack-compose-25898.md>)

Original publisher: [Read original article](<https://proandroiddev.com/real-time-lifecycle-aware-updates-in-jetpack-compose-be2e80e613c2?source=rss-56174fa84bcc------2>)

Author: Denys Rudenko

Published: 2021-04-18T21:20:35Z

Content type: tutorial

Language: en

Sources: [Stories by Denis Rudenko on Medium](<https://devfeed.tech/sources/stories-by-denis-rudenko-on-medium.md>)

Topics: [Jetpack Compose](<https://devfeed.tech/topics/jetpack-compose.md>), [Compose](<https://devfeed.tech/topics/compose.md>), [real-time](<https://devfeed.tech/topics/real-time.md>), [ui](<https://devfeed.tech/topics/ui.md>)

Tags: [android](<https://devfeed.tech/tags/android.md>), [androiddev](<https://devfeed.tech/tags/androiddev.md>), [compose](<https://devfeed.tech/tags/compose.md>), [flow](<https://devfeed.tech/tags/flow.md>), [jetpack](<https://devfeed.tech/tags/jetpack.md>), [jetpack-compose](<https://devfeed.tech/tags/jetpack-compose.md>), [real-time](<https://devfeed.tech/tags/real-time.md>), [viewmodel](<https://devfeed.tech/tags/viewmodel.md>)

### AI overview

A tutorial on implementing real-time, lifecycle-aware updates in Jetpack Compose using Kotlin flows and ViewModel scope. It demonstrates podcast download progress and currency updates for visible items, including flow creation, observation, cancellation, and UI recomposition.

### Source excerpt

This is a journey into real-time updates, flows creation, cancelation, and proper lifecycle scoping. We'll be exploring all of this using two examples: 1. Imitating podcast download progress. (on the right) 2. Imitating currency updates for visible items. (on the left) https://medium.com/media/a7a27205a34388e3956fbca89b1b9b2d/hrefPodcast downloadsStep 1: Creating podcasts ViewModelhttps://medium.com/media/44ad93376981982842b9bb4096f93d4b/href We're using getPodcasts() to populate list of podcasts, which we then put into our _podcasts, which is a MutableStateFlow. We'll be observing podcasts from our UI layer to build a LazyColumn of cards. downloadQueue map will be used to store active downloads. Step 2: Creating podcasts UIhttps://medium.com/media/70c8b3ab1157a3c847272f361797a6c1/hrefPodcastsScreen Observes viewModel.podcasts containing with the help of .collectAsState(). This means that list of cards will be diffed & recomposed each time the value of viewModel.podcasts changes. Whenever user taps on the download icon, we invoke the onDownloadPodcastClicked(), which we'll declare in the next step in the viewModel. PodcastCard Composable that contains podcast title & download status. Step 3: Imitating the download & updating the UI Let's add these 3 functions into the PodcastsViewModel. https://medium.com/media/e0d4da5c83948894c3960db82a5ff715/hrefonDownloadPodcastClicked() Checks whether downloadQueue already has specified podcastId inside. If it does - we do nothing, since this means that we already have an active download for that podcast. Creates the download flow using provideDownloadFlow():Flow<Int> to imitate the download & puts it in our downloadQueue map using podcastId as a key. Starts observation of the download progress by calling observeDownload(). provideDownloadFlow() Creates a flow which emits random numbers in range between 10 & 25 representing the download percentage in 0.5 second intervals. Whenever progress is 100 -- it removes itself from the dow

## Swipe to reveal in Jetpack Compose

DevFeed: [Swipe to reveal in Jetpack Compose](<https://devfeed.tech/articles/swipe-to-reveal-in-jetpack-compose-25899.md>)

Original publisher: [Read original article](<https://proandroiddev.com/swipe-to-reveal-in-jetpack-compose-6ffa8928a4c2?source=rss-56174fa84bcc------2>)

Author: Denys Rudenko

Published: 2021-02-19T17:21:58Z

Content type: tutorial

Language: en

Sources: [Stories by Denis Rudenko on Medium](<https://devfeed.tech/sources/stories-by-denis-rudenko-on-medium.md>)

Topics: [Jetpack Compose](<https://devfeed.tech/topics/jetpack-compose.md>), [Compose](<https://devfeed.tech/topics/compose.md>), [draggable](<https://devfeed.tech/topics/draggable.md>), [ui](<https://devfeed.tech/topics/ui.md>)

Tags: [android](<https://devfeed.tech/tags/android.md>), [androiddev](<https://devfeed.tech/tags/androiddev.md>), [compose](<https://devfeed.tech/tags/compose.md>), [compose-animation](<https://devfeed.tech/tags/compose-animation.md>), [draggable](<https://devfeed.tech/tags/draggable.md>), [gestures](<https://devfeed.tech/tags/gestures.md>), [jetpack-compose](<https://devfeed.tech/tags/jetpack-compose.md>), [stateflow](<https://devfeed.tech/tags/stateflow.md>), [swipe-to-reveal](<https://devfeed.tech/tags/swipe-to-reveal.md>), [viewmodel](<https://devfeed.tech/tags/viewmodel.md>)

### AI overview

A tutorial showing how to implement swipe-to-reveal cards in Jetpack Compose by detecting horizontal drag gestures and managing revealed card state with a view model and StateFlow.

### Source excerpt

Swipe to dismiss is really easy to implement in compose, including item removal animation by using a combination of SwipeToDismiss & AnimatedVisibility composables. SwipeToDismiss doesn't allow us to stop the dragging motion midway though, so let's take a look how we can achieve the following by detecting horizontal drag gestures on any composable. https://medium.com/media/1a355b2d3e397414c51442342f16cda5/hrefStep 1: Creating a data source Those who've read expandable lists in jetpack compose can skip this step, since data source is almost the same. First we define our model to hold the card related info: data class CardModel(val id: Int, val title: String) We'll be using AAC viewModel example here, but feel free to use any "controller" abstraction you prefer. https://medium.com/media/28ca457093268969ac4f2e179ee860ef/href This class serves 5 purposes: Holds list of cards using MutableStateFlow in _cards field, and exposes a StateFlow to observers via cards field. Holds list of "revealed" card ids in _revealedCardIdsList, and exposes them to observers via revealedCardIdsList field. Provides list of cards using getFakeData() function. We need a coroutine here, to emit the testList into _cards. Has onItemExpanded() to mark cards as revealed, by adding tapped card id to _revealedCardIdsList, and notify observers about this change by mutating the state of _revealedCardIdsList. Has onItemCollapsed() to remove revealed card id from _revealedCardIdsList, and notify observers about this change by mutating the state of _revealedCardIdsList. Step 2: CardsScreen composablehttps://medium.com/media/4505fde17b0e8f9672f392cb9b3296f3/href We are observing the viewModel.cards containing our list of cards, and viewModel.expandedCardIds with the help of .collectAsStateWithLifecycle(). UI is quite simple here, each list item is represented with a Box that contains our hidden action icons & the draggable card. ActionsRow -- row with 3 icons, exposes callbacks from each icon tap: onDelete,

## Expandable lists in Jetpack Compose

DevFeed: [Expandable lists in Jetpack Compose](<https://devfeed.tech/articles/expandable-lists-in-jetpack-compose-25896.md>)

Original publisher: [Read original article](<https://proandroiddev.com/expandable-lists-in-jetpack-compose-b0b78c767b4?source=rss-56174fa84bcc------2>)

Author: Denys Rudenko

Published: 2021-01-11T18:21:04Z

Content type: tutorial

Language: en

Sources: [Stories by Denis Rudenko on Medium](<https://devfeed.tech/sources/stories-by-denis-rudenko-on-medium.md>)

Topics: [Jetpack Compose](<https://devfeed.tech/topics/jetpack-compose.md>), [ui](<https://devfeed.tech/topics/ui.md>), [Data structures](<https://devfeed.tech/topics/data-structures.md>)

Tags: [android](<https://devfeed.tech/tags/android.md>), [compose](<https://devfeed.tech/tags/compose.md>), [constraintlayout](<https://devfeed.tech/tags/constraintlayout.md>), [coroutine](<https://devfeed.tech/tags/coroutine.md>), [data-class](<https://devfeed.tech/tags/data-class.md>), [expandable-animation](<https://devfeed.tech/tags/expandable-animation.md>), [expandable-list](<https://devfeed.tech/tags/expandable-list.md>), [expandable-view](<https://devfeed.tech/tags/expandable-view.md>), [jetpack-compose](<https://devfeed.tech/tags/jetpack-compose.md>), [layout](<https://devfeed.tech/tags/layout.md>), [lifecycle](<https://devfeed.tech/tags/lifecycle.md>), [memory](<https://devfeed.tech/tags/memory.md>), [state](<https://devfeed.tech/tags/state.md>), [stateflow](<https://devfeed.tech/tags/stateflow.md>), [viewmodel](<https://devfeed.tech/tags/viewmodel.md>)

### AI overview

A tutorial explains how to build expandable lists in Jetpack Compose. It uses a card data class, a view model with StateFlow for cards and expanded card IDs, composable functions that observe state, and transition state to animate card expansion.

### Source excerpt

Expandable views are a common way to hide details of a visualised data structures. Let's take a look at how the following can be achieved in 6 steps, using compose: https://medium.com/media/b9dface7145a04eb3bee8ac03eed3a4e/hrefStep 1: Creating a data class for carddata class ExpandableCardModel(val id: Int, val title: String)Step 2: Create a data source We'll be using AAC viewModel example here, but feel free to use any "controller" abstraction you like. https://medium.com/media/61d179f82414c569e4d8e415bbed8aa6/href This class serves 4 purposes: Holds list of cards using MutableStateFlow in _cards field, and exposes a StateFlow to observers via cards field. Holds list of expanded card ids in _expandedCardIdsList, and exposes them to observers via expandedCardIdsList field. Provides list of cards using getFakeData() function. We need a coroutine here, to emit the testList into _cards. Contains a onCardArrowClicked() to mark cards as expanded by adding tapped card id to _expandedCardIdsList, and notify observers about this change by mutating the state of _expandedCardIdsList. Step 3: MainActivityhttps://medium.com/media/6e10348f08fc4004c3a397cd46c19bd0/href We are initialising the CardsViewModel here, and providing it to the CardsScreen composable function. Step 4: CardsScreen composablehttps://medium.com/media/b2cd50c3ab379cd75897eda6b1aca4c6/href We are observing the viewModel.cards containing our list of cards, and viewModel.expandedCardIds with the help of .collectAsState(). What's the purpose of .collectAsState()? It's converting StateFlow into a State. Update: using .collectAsStateWithLifecycle() which is the same thing, but lifecycle aware, and helps to reduce memory consumption when UI is not visible to the user. More info in this article. What's a State? From the documentation: " State is a value holder where reads to the value property during the execution of a composable function, the current recomposeScope will be subscribed to changes of that value." Step