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 ☄

Android Jetpack Compose Review

Android Jetpack Compose Review

During the last Google I/O the Android team officially announced a brand new Jetpack library named Compose. Trust me when I say this library could change the way developers write Android applications in the future. So let’s review it and write down some thoughts about it.

Android Jetpack Compose Review

Disclaimer: 22 of May 2019, After publishing this blog post the Jetpack Compose team contacted to shed light on some parts  of the post. We recommend you to review them carefully because they point some parts of the implementation they couldn’t cover during the Google I/O talk or the official documentation.

Jetpack Compose aims to be a declarative framework to build Android user interfaces easily using a Kotlin like this:

@Composable
fun RallyApp() {
    RallyTheme {
        Scaffold(appBar = { RallyAppBar() }) {
            RallyBody()
        }
    }
}

@Composable
fun RallyAppBar() {
    Row {
        Text(text = title, style = +themeTextStyle { h4 })
    }
}

@Composable
fun RallyBody() {
    Padding(padding = 16.dp) {
        Column {
            RallyAlertCard()
            HeightSpacer(height = 10.dp)
            RallyAccountsCard()
            HeightSpacer(height = 10.dp)
            RallyBillsCard()
        }
    }
}

The idea is cool, isn’t it? If you are a web developer, you might be familiar with this idea because, years ago, React by Facebook already did this. Based on the concept of web-components, Facebook developers created a framework to be able to build web-based applications in a similar way. However, there are some small details we would like to review 😃

After watching the talk at Google I/O, reading the documentation, and playing with the official repository where this new library is being developed, we’ve got some thoughts we’d like to share with you.

API Design

Once you watch the talk, you notice the Jetpack Compose team has been thinking a lot about the current Android API. They remark how the usage of extensions for some components, and bad decisions they made in the past, doesn’t let the Android API properly evolve. From talking about code reusability to how the usage of the classic OOP design they followed is not generating a maintainable API over time. The team reviews how the current SDK encourage Android devs to keep the state of the app in the view implementation instead of having a single source of truth. After that quick review, they proposed a solution. Inspired by React/Redux, they suggest Jetpack Compose as the solution where developers will no longer create classes extending from the framework but composing their UI using the framework components. And this is awesome!!! However, once you look closer, you start finding some friction points we hope the Google team will solve before the first public release.

The first point we’d like to review is the usage of functions for the components’ declaration. Here you have an example:

@Composable
fun RallyBillsCard(): Unit {
    Card(color = cardInternalColor) {
        Column {
            Padding(padding = 12.dp) {
                Column {
                    Text(text = "Bills", style = +themeTextStyle { subtitle2 })
                    Text(text = "$1,810.00", style = +themeTextStyle { h1 })
                }
            }
        }
    }
}

As you can see, a component can be declared using a function. This might look like an excellent idea, however, we should think about the state of the view. Even when during the talk they mention the usage of a single source of truth and the usage of the lexical scope for the component function, having a local state for our views should be interesting. If instead of using a function for the class declaration we’d use a class we could handle local state in our components easily. With the current implementation, we’d have to wrap all these functions inside a class and keep their state linked to a host component. This is to be able to keep the state in the expected scope outside the setContent method you will find in activities and fragments. Otherwise, the state will be reset once the UI is rendered again, and this forces us to move all the UI state into the app state.

Another interesting point related to the usage of functions is the lack of components lifecycle. If we don’t have a separated lifecycle, how are we going to know if this is the first time we are rendering this component, or it was rendered before? Again, the answer could be to move the state of the view to the app state.

The other small detail can be found in the signature function:

@Composable
fun RallyBillsCard(): Unit {
...
}

Returning Unit will limit the API design from the testing viewpoint. Implementing the API as a huge side effect instead of deferring the computation until the view has to be rendered will not let us test this code using a unit/integration testing approach but the classic UI testing strategy. If you don’t believe me, do what I did. Go to the repository and try to test the code. You can find some examples of tests already written by the team inside the androidTests folder. You’ll see how the usage of an activity just for testing purposes let them write tests asking for the size of the view but not for the information the view is rendering or the style being applied in a friendly way.

If we could just get an instance of the components tree we could make assertions using libraries like Kotlin Snapshot, in the same way, React and Vue.js developers have been doing for the last years. We could write regular unit tests or snapshot tests like these:

fun testRendersTheTitleAsPartOfTheRallyAppBarComponent() {
   val title = "Any title"
   
   val component = RallyApp(title)
   
   assertEquals(title, component.title.text)
}

fun testRendersTheRallyAppBarComponent() {
   val component = RallyApp(title )
   
   component.matchWithSnapshot()
}

@Composable
fun RallyAppBar(title: String) {
    Row {
        Text(text = title, style = +themeTextStyle { h4 })
    }
}

Even when this testing approach is not a silver bullet and we might need to reduce the testing scope to get readable snapshots. Returning the components could simplify the way Android devs test their applications. Even if you don’t think snapshot testing could be a good testing strategy, a good design should let the developer choose the scope of the test or the testing strategy we could use for our automated test suite. However, if we keep returning Unit this will not be possible. If you don’t know what snapshot testing is, take a look at this link.

On the other hand, the usage of return values for our @Composable functions would let us create a truly declarative view tree we could optimize and evaluate in run-time or even in build-time when needed. As you can see, there are some benefits we should consider before the first public release of this library.

Additional notes to the component lifecycle:

