Auto Added by WPeMatico

Nested LazyColumn in Jetpack Compose

Jetpack Compose Series Episode IV— Nested Lazy Column

When displaying groups of elements, we generally use columns and rows. But when it comes to displaying long lists, compose provides efficient alternatives like LazyColumn and LazyRow, which only render visible items in the screen.

This lazy loading approach improves performance and reduces memory consumption.

Before implementing Nested LazyColumn, let’s briefly go through some basics about available components to render large list.

I. LazyColumn & LazyRow

When rendering large datasets, we often use LazyColumn for vertical arrangements and LazyRow for horizontal.

Similar to RecyclerView, it support reverse layout, orientation adjustment, multiple view types, etc.

LazyColumn {
items(data) { item ->
Box(
modifier = Modifier
.height(100.dp)
.fillMaxWidth()
.background(Color.Magenta)
.padding(16.dp)
)
Spacer(modifier = Modifier.padding(8.dp))
}
}


LazyRow {
items(data) { item ->
Box(
modifier = Modifier
.width(100.dp)
.height(200.dp)
.background(Color.Magenta)
.padding(16.dp)
)
Spacer(modifier = Modifier.padding(8.dp))
}
}

Index Position in LazyList

LazyColumn and LazyRow provide an itemsIndexed function that allows us to access the index number of each item in the list.

  LazyColumn {
itemsIndexed(items = dataList) { index, data ->

if (index == 0) {

}else{

}
}
}

Unique ID for LazyList

The key parameter in the LazyList ensures that each item in the list has a stable and unique key, which is essential for efficient list updates and performance optimization.

LazyColumn {
items(items = allProductEntities, key = { item -> item.id }) { product ->
ProductItem(product) {
onProductClick(product.id.toString())
}
}
}

Multiple ViewType

If we want to display different view types, such as headers, footers, or items with distinct UI representations, we can use the index or check view-type from the list to display it accordingly.

Header & Footer Items in LazyColumn
LazyColumn {
itemsIndexed(items = dataList) { index, data ->

if (index == 0) {
HeroCard(data)
} else {
when (data.categoryType) {

CategoryType.Recent -> {
RecentItem(data) {
onRecentItemClick(data.id))
}
}

CategoryType.Popular -> {
PopularItem(data) {
onPopularItemClick(data.id))
}
}

else -> {
TrendingItem(data) {
onTrendingItemClick(data.id)
}
}

}
}
}
}

Moreover, If there’s a need to append additional items to the list or add different components, we can use item function inside LazyList.

 LazyColumn {
item {
HeroCardItem()
}
items(data) { item ->
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.background(Color.Magenta)
.padding(16.dp)
)
Spacer(modifier = Modifier.padding(8.dp))
}
item {
FooterCardItem()
}

}

@Composable
fun HeroCardItem() {
Column {
Box(
modifier = Modifier
.height(500.dp)
.fillMaxWidth()
.padding(16.dp)
){
...
}
Spacer(modifier = Modifier.padding(8.dp))
}
}

@Composable
fun FooterCardItem() {
Column {
Box(
modifier = Modifier
.height(100.dp)
.fillMaxWidth()
.padding(16.dp)
){
...
}
Spacer(modifier = Modifier.padding(8.dp))
}
}

II. LazyGrid

With Compose, we can easily create grids using the Grid composable and its variants, such as LazyVerticalGrid and LazyHorizontalGrid with lazy loading capabilities.

We can define rows and columns in a grid by using the following types:

columns for LazyVerticalGrid and rows for LazyHorizontalGrid

— Adaptive: Adjusts the size of rows or columns based on content and available space.

--> (columns = GridCells.Adaptive(minSize = 128.dp))
--> (rows = GridCells.Adaptive(minSize = 128.dp))

— FixedSize: Specifies a fixed size for rows or columns.

--> (columns = GridCells.FixedSize(100.dp))
--> (rows = GridCells.FixedSize(100.dp))

