<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/">
	<channel>
		<title>Jetpack Compose: A Turning Point for Native Android UI | Infinum</title>
		<atom:link href="https://infinum.com/blog/jetpack-compose-native-android/feed/" rel="self" type="application/rss+xml" />
		<link>https://infinum.com/blog/jetpack-compose-native-android/</link>
		<description>Building digital products</description>
		<lastBuildDate>Tue, 28 Apr 2026 14:41:35 +0000</lastBuildDate>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>

					<item>
				<image>
					<url>8152https://infinum.com/uploads/2021/08/jetpack-compose-native-android-0.webp</url>
				</image>
				<title>Jetpack Compose: A Turning Point for Native Android UI</title>
				<link>https://infinum.com/blog/jetpack-compose-native-android/</link>
				<pubDate>Mon, 16 Aug 2021 14:45:00 +0000</pubDate>
				<dc:creator>Josip Šalković</dc:creator>
				<guid isPermaLink="false">https://infinum.com/the-capsized-eight/jetpack-compose-native-android/</guid>
				<description>
					<![CDATA[<p>It&#8217;s well on its way to completely replace Google’s existing toolkit.</p>
<p>The post <a href="https://infinum.com/blog/jetpack-compose-native-android/">Jetpack Compose: A Turning Point for Native Android UI</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</description>
				<content:encoded>
					<![CDATA[<div
	class="wrapper"
	data-id="es-352"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-92">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-95"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-93">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-94'
	>
	After more than two years of development, Google’s modern toolkit for native UI reached its stable build.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-98"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-96">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-97'
	>
	Jetpack Compose is now officially 1.0.</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-101"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-99">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-100'
	>
	It’s official: Jetpack Compose is now officially a stable version, but it has actually been used in its pre-release state since 2020 – so much so that there were 2000+ apps using Compose in the Play Store at the time of the official release. I mean, even the Play Store app itself uses it.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-104"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-102">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-103'
	>
	We’ve <a href="https://infinum.com/blog/jetpack-compose-framework/">introduced Compose’s basics in a previous blog post</a>, so now is the perfect time to concentrate on some of its features in more detail.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-107"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-105">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-106'
	>
	This blog post covers the tool’s core functionality – State. We’ll get into composition and recomposition, stateful composables, making them stateless with state hoisting, and their integration with ViewModel. We’ll also introduce animations to make the UI more alive. Finally, we’ll write instrumental tests to demonstrate how to test Composables.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-110"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-108">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-109'
	>
	(Re)composition and state management</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-113"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-111">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-112'
	>
	State in Compose</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-116"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-114">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-115'
	>
	State is one of Compose’s core principles, declared as any value that can change over time. Let’s use the example from the previous blog post:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-118"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">@Composable</span><span class="token">
</span></span><span class="line"><span class="token">fun InfinumContent</span><span class="token">() {
</span></span><span class="line"><span class="token">   </span><span class="token">Surface</span><span class="token">(shape </span><span class="token" style="color: #d73a49;">=</span><span class="token"> MaterialTheme.shapes.large) {
</span></span><span class="line"><span class="token">       </span><span class="token">Row </span><span class="token">{
</span></span><span class="line"><span class="token">           </span><span class="token">Image</span><span class="token">(
</span></span><span class="line"><span class="token">               </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">           )
</span></span><span class="line"><span class="token">           </span><span class="token">Text</span><span class="token">(
</span></span><span class="line"><span class="token">               </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">           )
</span></span><span class="line"><span class="token">       }
</span></span><span class="line"><span class="token">   }
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-121"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-119">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-120'
	>
	Imagine that the composable content above initially only displays the Infinum logo and then a click reveals the word “Infinum”. It’s possible to achieve that effect with the help of State. However, first we need to explain how Composable runs composables (composition) and how it reacts to state changes (recomposition).</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-124"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-122">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-123'
	>
	Composition and recompositon</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-127"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-125">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-126'
	>
	Composition is the tree structure made up of composables used to describe the UI, accomplished by running composables. When we emit a user interface for the first time, that’s the initial composition. During that initial composition, Jetpack Compose tracks the composables that we called to describe the user interface.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-130"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-128">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-129'
	>
	Every time the app’s state changes, a recomposition takes place. It re-runs the composables to update the composition.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-133"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-131">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-132'
	>
	Stateful composables</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-136"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-134">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-135'
	>
	Every composable function has its own memory space. We can store mutable and immutable objects inside that memory space with the composable <em>remember</em>. The value computed by <em>remember</em> is stored in the composition during initial composition, and that stored value is returned during recomposition. If a composable function contains <em>remember</em>, that composable is called statefully composable because it holds a local state.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-139"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-137">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-138'
	>
	In our example the goal is to display only the Infinum logo initially and then show the word “Infinum” when a click is performed. We need to store a variable that represents the current state in the composable’s memory: whether the composable is expanded or not. To do this, add the following line inside the beginning of <em>InfinumContent</em>:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-141"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token">var </span><span class="token">expanded </span><span class="token" style="color: #d73a49;">by</span><span class="token"> </span><span class="token">remember </span><span class="token">{ </span><span class="token">mutableStateOf</span><span class="token">(</span><span class="token" style="color: #005cc5;">false</span><span class="token">) }
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-144"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-142">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-143'
	>
	The expanded boolean value is stored in the composable’s memory by calling <em>remember</em>. The function <em>mutableStateOf</em> used above is just an observable type in Compose. Every time that a value changes, Compose performs a recomposition of any composable function that reads that value. As you can see, during the initial composition our composability is not expanded.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-147"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-145">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-146'
	>
	We need to somehow change the expanded value on click. To do this, we add a modifier attribute on <em>Surface</em>:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-149"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token">Surface</span><span class="token">(
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">   modifier </span><span class="token" style="color: #d73a49;">=</span><span class="token"> Modifier</span><span class="token">.clickable </span><span class="token">{
</span></span><span class="line"><span class="token">       expanded </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #d73a49;">!</span><span class="token">expanded
</span></span><span class="line"><span class="token">   }
</span></span><span class="line"><span class="token">)
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-152"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-150">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-151'
	>
	Every time a user clicks on the surface, the variable <em>expanded</em> changes. However, no recomposition will take place when <em>expanded</em> changes because we’re not reading that value anywhere. This is the composable <em>Wrap Text</em> with an if-statement that checks the value of <em>expanded</em>:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-154"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">if</span><span class="token"> (expanded) {
</span></span><span class="line"><span class="token">   </span><span class="token">Text</span><span class="token">(
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">   )
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-157"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-155">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-156'
	>
	When the value of <em>expanded</em> changes, a recomposition will be scheduled. Compose is smart enough to update only the if-statement because it has read that value.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-159"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">@Composable</span><span class="token">
</span></span><span class="line"><span class="token">fun InfinumContent</span><span class="token">() {
</span></span><span class="line"><span class="token">   </span><span class="token">var </span><span class="token">expanded </span><span class="token" style="color: #d73a49;">by</span><span class="token"> </span><span class="token">remember </span><span class="token">{ </span><span class="token">mutableStateOf</span><span class="token">(</span><span class="token" style="color: #005cc5;">false</span><span class="token">) }
</span></span><span class="line"><span class="token">   </span><span class="token">Surface</span><span class="token">(
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">       modifier </span><span class="token" style="color: #d73a49;">=</span><span class="token"> Modifier</span><span class="token">.clickable </span><span class="token">{
</span></span><span class="line"><span class="token">           expanded </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #d73a49;">!</span><span class="token">expanded
</span></span><span class="line"><span class="token">       }
</span></span><span class="line"><span class="token">   ) {
</span></span><span class="line"><span class="token">       </span><span class="token">Row</span><span class="token">(
</span></span><span class="line"><span class="token">         </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">       ) {
</span></span><span class="line"><span class="token">           </span><span class="token">Image</span><span class="token">(
</span></span><span class="line"><span class="token">              </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">           )
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #d73a49;">if</span><span class="token"> (expanded) {
</span></span><span class="line"><span class="token">               </span><span class="token">Text</span><span class="token">(
</span></span><span class="line"><span class="token">                   </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">               )
</span></span><span class="line"><span class="token">           }
</span></span><span class="line"><span class="token">       }
</span></span><span class="line"><span class="token">   }
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-162"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-160"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-161">
	<picture class="image__picture block-media__image-picture">
												<img
					src="https://infinum.com/uploads/2021/08/jetpack-compose-native-android-1.gif"
					class="image__img block-media__image-img"
					alt=""
										height="320"
															width="640"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-165"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-163">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-164'
	>
	The function <em>remember</em> helps us retain a state through recomposition, but it doesn’t retain a state when a change in configuration occurs. If some data needs to be retained after configuration changes, you need to use <em>rememberSaveable</em>, equivalent to <em>onSaveInstanceState</em> in view-based UI. Of course, the best solution would be to store data inside <em>ViewModel</em>, whose integration will be explained later.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-168"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-166">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-167'
	>
	Stateless composables and state hoisting</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-171"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-169">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-170'
	>
	Our composable from the previous section <em>InfinumContent</em> is stateful because it holds a state. Stateful composables are hard to test and reuse because they are tightly coupled to that state. Instead, we should make our composables stateless and move that state out of the composable function. We can do that with state hoisting.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-174"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-172">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-173'
	>
	State hoisting is a programming pattern for moving the state higher up in the composition tree, usually to the caller, to make a composable stateless. The easiest way to do that is to replace the state with a function parameter and an event with a lambda function to do a particular action when the value changes.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-177"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-175">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-176'
	>
	Let’s make our <em>InfinumContent</em> stateless. Add a function parameter of the Boolean type that will represent the state and a lambda function to represent an event to be invoked when the state changes. This is how our <em>InfinumContent</em> looks like now:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-179"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">@Composable</span><span class="token">
</span></span><span class="line"><span class="token">fun InfinumContent</span><span class="token">(expanded</span><span class="token">: </span><span class="token" style="color: #6f42c1;">Boolean</span><span class="token">, onExpandedChange</span><span class="token">: (</span><span class="token" style="color: #6f42c1;">Boolean</span><span class="token">) -&gt; </span><span class="token" style="color: #6f42c1;">Unit</span><span class="token">) {
</span></span><span class="line"><span class="token">   </span><span class="token">Surface</span><span class="token">(
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">       modifier </span><span class="token" style="color: #d73a49;">=</span><span class="token"> Modifier</span><span class="token">.clickable </span><span class="token">{
</span></span><span class="line"><span class="token">           </span><span class="token">onExpandedChange</span><span class="token">(</span><span class="token" style="color: #d73a49;">!</span><span class="token">expanded)
</span></span><span class="line"><span class="token">       }
</span></span><span class="line"><span class="token">   ) {
</span></span><span class="line"><span class="token">       </span><span class="token">Row</span><span class="token">(
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">       ) {
</span></span><span class="line"><span class="token">           </span><span class="token">Image</span><span class="token">(
</span></span><span class="line"><span class="token">              </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">           )
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #d73a49;">if</span><span class="token"> (expanded) {
</span></span><span class="line"><span class="token">               </span><span class="token">Text</span><span class="token">(
</span></span><span class="line"><span class="token">                   </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">               )
</span></span><span class="line"><span class="token">           }
</span></span><span class="line"><span class="token">       }
</span></span><span class="line"><span class="token">   }
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-182"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-180">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-181'
	>
	Change the onCreate method inside MainActivity as follows:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-184"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">override</span><span class="token"> </span><span class="token">fun onCreate</span><span class="token">(savedInstanceState</span><span class="token">: </span><span class="token" style="color: #6f42c1;">Bundle</span><span class="token">?</span><span class="token">) {
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #005cc5;">super</span><span class="token">.onCreate</span><span class="token">(savedInstanceState)
</span></span><span class="line"><span class="token">   </span><span class="token">setContent </span><span class="token">{
</span></span><span class="line"><span class="token">       </span><span class="token">InfinumComposeAppTheme </span><span class="token">{
</span></span><span class="line"><span class="token">           </span><span class="token">var </span><span class="token">expanded </span><span class="token" style="color: #d73a49;">by</span><span class="token"> </span><span class="token">rememberSaveable </span><span class="token">{ </span><span class="token">mutableStateOf</span><span class="token">(</span><span class="token" style="color: #005cc5;">false</span><span class="token">) }
</span></span><span class="line"><span class="token">           </span><span class="token">InfinumContent</span><span class="token">(
</span></span><span class="line"><span class="token">               expanded </span><span class="token" style="color: #d73a49;">=</span><span class="token"> expanded,
</span></span><span class="line"><span class="token">               onExpandedChange </span><span class="token" style="color: #d73a49;">=</span><span class="token"> { expanded </span><span class="token" style="color: #d73a49;">=</span><span class="token"> it }
</span></span><span class="line"><span class="token">           )
</span></span><span class="line"><span class="token">       }
</span></span><span class="line"><span class="token">   }
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-187"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-185">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-186'
	>
	As you can see, the state is hoisted to the caller and we use <em>rememberSaveable</em> instead of remembering because we want to survive configuration changes. Also, pay attention to how we named the lambda function. It’s <em>onExpandedChange</em>, present tense, not <em>onExpandedChanged</em>. The state hasn’t changed yet and a composable function is requesting the event handler to change it. The state moves down (onCreate → InfinumContent) and the event moves up (<em>InfinumContent</em> → <em>onCreate</em>). That pattern is called unidirectional data flow and it’s one of the core principles in the composable world.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-190"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-188">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-189'
	>
	Integration with ViewModel</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-193"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-191">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-192'
	>
	Storing the state inside <em>rememberSaveable</em> is good enough for simple cases. When there are complex data objects involved, a better solution is to store that state inside the <em>ViewModel</em>. Compose provides support for <em>ViewModel</em> integration. We can move our state inside the <em>ViewModel</em>, expose it in an observable holder, and handle events that request state changes.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-196"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-194">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-195'
	>
	Create the following <em>ViewModel</em> class:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-198"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token">class MainViewModel</span><span class="token">: </span><span class="token" style="color: #6f42c1;">ViewModel</span><span class="token">() {
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #d73a49;">private</span><span class="token"> </span><span class="token">val </span><span class="token">expanded </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token">MutableLiveData</span><span class="token">(</span><span class="token" style="color: #005cc5;">false</span><span class="token">)
</span></span><span class="line"><span class="token">   </span><span class="token">val </span><span class="token">expandedHolder</span><span class="token">: </span><span class="token" style="color: #6f42c1;">LiveData</span><span class="token">&lt;</span><span class="token" style="color: #6f42c1;">Boolean</span><span class="token">&gt; </span><span class="token" style="color: #d73a49;">=</span><span class="token"> expanded
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token">fun onExpandedChange</span><span class="token">(newExpanded</span><span class="token">: </span><span class="token" style="color: #6f42c1;">Boolean</span><span class="token">) {
</span></span><span class="line"><span class="token">       expanded.</span><span class="token" style="color: #d73a49;">value</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> newExpanded
</span></span><span class="line"><span class="token">   }
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-201"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-199">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-200'
	>
	Refactor <em>onCreate</em> inside <em>MainActivity</em> as follows:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-203"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">override</span><span class="token"> </span><span class="token">fun onCreate</span><span class="token">(savedInstanceState</span><span class="token">: </span><span class="token" style="color: #6f42c1;">Bundle</span><span class="token">?</span><span class="token">) {
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #005cc5;">super</span><span class="token">.onCreate</span><span class="token">(savedInstanceState)
</span></span><span class="line"><span class="token">   </span><span class="token">setContent </span><span class="token">{
</span></span><span class="line"><span class="token">       </span><span class="token">InfinumComposeAppTheme </span><span class="token">{
</span></span><span class="line"><span class="token">           </span><span class="token">val </span><span class="token">viewModel </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token">viewModel</span><span class="token">(modelClass </span><span class="token" style="color: #d73a49;">=</span><span class="token"> MainViewModel</span><span class="token">::</span><span class="token" style="color: #6f42c1;">class</span><span class="token">.java)
</span></span><span class="line"><span class="token">           </span><span class="token">val </span><span class="token">expanded </span><span class="token" style="color: #d73a49;">by</span><span class="token"> viewModel.expandedHolder</span><span class="token">.observeAsState</span><span class="token">(</span><span class="token" style="color: #005cc5;">false</span><span class="token">)
</span></span><span class="line"><span class="token">           </span><span class="token">InfinumContent</span><span class="token">(
</span></span><span class="line"><span class="token">               expanded </span><span class="token" style="color: #d73a49;">=</span><span class="token"> expanded,
</span></span><span class="line"><span class="token">               onExpandedChange </span><span class="token" style="color: #d73a49;">=</span><span class="token"> { viewModel</span><span class="token">.onExpandedChange</span><span class="token">(it) }
</span></span><span class="line"><span class="token">           )
</span></span><span class="line"><span class="token">       }
</span></span><span class="line"><span class="token">   }
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-206"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-204">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-205'
	>
	Compose provides the function <em>viewModel()</em> for getting an instance of the provided <em>ViewModel</em> class. The function follows the lifecycle of the caller by default, in this case Activity. The variable expanded is obtained by exposing <em>LiveData</em> inside the <em>ViewModel</em> and it’s observed as State. A state is another observable type that Jetpack Compose can use directly. The function <em>observeAsState</em> observes <em>LiveData</em> and returns a State object that is updated whenever <em>LiveData</em> changes. The parameter “by” is just the Kotlin syntactic way for unwrapping the returned value from State, in this context, Boolean.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-209"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-207">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-208'
	>
	Animations in Jetpack Compose</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-212"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-210">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-211'
	>
	Overview</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-215"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-213">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-214'
	>
	Animations are great tools in modern app development that help us build a more creative and better user interface. Jetpack Compose provides an API for easier animation implementation. The animation API in Compose is divided into two sections:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-218"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-216">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-217'
	>
	<li>High-level animation API – contains common animation patterns used across the app and is most suitable for Material Design Motion</li><li>Low-level animation API – gives more control and customization options and is built with Kotlin coroutine suspending functions</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-221"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-219">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-220'
	>
	In this article, we’ll cover only the High-level animation API. You can find out more about low-level APIs <a href="https://developer.android.com/jetpack/compose/animation#low-level-apis">here</a>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-224"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-222">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-223'
	>
	AnimatedVisibility (experimental)</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-227"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-225">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-226'
	>
	The <em>AnimatedVisibility</em> composable animates the appearance and disappearance of its content. By default, content appears by fading in and expanding, and disappears by fading out and shrinking. Keep in mind that this is an experimental API so it might change in the future or be completely removed.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-230"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-228">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-229'
	>
	Replace the if-statement inside InfinumContent with AnimatedVisibiliy as follows:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-232"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">@Composable</span><span class="token">
</span></span><span class="line"><span class="token">fun InfinumContent</span><span class="token">(expanded</span><span class="token">: </span><span class="token" style="color: #6f42c1;">Boolean</span><span class="token">, onExpandedChange</span><span class="token">: (</span><span class="token" style="color: #6f42c1;">Boolean</span><span class="token">) -&gt; </span><span class="token" style="color: #6f42c1;">Unit</span><span class="token">) {
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">   </span><span class="token">AnimatedVisibility</span><span class="token">(visible </span><span class="token" style="color: #d73a49;">=</span><span class="token"> expanded) {
</span></span><span class="line"><span class="token">               </span><span class="token">Text</span><span class="token">(
</span></span><span class="line"><span class="token">                   </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">               )
</span></span><span class="line"><span class="token">           }
</span></span><span class="line"><span class="token">       }
</span></span><span class="line"><span class="token">   }
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-235"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-233"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-234">
	<picture class="image__picture block-media__image-picture">
												<img
					src="https://infinum.com/uploads/2021/08/jetpack-compose-native-android-2.gif"
					class="image__img block-media__image-img"
					alt=""
										height="172"
															width="528"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-238"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-236">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-237'
	>
	AnimateContentSize</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-241"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-239">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-240'
	>
	As its name says, <em>AnimateContentSize</em> animates the size changes of the called composable. Inside <em>InfinumContent</em>, add the <em>animateContentSize</em> modifier to the composable you want to animate:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-243"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">@Composable</span><span class="token">
</span></span><span class="line"><span class="token">fun InfinumContent</span><span class="token">(expanded</span><span class="token">: </span><span class="token" style="color: #6f42c1;">Boolean</span><span class="token">, onExpandedChange</span><span class="token">: (</span><span class="token" style="color: #6f42c1;">Boolean</span><span class="token">) -&gt; </span><span class="token" style="color: #6f42c1;">Unit</span><span class="token">) {
</span></span><span class="line"><span class="token">   </span><span class="token">Column</span><span class="token">(
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">   ) {
</span></span><span class="line"><span class="token">       </span><span class="token">Card</span><span class="token">(
</span></span><span class="line"><span class="token">           modifier </span><span class="token" style="color: #d73a49;">=</span><span class="token"> Modifier
</span></span><span class="line"><span class="token">               </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">               </span><span class="token">.animateContentSize</span><span class="token">()
</span></span><span class="line"><span class="token">       ) {
</span></span><span class="line"><span class="token">          </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">       }
</span></span><span class="line"><span class="token">   }
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-246"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-244"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-245">
	<picture class="image__picture block-media__image-picture">
												<img
					src="https://infinum.com/uploads/2021/08/jetpack-compose-native-android-3.gif"
					class="image__img block-media__image-img"
					alt=""
										height="172"
															width="528"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-249"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-247">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-248'
	>
	Crossfade</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-252"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-250">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-251'
	>
	Crossfade animates the switch between two layouts with a crossfade effect. Let’s say we want to accomplish that between the non-expanded state only showing the Infinum logo composable and the expanded state that shows only the Infinum text composable. Switching between these composables needs to be done with a crossfade. Refactor <em>InfinumContent</em> in the following way:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-254"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">@Composable</span><span class="token">
</span></span><span class="line"><span class="token">fun InfinumContent</span><span class="token">(expanded</span><span class="token">: </span><span class="token" style="color: #6f42c1;">Boolean</span><span class="token">, onExpandedChange</span><span class="token">: (</span><span class="token" style="color: #6f42c1;">Boolean</span><span class="token">) -&gt; </span><span class="token" style="color: #6f42c1;">Unit</span><span class="token">) {
</span></span><span class="line"><span class="token">   </span><span class="token">Column</span><span class="token">(
</span></span><span class="line"><span class="token">      </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">   ) {
</span></span><span class="line"><span class="token">       </span><span class="token">Card</span><span class="token">(
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">       ) {
</span></span><span class="line"><span class="token">           </span><span class="token">Row</span><span class="token">(
</span></span><span class="line"><span class="token">               </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">           ) {
</span></span><span class="line"><span class="token">               </span><span class="token">Crossfade</span><span class="token">(targetState </span><span class="token" style="color: #d73a49;">=</span><span class="token"> expanded) {
</span></span><span class="line"><span class="token">                   </span><span class="token" style="color: #d73a49;">if</span><span class="token"> (it) {
</span></span><span class="line"><span class="token">                       </span><span class="token">Text</span><span class="token">(
</span></span><span class="line"><span class="token">                          </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">                       )
</span></span><span class="line"><span class="token">                   } </span><span class="token" style="color: #d73a49;">else</span><span class="token"> {
</span></span><span class="line"><span class="token">                       </span><span class="token">Image</span><span class="token">(
</span></span><span class="line"><span class="token">                          </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">                       )
</span></span><span class="line"><span class="token">                   }
</span></span><span class="line"><span class="token">               }
</span></span><span class="line"><span class="token">           }
</span></span><span class="line"><span class="token">       }
</span></span><span class="line"><span class="token">   }
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-257"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-255">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-256'
	>
	Testing a Compose layout</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-260"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-258">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-259'
	>
	Overview</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-263"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-261">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-262'
	>
	Testing a Compose layout is substantially different than testing view-based UI. View-based UI architecture defines what the view is. The view occupies its space on screen and has attributes such as the identifier, margins, padding, etc.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-266"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-264">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-265'
	>
	On the other hand, composables are functions that emit pieces of UI. They don’t have attributes or identifiers to distinguish them by. We will test our sample by introducing semantics.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-269"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-267">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-268'
	>
	Semantics</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-272"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-270">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-271'
	>
	We need a way to identify our composables. Semantics, as the name implies, gives meaning to a piece of UI, it describes it. Semantics are generated alongside the UI hierarchy emitted on screen. A piece of UI in semantics can be anything, from a whole screen to just a single composable.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-275"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-273">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-274'
	>
	Testing the InfinumContent composable</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-278"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-276">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-277'
	>
	Compose tests are instrumented tests that will be executed on an Android device. To perform this test, create the <em>InfinumLogoTest</em> class inside the <em>androidTest</em> folder as follows:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-280"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">@RunWith</span><span class="token">(AndroidJUnit4</span><span class="token">::</span><span class="token" style="color: #6f42c1;">class</span><span class="token">)
</span></span><span class="line"><span class="token">class InfinumLogoTest </span><span class="token">{
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #6f42c1;">@get</span><span class="token">:</span><span class="token" style="color: #6f42c1;">Rule</span><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token">val </span><span class="token">composeTestRule </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token">createAndroidComposeRule</span><span class="token">&lt;</span><span class="token" style="color: #6f42c1;">MainActivity</span><span class="token">&gt;</span><span class="token">()
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-283"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-281">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-282'
	>
	We are creating <em>composeTestRule</em> by calling <em>createAndroidComposeRule</em> with the given activity. With this rule, we can use Compose functionalities or access the Activity directly if we need to.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-286"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-284">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-285'
	>
	We want to check if only the logo is displayed initially. Since the logo is an Image composable and it must have the <em>contentDescription</em> attribute, this would be our testing method:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-288"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">@Test</span><span class="token">
</span></span><span class="line"><span class="token">fun logo_initially_displayed</span><span class="token">() {
</span></span><span class="line"><span class="token">   </span><span class="token">val </span><span class="token">infinumLogo </span><span class="token" style="color: #d73a49;">=</span><span class="token"> composeTestRule.activity</span><span class="token">.getString</span><span class="token">(R.string.infinum_mark_description)
</span></span><span class="line"><span class="token">   composeTestRule</span><span class="token">.onNodeWithContentDescription</span><span class="token">(infinumLogo)</span><span class="token">.assertIsDisplayed</span><span class="token">()
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-291"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-289">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-290'
	>
	We use <em>composeTestRule</em> to access the Activity directly to fetch the String that represents the Image content description. We then call the function <em>onNodeWithContentDescription()</em> with a given String, which will try to find a semantic node with that description. And finally, we call <em>assertIsDisplayed()</em> on that semantic node representing the Infinum logo. Our test is successful!</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-294"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-292">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-293'
	>
	Next, we test to confirm that the text doesn’t initially exist inside the composition. Let’s use this method:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-296"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">@Test</span><span class="token">
</span></span><span class="line"><span class="token">fun text_initially_not_exist</span><span class="token">() {
</span></span><span class="line"><span class="token">   </span><span class="token">val </span><span class="token">infinumText </span><span class="token" style="color: #d73a49;">=</span><span class="token"> composeTestRule.activity</span><span class="token">.getString</span><span class="token">(R.string.infinum)
</span></span><span class="line"><span class="token">   composeTestRule</span><span class="token">.onNodeWithText</span><span class="token">(infinumText)</span><span class="token">.assertDoesNotExist</span><span class="token">()
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-299"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-297">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-298'
	>
	Since the <em>Text</em> composable doesn’t have a content description, we can’t use the <em>onNodeWithContentDescription()</em> function. Instead, we can use <em>onNodeWithText()</em> with the provided text and then call <em>assertDoesNotExist()</em>. Another success!</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-302"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-300">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-301'
	>
	Finally, we want to test that the text is displayed when a click is performed. Our clickable composable inside <em>InfinumContent</em> is a Card. A card doesn’t have a text or content description. We need to somehow tag that component to use it inside our test class.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-305"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-303">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-304'
	>
	Let’s declare our tag for that <em>Card</em> composable:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-307"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token">val </span><span class="token">INFINUM_CARD </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">Infinum Card</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-310"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-308">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-309'
	>
	Connect the composable <em>Card</em> with the declared tag as follows:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-312"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">@Composable</span><span class="token">
</span></span><span class="line"><span class="token">fun InfinumContent</span><span class="token">(expanded</span><span class="token">: </span><span class="token" style="color: #6f42c1;">Boolean</span><span class="token">, onExpandedChange</span><span class="token">: (</span><span class="token" style="color: #6f42c1;">Boolean</span><span class="token">) -&gt; </span><span class="token" style="color: #6f42c1;">Unit</span><span class="token">) {
</span></span><span class="line"><span class="token">   </span><span class="token">Card</span><span class="token">(
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">       modifier </span><span class="token" style="color: #d73a49;">=</span><span class="token"> Modifier
</span></span><span class="line"><span class="token">           </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">           </span><span class="token">.testTag</span><span class="token">(INFINUM_CARD)
</span></span><span class="line"><span class="token">   ) {
</span></span><span class="line"><span class="token">      </span><span class="token" style="color: #d73a49;">..</span><span class="token">.
</span></span><span class="line"><span class="token">   }
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-315"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-313">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-314'
	>
	Our test function then looks like this:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-317"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">@Test</span><span class="token">
</span></span><span class="line"><span class="token">fun text_displayed_when_click_performed</span><span class="token">() {
</span></span><span class="line"><span class="token">   </span><span class="token">val </span><span class="token">infinumText </span><span class="token" style="color: #d73a49;">=</span><span class="token"> composeTestRule.activity</span><span class="token">.getString</span><span class="token">(R.string.infinum)
</span></span><span class="line"><span class="token">   composeTestRule</span><span class="token">.onNodeWithTag</span><span class="token">(INFINUM_CARD)</span><span class="token">.performClick</span><span class="token">()
</span></span><span class="line"><span class="token">   composeTestRule</span><span class="token">.onNodeWithText</span><span class="token">(infinumText)</span><span class="token">.assertIsDisplayed</span><span class="token">()
</span></span><span class="line"><span class="token">}
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-320"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-318">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-319'
	>
	Now we are calling the <em>onNodeWithTag()</em> function with a given tag, and simulating the click event by calling the <em>performClick()</em> method. After that, we are checking to see if the text “Infinum” is displayed. Again, it passed the test.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-323"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-321"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-322">
	<picture class="image__picture block-media__image-picture">
												<img
					src="https://infinum.com/uploads/2021/08/jetpack-compose-native-android-4.webp"
					class="image__img block-media__image-img"
					alt=""
										height="427"
															width="1135"
										loading="lazy"
					 />
					</picture>

			<figcaption class="image__figcaption block-media__image-figcaption">
			the output in android studio		</figcaption>
	</figure></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-326"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-324">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-325'
	>
	Jetpack Compose pros &amp; cons</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-329"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-327">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-328'
	>
	Jetpack Compose is a powerful tool for building native Android UI. A major advantage of the declarative approach is that we just describe the UI and Compose takes care of the rest as the app state changes. This example showed how quick and easy it is to build the Infinum logo, style it and animate it on click. In other words, you can do more with less code.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-332"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-330">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-331'
	>
	The preview feature in Android Studio is an unquestionable development accelerator because you can immediately see how the UI is going to look like. Plus, with interactive mode, you can perform clicks and see the changes.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-335"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-333">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-334'
	>
	Furthermore, Compose is interoperable with existing view-based UI so we can quickly switch between the two. This feature comes in handy for migrating a project to Compose because migrating an entire app at once is not a small task.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-338"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-336">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-337'
	>
	Compose also provides us with other great features such as the built-in Material Design support for styling, Dark Theme support, powerful Animation API for creating a livelier UI, etc.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-341"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-339">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-340'
	>
	On the other hand, integration with other Jetpack components could also be better. Some views inside view-based UI don’t exist in Compose so we need to use AndroidView and custom solutions. Lack of support for most popular libraries nowadays is also a disadvantage.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-344"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-342">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-343'
	>
	Jetpack Compose – looking forward</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-347"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-345">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-346'
	>
	Overall, the previous Android UI toolkit was not sustainable. The API started showing signs of aging, it was tightly coupled with Android OS, not Kotlin based, and lacked Material Design support. Just looking at the base Android View class, we can see that there are over 30,000 lines of code.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-350"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-348">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-349'
	>
	Jetpack Compose is an enormous turning point for building native Android UI, not just from the practical side, but in the way we think about it. With the stable version, it’s well on its way to completely replacing Google’s existing toolkit.</p></div>	</div>
</div>
</div>		</div>
	</div><p>The post <a href="https://infinum.com/blog/jetpack-compose-native-android/">Jetpack Compose: A Turning Point for Native Android UI</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</content:encoded>
			</item>
		
	</channel>
</rss>