After publishing this blog post the Jetpack Compose team contacted me to review this part of the content. They sent me a few notes about some parts of the post:

“We also use Effects for dealing with lifecycle related things. We have a few primitives that you can build a lot on top of to handle this.  For example the `onCommit` and the `onActive` effects. `onActive` runs the first time the component composes, and never after that. In the callback you can also define an `onDispose` callback which will get called as the component is leaving the hierarchy. Similarly, there’s an `onDispose` effect that will just do the latter. `onCommit` is a little more multi-purpose. With no input parameters, It will run every time the component composes. You can also add input parameters, and it will run any time any of them change. This can be useful for a couple of different scenarios.”

@Composable
fun UserProfile(userId: Int) {
   val user = +state<User?>(userId) { null }
   +onCommit(userId) {
       val cancellationToken = UserAPI.get(userId) {
           user.value = it
       }
       onDispose {
           UserAPI.cancel(cancellationToken)
       }
   }
   if (user == null)  {
     Loading()
     return
   }
  Text(text=user.name)
  Image(src=user.photo)
}

“Here we used `userId` as a parameter of `onCommit` so that this code will execute only when the parameter passed into `UserProfile` changes, so we don’t call the API too many times, for instance.”

“You can do some interesting things with this, including building your own more complex reusable effects (again, the concept of “Effect” I believe will eventually get unified with that of a `@Composable` function, so the syntax and keywords and everything will just be the same, but right now they are different).”

“I actually am a fan of snapshot testing, but just don’t think that snapshot testing virtual DOM is the right thing to be doing. I view virtual DOM as an implementation detail and think there can be a lot of problems relying on assertions of shallow renderings of components. I since have come to the conclusion that shallow rendering is probably a mistake. Similarly, I think that anyone relying on the return values of render functions (or composable functions in this case) in tests is probably a bit of a code smell.”

No silver bullets. You still need activities and fragments

During the Google I/O talk, they introduced Compose as a new set of Jetpack UI widget without Activities or Fragments. Just composable components. However, your code will need a component like an Activity, View or Fragment to be able to make it work. This will help you a lot if you want to integrate the usage of the library in just part of your app, but this means any developer using Jetpack Compose will have to learn the old school Android/Fragment/View API. At least, until they develop other components wrapping the navigation or some UI patterns like the usage of tabs into Composable components. This is how an activity using a component would look like:

class RallyActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            AppComponent()
        }
    }

This part will let you use this library in a friendly and compatible way from the beginning and at the same time will work as a workaround for all the design issues you can find in the library.

Where is my state?

During the talk, the team mentioned a lot the usage of a single source of truth and the suggested the usage of a unidirectional data flow. However, this is not as easy as it looks like. To be able to use something like Redux, we will need to be able to create a way to manipulate the state in a friendly way and keep it in memory, being able to mutate it asynchronously when needed without concurrency issues. However, there are no tools like this right now. And this will not be an easy task to implement. The talk describes the usage of LiveData observable fields and data class marked with @Model annotations for the UI updates and the usage of mutable variables as part of the data declaration. Something like this:

@Model
//Look at the VAR username and VAR passwords
class LoginState(var username: String, var password: String) {
    val valid: Boolean get() = username.length > 0 && password.length > 0
    fun login() = Api.login(username, password)
}

Even if we are able to create observable classes using mutable state you still need to keep this class linked to an Activity lifecycle or a Fragment lifecycle because there is no such thing as a unique store you can handle easily for now. So I don’t fully understand how they want to get rid of the state being part of activities and fragments when the proposed implementation doesn’t handle the state lifecycle as Redux already does. I hope they don’t suggest to keep the state as a mutable singleton in the future while using classes with mutable variables annotated with @Model.

Looking at the current state of the library, the only thing I can think about is all the features they still need to develop before considering the first public release. If you want to compare it with the current React/Redux state of the art you can review how React components lifecycle looks like and how these components have to be linked to Redux in these infographics Sergio Gutiérrez designed in a previous blog post:

The lifecycle of a React component:

Android Jetpack Compose Review

Integration between any React component and Redux:

Android Jetpack Compose Review

If you compare it with the current state of the library, there are a lot of pull requests they still have to send before the first release. Maybe, in the future, we can see how they connect Jetpack Compose with libraries like kotlin-redux to be able to handle unidirectional data flows and Arrow for the lenses usage, recursion schemes, side effects, and simple data-types.

Additional notes to the state management:

After publishing this blog post the Jetpack Compose team contacted me to review this part of the content. They sent me a few notes about some parts of the post:

“This isn’t where we’d like people to go, but I can see how this could be easily misinterpreted. Will work on messaging here. We may often hear of “top-down data flow” or “unidirectional data flow” to describe this pattern, and the latter is probably the more accurate term. Composable functions can have local state. Data for a composable function (or “props” to use a React term) is provided to the composable via function parameters, but local state scoped to the composable can be introduced by leveraging Compose’s memoization capabilities. Right now in the repo we have a concept called “Effects” (which may be unifying with Composable functions in the future). You can use these to introduce local state now, either by creating an @Model state class and introducing it with memo, or by using any type and introducing it using state. For example:”