— Fixed: Sets a fixed number of rows or columns.

--> (columns = GridCells.Fixed(4))
--> (rows = GridCells.Fixed(4))
--> (columns = StaggeredGridCells.Fixed(2)),
@Composable
fun ExampleVGrid(data: List<String>) {

LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 128.dp),
contentPadding = PaddingValues(8.dp)
) {
items(data.size) { index ->
Card(
modifier = Modifier
.padding(4.dp)
.fillMaxWidth(),
) {
Text(
text = data[index],
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
modifier = Modifier.padding(16.dp)
)
}
}

}

}

III. Flow Layout

Flow layout helps us arrange our elements in a natural flow. We have FlowColumn and FlowRow to arrange vertically and horizontally.

Note: FlowRow and FlowColumn are experimental.

Read more about FlowLayout

Kt. Academy

Nested LazyColumn in Jetpack Compose was originally published in Kt. Academy on Medium, where people are continuing the conversation by highlighting and responding to this story.

Continue ReadingNested LazyColumn in Jetpack Compose

The Lesson we Skipped in Jetpack Compose ☄

The Lesson You Skipped in Jetpack Compose ☄

Core concepts of Composables and its helping hands — Episode III

We have used Composable in the last episodes of the Jetpack Compose Series. Now, Let’s see what happens under the hood.

When we annotate @Composable we tell the Compose compiler that this function is meant to convert data into UI elements. This rendering process happens in three key phases:

  1. Composition: Determines what UI elements to display. Compose execute composable functions to create a blueprint of UI.
  2. Layout: Determines where to position UI elements. This phase includes measurement and placement steps, where elements are sized and positioned within a 2D coordinate system.
  3. Drawing: Renders the UI elements onto a Canvas/ Device Screen.
Source: .

State Hoisting

It is the way of making a component stateless by relocating its state to a higher level in the component hierarchy.

Let’s remake the above EditProfileScreen.kt

@Composable
fun EditProfileScreen() {
var fullName by remember { mutableStateOf("") }

// state hoisting
EditProfileContent(fullName, onFullNameChange = { fullName = it })
}