@Composable fun Counter() {
  // introduce a state value (of type `Int`, with initial value of `0`.
  // Note: the `+` syntax is temporary
  val count = +state { 0 }
  // use it to compose your UI. pass it into other composables as parameters
  Text(text="Count: ${count.value}")
  // modify the value inside of event handlers, for instance
  Button(text="Increment, onClick = { count.value += 1; })
}

“Alternatively, use the @Model and memo like I mentioned:”

@Model class CounterState(var count: Int)
@Composable fun Counter() {
  // introduce a CounterState value. Instance will be preserved across compositions.
  // Note: the `+` syntax is temporary
  val count = +memo { Counter(0) }
  // use it to compose your UI. pass it into other composables as parameters
  Text(text="Count: ${count.count}")
  // modify the value inside of event handlers, for instance
  Button(text="Increment, onClick = { count.count += 1; })
}

What about the performance?

Even when they talk about the implementation of a list of components, and they take into account the performance during the talk. They don’t go deep into the library implementation details. They describe the possible implementation of a ScrollingList but if you review the repository there is no such component implemented for now. They describe a promising component like this:

@Composable
fun NewsFeed(stories: LiveData<List<StoryData>>) {
    ScrollingList(stories.observe()) { story ->
        StoryWidget(story)
    }
}

However, this is not part of the repository right now and we will have to wait until a new release in order to check if the suggested implementation looks like this or not.

Also, related to the usage of Unit as return value has another important implication here. As the team is not designing the components API as a set of data we can manipulate easily, there is no way we can compare an already rendered view with a new version of this view with just a bunch of modified values in order to implement an efficient UI rendering from the outside. And this could be really important depending on the final implementation! If you review the React ecosystem, you’ll see how the usage of types for the Virtual DOM design let Facebook engineers implement a smooth UI thanks to the partial update of the device DOM using an efficient and fast diff algorithm. You can read about the Virtual DOM implementation here if you want.

I guess there is an inner mechanism inside the rendering code making all these components efficient when talking about drawing. However, I can’t talk about it because it wasn’t part of the talk and the code I’ve reviewed seems to use canvas API for the component’s rendering.

What if I don’t want to use Jetpack Compose?

Don’t you worry if you want to code like this, but you don’t have time to wait for the final release. You can always create custom views you can compose in your old school layouts and link them with the Jetpack Data Binding library. If you implement your custom view using a single rendering binding method and link the view with a single source of truth implemented using any Redux library you’ll get all the benefits. Don’t forget the layout and your custom views is something you can already compose easily. Keeping the state of the app as part of your model and implementing your custom views as a class with a render function will give you a similar result. Even if you don’t want to use custom views,  you can already do this with your view holders if you don’t follow the Google official guidelines and implement your RecyclerView’s view holder without exposing the internal views and with a method to render the data passed as a parameter. This solution is not based on a nice Kotlin DSL, but might be worthy for you.

Conclusions

The Android team is doing it great. Believe me when I say that this Compose API is way better than the current state of the art of Android development. If you don’t think so try to write a React/ReactNative/Vue application and you’ll better understand what using components like these will be in the future.

Keep in mind that even when this library will help us a lot in the future, the rest of the SDK will have to evolve similarly if we want to improve the way we write our apps. Apart from the design changes the Android Team will implement in the future in the Compose API,  there are a lot of points to enhance in the rest of the SDK: stop returning the execution of the libraries calls as part of the “onActivityResult” methods, let the users get an instance of an activity without initialising the activity lifecycle (really useful for testing), create a friendly API for the animated transitions, etc.

Last but not least, we think releasing a sneak peek like this of the source code, and the examples, is one of the best things Google did. This talk, documentation, and source code let them gather tons of feedback they use for future improvements. There are a lot of things to develop and I hope we will be able to use Compose in all our apps in the future.

References

Here you can find some useful resources about this topic:

Continue ReadingAndroid Jetpack Compose Review

Android Jetpack Compose Review

Android Jetpack Compose Review

During the last Google I/O the Android team officially announced a brand new Jetpack library named Compose. Trust me when I say this library could change the way developers write Android applications in the future. So let’s review it and write down some thoughts about it.

Android Jetpack Compose Review

Disclaimer: 22 of May 2019, After publishing this blog post the Jetpack Compose team contacted to shed light on some parts  of the post. We recommend you to review them carefully because they point some parts of the implementation they couldn’t cover during the Google I/O talk or the official documentation.

Jetpack Compose aims to be a declarative framework to build Android user interfaces easily using a Kotlin like this:

@Composable
fun RallyApp() {
    RallyTheme {
        Scaffold(appBar = { RallyAppBar() }) {
            RallyBody()
        }
    }
}

@Composable
fun RallyAppBar() {
    Row {
        Text(text = title, style = +themeTextStyle { h4 })
    }
}

@Composable
fun RallyBody() {
    Padding(padding = 16.dp) {
        Column {
            RallyAlertCard()
            HeightSpacer(height = 10.dp)
            RallyAccountsCard()
            HeightSpacer(height = 10.dp)
            RallyBillsCard()
        }
    }
}

The idea is cool, isn’t it? If you are a web developer, you might be familiar with this idea because, years ago, React by Facebook already did this. Based on the concept of web-components, Facebook developers created a framework to be able to build web-based applications in a similar way. However, there are some small details we would like to review 😃

After watching the talk at Google I/O, reading the documentation, and playing with the official repository where this new library is being developed, we’ve got some thoughts we’d like to share with you.

API Design

Once you watch the talk, you notice the Jetpack Compose team has been thinking a lot about the current Android API. They remark how the usage of extensions for some components, and bad decisions they made in the past, doesn’t let the Android API properly evolve. From talking about code reusability to how the usage of the classic OOP design they followed is not generating a maintainable API over time. The team reviews how the current SDK encourage Android devs to keep the state of the app in the view implementation instead of having a single source of truth. After that quick review, they proposed a solution. Inspired by React/Redux, they suggest Jetpack Compose as the solution where developers will no longer create classes extending from the framework but composing their UI using the framework components. And this is awesome!!! However, once you look closer, you start finding some friction points we hope the Google team will solve before the first public release.

The first point we’d like to review is the usage of functions for the components’ declaration. Here you have an example:

@Composable
fun RallyBillsCard(): Unit {
    Card(color = cardInternalColor) {
        Column {
            Padding(padding = 12.dp) {
                Column {
                    Text(text = "Bills", style = +themeTextStyle { subtitle2 })
                    Text(text = "$1,810.00", style = +themeTextStyle { h1 })
                }
            }
        }
    }
}

As you can see, a component can be declared using a function. This might look like an excellent idea, however, we should think about the state of the view. Even when during the talk they mention the usage of a single source of truth and the usage of the lexical scope for the component function, having a local state for our views should be interesting. If instead of using a function for the class declaration we’d use a class we could handle local state in our components easily. With the current implementation, we’d have to wrap all these functions inside a class and keep their state linked to a host component. This is to be able to keep the state in the expected scope outside the setContent method you will find in activities and fragments. Otherwise, the state will be reset once the UI is rendered again, and this forces us to move all the UI state into the app state.

Another interesting point related to the usage of functions is the lack of components lifecycle. If we don’t have a separated lifecycle, how are we going to know if this is the first time we are rendering this component, or it was rendered before? Again, the answer could be to move the state of the view to the app state.

The other small detail can be found in the signature function:

@Composable
fun RallyBillsCard(): Unit {
...
}

Returning Unit will limit the API design from the testing viewpoint. Implementing the API as a huge side effect instead of deferring the computation until the view has to be rendered will not let us test this code using a unit/integration testing approach but the classic UI testing strategy. If you don’t believe me, do what I did. Go to the repository and try to test the code. You can find some examples of tests already written by the team inside the androidTests folder. You’ll see how the usage of an activity just for testing purposes let them write tests asking for the size of the view but not for the information the view is rendering or the style being applied in a friendly way.

If we could just get an instance of the components tree we could make assertions using libraries like Kotlin Snapshot, in the same way, React and Vue.js developers have been doing for the last years. We could write regular unit tests or snapshot tests like these:

fun testRendersTheTitleAsPartOfTheRallyAppBarComponent() {
   val title = "Any title"
   
   val component = RallyApp(title)
   
   assertEquals(title, component.title.text)
}

fun testRendersTheRallyAppBarComponent() {
   val component = RallyApp(title )
   
   component.matchWithSnapshot()
}

@Composable
fun RallyAppBar(title: String) {
    Row {
        Text(text = title, style = +themeTextStyle { h4 })
    }
}

Even when this testing approach is not a silver bullet and we might need to reduce the testing scope to get readable snapshots. Returning the components could simplify the way Android devs test their applications. Even if you don’t think snapshot testing could be a good testing strategy, a good design should let the developer choose the scope of the test or the testing strategy we could use for our automated test suite. However, if we keep returning Unit this will not be possible. If you don’t know what snapshot testing is, take a look at this link.

On the other hand, the usage of return values for our @Composable functions would let us create a truly declarative view tree we could optimize and evaluate in run-time or even in build-time when needed. As you can see, there are some benefits we should consider before the first public release of this library.

Additional notes to the component lifecycle:

After publishing this blog post the Jetpack Compose team contacted me to review this part of the content. They sent me a few notes about some parts of the post:

“We also use Effects for dealing with lifecycle related things. We have a few primitives that you can build a lot on top of to handle this.  For example the `onCommit` and the `onActive` effects. `onActive` runs the first time the component composes, and never after that. In the callback you can also define an `onDispose` callback which will get called as the component is leaving the hierarchy. Similarly, there’s an `onDispose` effect that will just do the latter. `onCommit` is a little more multi-purpose. With no input parameters, It will run every time the component composes. You can also add input parameters, and it will run any time any of them change. This can be useful for a couple of different scenarios.”

@Composable
fun UserProfile(userId: Int) {
   val user = +state<User?>(userId) { null }
   +onCommit(userId) {
       val cancellationToken = UserAPI.get(userId) {
           user.value = it
       }
       onDispose {
           UserAPI.cancel(cancellationToken)
       }
   }
   if (user == null)  {
     Loading()
     return
   }
  Text(text=user.name)
  Image(src=user.photo)
}

“Here we used `userId` as a parameter of `onCommit` so that this code will execute only when the parameter passed into `UserProfile` changes, so we don’t call the API too many times, for instance.”

“You can do some interesting things with this, including building your own more complex reusable effects (again, the concept of “Effect” I believe will eventually get unified with that of a `@Composable` function, so the syntax and keywords and everything will just be the same, but right now they are different).”

“I actually am a fan of snapshot testing, but just don’t think that snapshot testing virtual DOM is the right thing to be doing. I view virtual DOM as an implementation detail and think there can be a lot of problems relying on assertions of shallow renderings of components. I since have come to the conclusion that shallow rendering is probably a mistake. Similarly, I think that anyone relying on the return values of render functions (or composable functions in this case) in tests is probably a bit of a code smell.”

No silver bullets. You still need activities and fragments

During the Google I/O talk, they introduced Compose as a new set of Jetpack UI widget without Activities or Fragments. Just composable components. However, your code will need a component like an Activity, View or Fragment to be able to make it work. This will help you a lot if you want to integrate the usage of the library in just part of your app, but this means any developer using Jetpack Compose will have to learn the old school Android/Fragment/View API. At least, until they develop other components wrapping the navigation or some UI patterns like the usage of tabs into Composable components. This is how an activity using a component would look like:

class RallyActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            AppComponent()
        }
    }

This part will let you use this library in a friendly and compatible way from the beginning and at the same time will work as a workaround for all the design issues you can find in the library.

Where is my state?

During the talk, the team mentioned a lot the usage of a single source of truth and the suggested the usage of a unidirectional data flow. However, this is not as easy as it looks like. To be able to use something like Redux, we will need to be able to create a way to manipulate the state in a friendly way and keep it in memory, being able to mutate it asynchronously when needed without concurrency issues. However, there are no tools like this right now. And this will not be an easy task to implement. The talk describes the usage of LiveData observable fields and data class marked with @Model annotations for the UI updates and the usage of mutable variables as part of the data declaration. Something like this:

@Model
//Look at the VAR username and VAR passwords
class LoginState(var username: String, var password: String) {
    val valid: Boolean get() = username.length > 0 && password.length > 0
    fun login() = Api.login(username, password)
}

Even if we are able to create observable classes using mutable state you still need to keep this class linked to an Activity lifecycle or a Fragment lifecycle because there is no such thing as a unique store you can handle easily for now. So I don’t fully understand how they want to get rid of the state being part of activities and fragments when the proposed implementation doesn’t handle the state lifecycle as Redux already does. I hope they don’t suggest to keep the state as a mutable singleton in the future while using classes with mutable variables annotated with @Model.