@Composable
fun EditProfileContent(fullName: String, onFullNameChange: (String) -> Unit) {

Column(
Modifier
.background(Color.White)
.padding(30.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally
) {

OutlinedTextField(
value = fullName,
onValueChange = onFullNameChange,
label = { Text("Full Name") }
)
Spacer(modifier = Modifier.padding(20.dp))
Text(text = fullName)
}
}

The general pattern for state hoisting is to replace the state variable with two parameters:

  • value: T: the current value to display
  • onValueChange: (T) -> Unit: an event that requests the value to change, where T is the proposed new value
State & Event of EditProfileScreen

The pattern where the state goes down, and events go up is called a unidirectional data flow.

A hoisted state offers several key advantages:

- SingleSource : Centralizing the state reduces errors, providing a single, reliable sourc
- Encapsulated: State changes are restricted to specific composables, ensuring internal management.
- Shareable: The hoisted state can be accessed by multiple composables, facilitating seamless data sharing.
- Interceptable: Callers can intercept or modify events before they affect the state, allowing for customized handling.
- Decoupled: State for stateless composables can reside anywhere, promoting separation of concerns.

Just Keep In Mind

  • Composable shouldn’t break the unidirectional data flow.
  • Composable functions can be executed in any order or parallel.
  • Recomposition is optimistic and optimizes by skipping unnecessary functions.
  • Composables can run frequently, even every animation frame.

Okay Then… ⚠️

What if we need a service that needs to be initialized or created on the composables screen? Will it recompose multiple times?

Yes, there are scenarios where we must interact with the external world or perform certain actions unrelated to UI rendering. These states triggering in every recomposition can lead to unexpected application behaviour. 🫠

“We have a problem, We have introduced another problem to fix it” — Jetpack Compose Engineers👺

To prevent these unexpected issues in composable functions when recomposition occurs, we use effect-handlers

Effect-Handlers 🔧

Effect-Handlers in Jetpack Compose are mechanisms used to manage side effects in the UI. They facilitate the execution of tasks that are not directly related to rendering the user interface, such as network requests, database operations, or animations.

*Dealing with Effect-handlers were a challenge for developers until this article.

Effect handlers enhance performance, maintainability, and debugging by separating non-UI tasks from UI rendering logic.

There are two types of effect handlers:

  1. Suspended Effect Handler: for suspending functions.
     — LaunchEffect
     — rememberCoroutineScope
  2. Non-suspended Effect Handler: for non-suspending functions
     — DisposableEffect
     — SideEffect

LaunchEffect

It is the commonly used side effect in Jetpack Compose. It triggers when a composition first starts and can execute suspend functions.

It accepts two parameters: a key and a coroutineScope block.

  • We can use any state in the key parameter.
  • Inside the coroutineScope block, we can use suspended or non-suspended functions.
  • LaunchEffect runs only once in the composable function.
  • To run the LaunchEffect block again, you can provide a state that changes over time in the key parameter.

Suppose, we need to init DataManager in HomeScreen composable.

@Composable
fun HomeScreen(viewModel: HomeViewModel = hiltViewModel()) {
// This key can be changed as necessary like using counter
LaunchedEffect(Unit) {
// Here Unit will Trigger Only 1 Time
viewModel.sentMessageSeenStatus()

}

//....
}

rememberCoroutineScope()

rememberCoroutineScope() creates a coroutine scope tied to the current composable, ensuring proper lifecycle management for launched coroutines.

Suppose we need to call some suspended function from composables.

@Composable
fun ResetPasswordScreen(viewModel: AuthViewModel = hiltViewModel()) {

// Remember the coroutine scope tied to this composable's lifecycle
val coroutineScope = rememberCoroutineScope()

LaunchedEffect(Unit) {
coroutineScope.launch {
viewModel.startReSendTokenTimer()
}
}

//....
}

This ensures that any coroutines launched within this scope are automatically canceled when the LoginScreen composable is removed from the composition.

Disposable-effect

DisposableEffect() operates similarly to LaunchedEffect(). Both trigger their side effects upon entering the composition and rerun them when the specified keys change.

The difference is that DisposableEffect allows us to specify cleanup logic that will be executed when the composable is removed from the composition.

@Composable
fun TrackingScreen() {
val context = LocalContext.current
val scope = rememberCoroutineScope()
var data by remember { mutableStateOf<SensorMetaData?>(null) }

DisposableEffect(Unit) {
val sensorMetaManager = SensorMetaManager(context)
sensorMetaManager.init()

val job = scope.launch {
sensorMetaManager.data
.receiveAsFlow()
.onEach {
data = it
}
.collect()
}

onDispose {
sensorMetaManager.unRegisterSensors()
job.cancel()
}
}
}

This is useful for scenarios where you need to perform cleanup or cancel ongoing operations when the composable is no longer needed.

Side-effects

It is used to perform side effects that don’t depend on the composable’s inputs. It’s useful for executing actions like logging, analytics, or interactions with external systems.

The SideEffect function allows you to perform side effects during composition. It is executed each time the composable is recomposed. For example:

@Composable
fun LoginScreen() {

SideEffect {
Log.d("LoginScreen", "Starting login screen...")
}

}

Note: Avoid executing non-composable code within a composable function; Always use side effects for such tasks.

State Management with Kotlin Flow

Flows in Kotlin allow for the sequential retrieval of multiple values from coroutine-based asynchronous tasks. They are particularly useful for scenarios like receiving a stream of data over a network connection.

val devicesFlow: Flow<String> = flowOf("Android", "IOS", "Web")
Figure: devices String Flow in components
  • Producers: Responsible for providing the data that makes up the flow.
  • Intermediaries: Operators are applied to manipulate the flow stream between the producer and consumer.
  • Consumers: Collect and process the values emitted by the producer.

Types of Flows

  • Cold Flows: Producer code executes only when a consumer begins collecting values. It’s like a Water Tap that only releases water when someone turns it on.
  • Hot Flows: Immediately emit values upon creation, regardless of consumer status.

Ways of Creating Flow

  1. Using the flow builder function

You can create a flow using the flow builder function. Inside the flow block, you can emit values using the emit function.

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

fun simpleFlow(): Flow<Int> = flow {
for (i in 1..5) {
emit(i)
}
}

2. Converting collections to Flow

You can convert collections (such as lists or arrays) to a Flow using the asFlow() extension function.

import kotlinx.coroutines.flow.asFlow

fun listToFlow(): Flow<Int> {
val list = listOf(1, 2, 3, 4, 5)
return list.asFlow()
}

3. Using flowOf function

You can use the flowOf function to create a Flow with predefined values.

import kotlinx.coroutines.flow.flowOf

fun predefinedFlow(): Flow<Int> = flowOf(1, 2, 3, 4, 5)

StateFlow and SharedFlow:

StateFlow and SharedFlow are both implementations of hot flows that can be used for managing state and sharing data between different parts of the application.

StateFlow represents a flow of state values that emits the latest value immediately and then only emits subsequent values upon change.

SharedFlow allows multiple collectors and can buffer and replay values for new subscribers.

Converting a flow from Cold to Hot

A cold flow can be made hot by calling the shareIn() function on the flow. This call requires a coroutine scope in which to execute the flow, a replay value, and a start policy setting indicating the conditions under which the flow is to start and stop. The available start policy options are as follows:

  • SharingStarted.WhileSubscribed() — The flow is kept alive as long as it has active subscribers.
  • SharingStarted.Eagerly() — The flow begins immediately and remains active even in the absence of active subscribers.
  • SharingStarted.Lazily() — The flow begins only after the first consumer subscribes and remains active even in the absence of active subscribers.
fun getColdDevicesFlow(): Flow<String> = flow {
val devices = listOf("Android", "iOS", "Web", "Smart TV")
for (device in devices) {
emit(device)
delay(2000) // Emit every 2 second
}
}.shareIn(viewModelScope, SharingStarted
.WhileSubscribed(replayExpirationMillis = 0))

MutableStateFlow

It is a type of state flow in Kotlin coroutines, often used in Jetpack Compose for managing mutable states. Unlike regular state flows, MutableStateFlow allows you to change its value programmatically using its value property. This makes it ideal for representing mutable states in your app. It emits values sequentially over time and observers can track changes to its value.

class ProfileViewModel : ViewModel() {
private val _deviceFlow = MutableStateFlow("Android")
val deviceFlow: StateFlow<String> = _deviceFlow.asStateFlow()

fun updateText(newText: String) {
_deviceFlow.value = newText
}
}

@Composable
fun ProfileScreen(viewModel: ProfileViewModel = viewModel()) {
val deviceFlow = viewModel.deviceFlow.collectAsState()
// Use deviceFlow.value in your UI
}

Flows (Unlike LiveData) don’t naturally understand Android’s lifecycle because they’re from Kotlin, not Android.

However, we can handle this by collecting flow values responsibly within lifecycle scopes using coroutines or other methods. It helps applications by facilitating asynchronous programming, state management, and composition of UI components reactively and efficiently.

We will look into the usages of Flow in the upcoming series. From requesting API to storing in the room & rendering in LazyColumn.

Hope you will see it in FYP! 🗿

Keep Learning, Keep Composing…

Kt. Academy Open Workshops

The Lesson we Skipped in Jetpack Compose ☄ was originally published in Kt. Academy on Medium, where people are continuing the conversation by highlighting and responding to this story.

Continue ReadingThe Lesson we Skipped in Jetpack Compose ☄

Hello Kotliner,

Long time no see!

The first two months of 2024 have passed! We hope they went well for you, and even better things are about to come.
In this newsletter, we’ll dive deep into Kotlin and Jetpack Compose, and we’ll exercise our Kotlin skills.
Have fun! 🛝

Articles

Effective Kotlin Item 20: Do not repeat common algorithms.
Learn the importance of extracting utility functions for your programs to avoid reinventing the wheel and optimising your code. Goodbye to repetitive algorithms!
👉 Go to the article

Effective Kotlin Item 25: Each function should be written in terms of a single level of abstraction.
Why each function should be written in terms of a single level of abstraction and how to achieve it.
👉 Go to the article

Effective Kotlin Item 26: Use abstraction to protect code against changes.
Get to know the facts about how we abstract elements, and what are the consequences.
👉 Go to the article

Abstraction design: Introduction
Discover the essence of abstraction and its role as the article by Marcin Moskala unfolds the principles behind creating and using abstractions.
👉 Go to the article

Kotlin Coroutines dispatchers — update.
What is abstraction in programming and why is it so important? Discover the essence of abstraction and its role as the article by Marcin Moskala unfolds the principles behind creating and using abstractions.
👉 Go to the article

Jetpack Compose I: Beginner’s Series.
The only Android Jetpack Compose guide you need. A straightforward insight leads you from foundational basics to advanced.
👉 Go to the article

Jetpack Compose II: Compose Navigation.
Learn about Nested, BottomAppBar, and Dialog Navigation — in Jetpack Compose.
👉 Go to the article

Kotlin Exercises

  • Pretty Time Display
    Do you know the secondToPrettyTime function in Kotlin?
    Give it a try with this exercise. 👇
    Playground
  • Best students list
    Implement makeBestStudentsList function to display the best 10 students, so they can get an internship.
    Take a moment and try to solve it. 👇
    Playground

Kotlin OPEN Workshops

KOTLIN PRO
For Kotlin Developers who want to master everyday Kotlin usage.

  • Dates: 26–28 March 2024
  • Times: 9:00–17:00 UTC+1
  • Fee: 400 euros
  • Registration Here

KOTLIN COROUTINES
Workshop covering asynchronous programming in Kotlin using Kotlin Coroutines.

  • Dates: 9–10 April 2024
  • Times: 9:00–17:00 UTC+2
  • Fee: 300 euros
  • Registration Here

KOTLIN MASTERY
For experienced Kotlin developers who want to master advanced Kotlin usage.

  • Dates: 13–14 May 2024
  • Times: 9:00–17:00 UTC+2
  • Fee: 300 euros
  • Registration Here

Last but not least — we’ve been diligently working to analyze your awesome feedback and keep up with the latest trends and developments in Kotlin. We’re super excited to share with you the outcome — brand new workshops, redesigned with your unique needs in mind! Feel free to dive in and explore. Happy learning!

Download PDF 👇

https://drive.google.com/file/d/13asOxpTAy66-aX7dRZfI7Gk8TcKGGt0X/view


Hello Kotliner, was originally published in Kt. Academy on Medium, where people are continuing the conversation by highlighting and responding to this story.

Continue ReadingHello Kotliner,

Kotlin Advanced | Kotlin Basics | Jetpack Compose | Open Workshops

Hello Kotliner!

Today we would like to share some really interesting articles and exciting news.

Curious?

Check it all out!

The second edition of Effective Kotlin

We are very excited to present you with the new, second edition of the Effective Kotlin book. New Items, new examples, and only the latest Kotlin best practices all in one place!


Kotlin Advanced | Kotlin Basics | Jetpack Compose | Open Workshops was originally published in Kt. Academy on Medium, where people are continuing the conversation by highlighting and responding to this story.

Continue ReadingKotlin Advanced | Kotlin Basics | Jetpack Compose | Open Workshops

End of content

No more pages to load