Looking at the current state of the library, the only thing I can think about is all the features they still need to develop before considering the first public release. If you want to compare it with the current React/Redux state of the art you can review how React components lifecycle looks like and how these components have to be linked to Redux in these infographics Sergio Gutiérrez designed in a previous blog post:

The lifecycle of a React component:

Android Jetpack Compose Review

Integration between any React component and Redux:

Android Jetpack Compose Review

If you compare it with the current state of the library, there are a lot of pull requests they still have to send before the first release. Maybe, in the future, we can see how they connect Jetpack Compose with libraries like kotlin-redux to be able to handle unidirectional data flows and Arrow for the lenses usage, recursion schemes, side effects, and simple data-types.

Additional notes to the state management:

After publishing this blog post the Jetpack Compose team contacted me to review this part of the content. They sent me a few notes about some parts of the post:

“This isn’t where we’d like people to go, but I can see how this could be easily misinterpreted. Will work on messaging here. We may often hear of “top-down data flow” or “unidirectional data flow” to describe this pattern, and the latter is probably the more accurate term. Composable functions can have local state. Data for a composable function (or “props” to use a React term) is provided to the composable via function parameters, but local state scoped to the composable can be introduced by leveraging Compose’s memoization capabilities. Right now in the repo we have a concept called “Effects” (which may be unifying with Composable functions in the future). You can use these to introduce local state now, either by creating an @Model state class and introducing it with memo, or by using any type and introducing it using state. For example:”

@Composable fun Counter() {
  // introduce a state value (of type `Int`, with initial value of `0`.
  // Note: the `+` syntax is temporary
  val count = +state { 0 }
  // use it to compose your UI. pass it into other composables as parameters
  Text(text="Count: ${count.value}")
  // modify the value inside of event handlers, for instance
  Button(text="Increment, onClick = { count.value += 1; })
}

“Alternatively, use the @Model and memo like I mentioned:”

@Model class CounterState(var count: Int)
@Composable fun Counter() {
  // introduce a CounterState value. Instance will be preserved across compositions.
  // Note: the `+` syntax is temporary
  val count = +memo { Counter(0) }
  // use it to compose your UI. pass it into other composables as parameters
  Text(text="Count: ${count.count}")
  // modify the value inside of event handlers, for instance
  Button(text="Increment, onClick = { count.count += 1; })
}

What about the performance?

Even when they talk about the implementation of a list of components, and they take into account the performance during the talk. They don’t go deep into the library implementation details. They describe the possible implementation of a ScrollingList but if you review the repository there is no such component implemented for now. They describe a promising component like this:

@Composable
fun NewsFeed(stories: LiveData<List<StoryData>>) {
    ScrollingList(stories.observe()) { story ->
        StoryWidget(story)
    }
}

However, this is not part of the repository right now and we will have to wait until a new release in order to check if the suggested implementation looks like this or not.

Also, related to the usage of Unit as return value has another important implication here. As the team is not designing the components API as a set of data we can manipulate easily, there is no way we can compare an already rendered view with a new version of this view with just a bunch of modified values in order to implement an efficient UI rendering from the outside. And this could be really important depending on the final implementation! If you review the React ecosystem, you’ll see how the usage of types for the Virtual DOM design let Facebook engineers implement a smooth UI thanks to the partial update of the device DOM using an efficient and fast diff algorithm. You can read about the Virtual DOM implementation here if you want.

I guess there is an inner mechanism inside the rendering code making all these components efficient when talking about drawing. However, I can’t talk about it because it wasn’t part of the talk and the code I’ve reviewed seems to use canvas API for the component’s rendering.

What if I don’t want to use Jetpack Compose?

Don’t you worry if you want to code like this, but you don’t have time to wait for the final release. You can always create custom views you can compose in your old school layouts and link them with the Jetpack Data Binding library. If you implement your custom view using a single rendering binding method and link the view with a single source of truth implemented using any Redux library you’ll get all the benefits. Don’t forget the layout and your custom views is something you can already compose easily. Keeping the state of the app as part of your model and implementing your custom views as a class with a render function will give you a similar result. Even if you don’t want to use custom views,  you can already do this with your view holders if you don’t follow the Google official guidelines and implement your RecyclerView’s view holder without exposing the internal views and with a method to render the data passed as a parameter. This solution is not based on a nice Kotlin DSL, but might be worthy for you.

Conclusions

The Android team is doing it great. Believe me when I say that this Compose API is way better than the current state of the art of Android development. If you don’t think so try to write a React/ReactNative/Vue application and you’ll better understand what using components like these will be in the future.

Keep in mind that even when this library will help us a lot in the future, the rest of the SDK will have to evolve similarly if we want to improve the way we write our apps. Apart from the design changes the Android Team will implement in the future in the Compose API,  there are a lot of points to enhance in the rest of the SDK: stop returning the execution of the libraries calls as part of the “onActivityResult” methods, let the users get an instance of an activity without initialising the activity lifecycle (really useful for testing), create a friendly API for the animated transitions, etc.

Last but not least, we think releasing a sneak peek like this of the source code, and the examples, is one of the best things Google did. This talk, documentation, and source code let them gather tons of feedback they use for future improvements. There are a lot of things to develop and I hope we will be able to use Compose in all our apps in the future.

References

Here you can find some useful resources about this topic:

Continue ReadingAndroid Jetpack Compose Review

Android Jetpack Compose Review

Android Jetpack Compose Review

During the last Google I/O the Android team officially announced a brand new Jetpack library named Compose. Trust me when I say this library could change the way developers write Android applications in the future. So let’s review it and write down some thoughts about it.

Android Jetpack Compose Review

Disclaimer: 22 of May 2019, After publishing this blog post the Jetpack Compose team contacted to shed light on some parts  of the post. We recommend you to review them carefully because they point some parts of the implementation they couldn’t cover during the Google I/O talk or the official documentation.

Jetpack Compose aims to be a declarative framework to build Android user interfaces easily using a Kotlin like this:

@Composable
fun RallyApp() {
    RallyTheme {
        Scaffold(appBar = { RallyAppBar() }) {
            RallyBody()
        }
    }
}

@Composable
fun RallyAppBar() {
    Row {
        Text(text = title, style = +themeTextStyle { h4 })
    }
}

@Composable
fun RallyBody() {
    Padding(padding = 16.dp) {
        Column {
            RallyAlertCard()
            HeightSpacer(height = 10.dp)
            RallyAccountsCard()
            HeightSpacer(height = 10.dp)
            RallyBillsCard()
        }
    }
}

The idea is cool, isn’t it? If you are a web developer, you might be familiar with this idea because, years ago, React by Facebook already did this. Based on the concept of web-components, Facebook developers created a framework to be able to build web-based applications in a similar way. However, there are some small details we would like to review 😃

After watching the talk at Google I/O, reading the documentation, and playing with the official repository where this new library is being developed, we’ve got some thoughts we’d like to share with you.

API Design

Once you watch the talk, you notice the Jetpack Compose team has been thinking a lot about the current Android API. They remark how the usage of extensions for some components, and bad decisions they made in the past, doesn’t let the Android API properly evolve. From talking about code reusability to how the usage of the classic OOP design they followed is not generating a maintainable API over time. The team reviews how the current SDK encourage Android devs to keep the state of the app in the view implementation instead of having a single source of truth. After that quick review, they proposed a solution. Inspired by React/Redux, they suggest Jetpack Compose as the solution where developers will no longer create classes extending from the framework but composing their UI using the framework components. And this is awesome!!! However, once you look closer, you start finding some friction points we hope the Google team will solve before the first public release.

The first point we’d like to review is the usage of functions for the components’ declaration. Here you have an example:

@Composable
fun RallyBillsCard(): Unit {
    Card(color = cardInternalColor) {
        Column {
            Padding(padding = 12.dp) {
                Column {
                    Text(text = "Bills", style = +themeTextStyle { subtitle2 })
                    Text(text = "$1,810.00", style = +themeTextStyle { h1 })
                }
            }
        }
    }
}

As you can see, a component can be declared using a function. This might look like an excellent idea, however, we should think about the state of the view. Even when during the talk they mention the usage of a single source of truth and the usage of the lexical scope for the component function, having a local state for our views should be interesting. If instead of using a function for the class declaration we’d use a class we could handle local state in our components easily. With the current implementation, we’d have to wrap all these functions inside a class and keep their state linked to a host component. This is to be able to keep the state in the expected scope outside the setContent method you will find in activities and fragments. Otherwise, the state will be reset once the UI is rendered again, and this forces us to move all the UI state into the app state.

Another interesting point related to the usage of functions is the lack of components lifecycle. If we don’t have a separated lifecycle, how are we going to know if this is the first time we are rendering this component, or it was rendered before? Again, the answer could be to move the state of the view to the app state.

The other small detail can be found in the signature function:

@Composable
fun RallyBillsCard(): Unit {
...
}

Returning Unit will limit the API design from the testing viewpoint. Implementing the API as a huge side effect instead of deferring the computation until the view has to be rendered will not let us test this code using a unit/integration testing approach but the classic UI testing strategy. If you don’t believe me, do what I did. Go to the repository and try to test the code. You can find some examples of tests already written by the team inside the androidTests folder. You’ll see how the usage of an activity just for testing purposes let them write tests asking for the size of the view but not for the information the view is rendering or the style being applied in a friendly way.

If we could just get an instance of the components tree we could make assertions using libraries like Kotlin Snapshot, in the same way, React and Vue.js developers have been doing for the last years. We could write regular unit tests or snapshot tests like these:

fun testRendersTheTitleAsPartOfTheRallyAppBarComponent() {
   val title = "Any title"
   
   val component = RallyApp(title)
   
   assertEquals(title, component.title.text)
}

fun testRendersTheRallyAppBarComponent() {
   val component = RallyApp(title )
   
   component.matchWithSnapshot()
}

@Composable
fun RallyAppBar(title: String) {
    Row {
        Text(text = title, style = +themeTextStyle { h4 })
    }
}

Even when this testing approach is not a silver bullet and we might need to reduce the testing scope to get readable snapshots. Returning the components could simplify the way Android devs test their applications. Even if you don’t think snapshot testing could be a good testing strategy, a good design should let the developer choose the scope of the test or the testing strategy we could use for our automated test suite. However, if we keep returning Unit this will not be possible. If you don’t know what snapshot testing is, take a look at this link.

On the other hand, the usage of return values for our @Composable functions would let us create a truly declarative view tree we could optimize and evaluate in run-time or even in build-time when needed. As you can see, there are some benefits we should consider before the first public release of this library.

Additional notes to the component lifecycle:

After publishing this blog post the Jetpack Compose team contacted me to review this part of the content. They sent me a few notes about some parts of the post:

“We also use Effects for dealing with lifecycle related things. We have a few primitives that you can build a lot on top of to handle this.  For example the `onCommit` and the `onActive` effects. `onActive` runs the first time the component composes, and never after that. In the callback you can also define an `onDispose` callback which will get called as the component is leaving the hierarchy. Similarly, there’s an `onDispose` effect that will just do the latter. `onCommit` is a little more multi-purpose. With no input parameters, It will run every time the component composes. You can also add input parameters, and it will run any time any of them change. This can be useful for a couple of different scenarios.”

@Composable
fun UserProfile(userId: Int) {
   val user = +state<User?>(userId) { null }
   +onCommit(userId) {
       val cancellationToken = UserAPI.get(userId) {
           user.value = it
       }
       onDispose {
           UserAPI.cancel(cancellationToken)
       }
   }
   if (user == null)  {
     Loading()
     return
   }
  Text(text=user.name)
  Image(src=user.photo)
}

“Here we used `userId` as a parameter of `onCommit` so that this code will execute only when the parameter passed into `UserProfile` changes, so we don’t call the API too many times, for instance.”

“You can do some interesting things with this, including building your own more complex reusable effects (again, the concept of “Effect” I believe will eventually get unified with that of a `@Composable` function, so the syntax and keywords and everything will just be the same, but right now they are different).”

“I actually am a fan of snapshot testing, but just don’t think that snapshot testing virtual DOM is the right thing to be doing. I view virtual DOM as an implementation detail and think there can be a lot of problems relying on assertions of shallow renderings of components. I since have come to the conclusion that shallow rendering is probably a mistake. Similarly, I think that anyone relying on the return values of render functions (or composable functions in this case) in tests is probably a bit of a code smell.”

No silver bullets. You still need activities and fragments

During the Google I/O talk, they introduced Compose as a new set of Jetpack UI widget without Activities or Fragments. Just composable components. However, your code will need a component like an Activity, View or Fragment to be able to make it work. This will help you a lot if you want to integrate the usage of the library in just part of your app, but this means any developer using Jetpack Compose will have to learn the old school Android/Fragment/View API. At least, until they develop other components wrapping the navigation or some UI patterns like the usage of tabs into Composable components. This is how an activity using a component would look like:

class RallyActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            AppComponent()
        }
    }

This part will let you use this library in a friendly and compatible way from the beginning and at the same time will work as a workaround for all the design issues you can find in the library.

Where is my state?

During the talk, the team mentioned a lot the usage of a single source of truth and the suggested the usage of a unidirectional data flow. However, this is not as easy as it looks like. To be able to use something like Redux, we will need to be able to create a way to manipulate the state in a friendly way and keep it in memory, being able to mutate it asynchronously when needed without concurrency issues. However, there are no tools like this right now. And this will not be an easy task to implement. The talk describes the usage of LiveData observable fields and data class marked with @Model annotations for the UI updates and the usage of mutable variables as part of the data declaration. Something like this:

@Model
//Look at the VAR username and VAR passwords
class LoginState(var username: String, var password: String) {
    val valid: Boolean get() = username.length > 0 && password.length > 0
    fun login() = Api.login(username, password)
}

Even if we are able to create observable classes using mutable state you still need to keep this class linked to an Activity lifecycle or a Fragment lifecycle because there is no such thing as a unique store you can handle easily for now. So I don’t fully understand how they want to get rid of the state being part of activities and fragments when the proposed implementation doesn’t handle the state lifecycle as Redux already does. I hope they don’t suggest to keep the state as a mutable singleton in the future while using classes with mutable variables annotated with @Model.

Looking at the current state of the library, the only thing I can think about is all the features they still need to develop before considering the first public release. If you want to compare it with the current React/Redux state of the art you can review how React components lifecycle looks like and how these components have to be linked to Redux in these infographics Sergio Gutiérrez designed in a previous blog post:

The lifecycle of a React component:

Android Jetpack Compose Review

Integration between any React component and Redux:

Android Jetpack Compose Review

If you compare it with the current state of the library, there are a lot of pull requests they still have to send before the first release. Maybe, in the future, we can see how they connect Jetpack Compose with libraries like kotlin-redux to be able to handle unidirectional data flows and Arrow for the lenses usage, recursion schemes, side effects, and simple data-types.

Additional notes to the state management:

After publishing this blog post the Jetpack Compose team contacted me to review this part of the content. They sent me a few notes about some parts of the post:

“This isn’t where we’d like people to go, but I can see how this could be easily misinterpreted. Will work on messaging here. We may often hear of “top-down data flow” or “unidirectional data flow” to describe this pattern, and the latter is probably the more accurate term. Composable functions can have local state. Data for a composable function (or “props” to use a React term) is provided to the composable via function parameters, but local state scoped to the composable can be introduced by leveraging Compose’s memoization capabilities. Right now in the repo we have a concept called “Effects” (which may be unifying with Composable functions in the future). You can use these to introduce local state now, either by creating an @Model state class and introducing it with memo, or by using any type and introducing it using state. For example:”

@Composable fun Counter() {
  // introduce a state value (of type `Int`, with initial value of `0`.
  // Note: the `+` syntax is temporary
  val count = +state { 0 }
  // use it to compose your UI. pass it into other composables as parameters
  Text(text="Count: ${count.value}")
  // modify the value inside of event handlers, for instance
  Button(text="Increment, onClick = { count.value += 1; })
}

“Alternatively, use the @Model and memo like I mentioned:”

@Model class CounterState(var count: Int)
@Composable fun Counter() {
  // introduce a CounterState value. Instance will be preserved across compositions.
  // Note: the `+` syntax is temporary
  val count = +memo { Counter(0) }
  // use it to compose your UI. pass it into other composables as parameters
  Text(text="Count: ${count.count}")
  // modify the value inside of event handlers, for instance
  Button(text="Increment, onClick = { count.count += 1; })
}

What about the performance?

Even when they talk about the implementation of a list of components, and they take into account the performance during the talk. They don’t go deep into the library implementation details. They describe the possible implementation of a ScrollingList but if you review the repository there is no such component implemented for now. They describe a promising component like this:

@Composable
fun NewsFeed(stories: LiveData<List<StoryData>>) {
    ScrollingList(stories.observe()) { story ->
        StoryWidget(story)
    }
}

However, this is not part of the repository right now and we will have to wait until a new release in order to check if the suggested implementation looks like this or not.

Also, related to the usage of Unit as return value has another important implication here. As the team is not designing the components API as a set of data we can manipulate easily, there is no way we can compare an already rendered view with a new version of this view with just a bunch of modified values in order to implement an efficient UI rendering from the outside. And this could be really important depending on the final implementation! If you review the React ecosystem, you’ll see how the usage of types for the Virtual DOM design let Facebook engineers implement a smooth UI thanks to the partial update of the device DOM using an efficient and fast diff algorithm. You can read about the Virtual DOM implementation here if you want.

I guess there is an inner mechanism inside the rendering code making all these components efficient when talking about drawing. However, I can’t talk about it because it wasn’t part of the talk and the code I’ve reviewed seems to use canvas API for the component’s rendering.

What if I don’t want to use Jetpack Compose?

Don’t you worry if you want to code like this, but you don’t have time to wait for the final release. You can always create custom views you can compose in your old school layouts and link them with the Jetpack Data Binding library. If you implement your custom view using a single rendering binding method and link the view with a single source of truth implemented using any Redux library you’ll get all the benefits. Don’t forget the layout and your custom views is something you can already compose easily. Keeping the state of the app as part of your model and implementing your custom views as a class with a render function will give you a similar result. Even if you don’t want to use custom views,  you can already do this with your view holders if you don’t follow the Google official guidelines and implement your RecyclerView’s view holder without exposing the internal views and with a method to render the data passed as a parameter. This solution is not based on a nice Kotlin DSL, but might be worthy for you.

Conclusions

The Android team is doing it great. Believe me when I say that this Compose API is way better than the current state of the art of Android development. If you don’t think so try to write a React/ReactNative/Vue application and you’ll better understand what using components like these will be in the future.

Keep in mind that even when this library will help us a lot in the future, the rest of the SDK will have to evolve similarly if we want to improve the way we write our apps. Apart from the design changes the Android Team will implement in the future in the Compose API,  there are a lot of points to enhance in the rest of the SDK: stop returning the execution of the libraries calls as part of the “onActivityResult” methods, let the users get an instance of an activity without initialising the activity lifecycle (really useful for testing), create a friendly API for the animated transitions, etc.

Last but not least, we think releasing a sneak peek like this of the source code, and the examples, is one of the best things Google did. This talk, documentation, and source code let them gather tons of feedback they use for future improvements. There are a lot of things to develop and I hope we will be able to use Compose in all our apps in the future.

References

Here you can find some useful resources about this topic:

Continue ReadingAndroid Jetpack Compose Review

End of content

No more pages to load