<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[kt.academy - Medium]]></title>
        <description><![CDATA[Blog with mission to simplify Kotlin learning - Medium]]></description>
        <link>https://blog.kotlin-academy.com?source=rss----e57b304801ef---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>kt.academy - Medium</title>
            <link>https://blog.kotlin-academy.com?source=rss----e57b304801ef---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 15 Jun 2026 04:55:44 GMT</lastBuildDate>
        <atom:link href="https://blog.kotlin-academy.com/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Powerful tools, you can go crazy with]]></title>
            <link>https://blog.kotlin-academy.com/powerful-tools-you-can-go-crazy-with-8a896584347e?source=rss----e57b304801ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/8a896584347e</guid>
            <category><![CDATA[kotlin-coroutines]]></category>
            <dc:creator><![CDATA[Marcin Moskala]]></dc:creator>
            <pubDate>Tue, 02 Jun 2026 12:13:30 GMT</pubDate>
            <atom:updated>2026-06-02T12:13:30.726Z</atom:updated>
            <content:encoded><![CDATA[<p>Hello Kotliners,</p><p>This is an interesting issue. We will explore some tools that are very powerful, but that can be also terribly misused. Enjoy 😉</p><p>In this issue you will find:</p><p>💡 Compose tip: Preview options</p><p>📝 Scope functions</p><p>🎬 Example: Using Animations API in Compose</p><p>🤕 Best Practice: Application exceptions should not implement CancellationException</p><p>📝 Article: The future of mobile development</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UcrVKBJAMMxDuIkfbK5xLQ.jpeg" /></figure><p>Make Compose UI consistent, modern, refined.</p><p>Join <strong>Polished Compos</strong>e to learn how strong Android products improve visual quality through better theming, cleaner layouts, motion, and polish.</p><p>Last moment to join!</p><p>Enroll now → <a href="http://polishedcompose.com/">http://polishedcompose.com/</a></p><h3>Compose tip: Preview options</h3><p>Remember that you can define multiple previews 📱 This allows you to see many different:</p><p>👉 Parameters — check the same component in different states.</p><p>👉 Screen sizes — phone, tablet, desktop</p><p>👉 Orientations — portrait, landscape</p><p>👉 Screen characteristics — density, cutouts</p><p>👉 Locales, backgrounds, API levels and much more…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*d46CFpX3ptOm_81U.png" /></figure><h3>Scope functions</h3><p>Kotlin stdlib provides simple but very useful functions that can be called on any value, known as scope functions. Each has its purpose and use cases where it truly shines. Let’s discuss let, also, takeIf, apply, with and run.</p><p>Let is used to transform one value into another. It allows calling functions “on” parameters. This also allows method call chaining. It is often used together with ?. to transform only non-null values.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sjUv7o-iKKPjTdtP.jpeg" /></figure><p>also is used to execute an additional operation (side-effect) on a value. It returns the object it was called on. It is used to make an additional operation with a value.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I0CYqwWcIhrHD52IN7j-ig.jpeg" /></figure><p>takeIf and takeUnless are used to turn values we are not interested in into nulls. It is often used to eliminate invalid values, or files that does not exist.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yGyylhIVpyoLHA3nV__C8g.jpeg" /></figure><p>let, also, are takeIf are for any object, what map, onEach, and filter are for collections.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0lCmt3qaYqwOxk9KaZUSRw.jpeg" /></figure><p>apply is a function similar to also, but apply chages receiver (this) inside its body in the way it refers to the object apply is called on. It is perfect for mutable object configuration (setting properties).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/620/1*Mww43I1DXq44uYzaiHWmlA.jpeg" /></figure><p>with also changes the receiver for its body, but it accepts it as an argument, what makes receiver change more visible. We use with to explicitly turn an argument into a receiver.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-71o4GS2-5yEROgN.jpeg" /></figure><p>There is also run, that behaves similar to with, but I do not know any good use cases for its usage. If you want to map, prefer let, if you want to change receiver, prefer apply or with. Here is a table comparing how each of those functions works.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/673/1*aL8ZBNJ23GZ5HpJesK_45w.jpeg" /></figure><h3>Example: Using Animations API in Compose</h3><p>With the amazing Compose Animations API, you can make so many beautiful effects so easily; you just need an idea, and the sky is the limit. 🚀</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Sd74UIHdEN2iDXbN.gif" /></figure><h3>Best Pracitce: Application exceptions should not implement CancellationException</h3><p>Application exceptions should not implement CancellationException. This is because CancellationExceptionhas a special meaning for coroutines, and they do not propagate it like other exceptions!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ew-ysHKE4dtltiqOBCr0VQ.png" /></figure><p>To learn more, read this section of Kotlin Coroutines: Deep Dive</p><p><a href="https://kt.academy/article/cc-cancellation#cancellationexception-does-not-propagate-to-its-parent">https://kt.academy/article/cc-cancellation#cancellationexception-does-not-propagate-to-its-parent</a></p><h3>Article: The future of mobile development</h3><p>What the future holds for mobile development, and how it changed in recent years.</p><h4>Learn from this article 👇</h4><p><a href="https://kt.academy/article/future_of_mobile">https://kt.academy/article/future_of_mobile</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5q2qgknjE7fzuwChHih4-w.png" /></figure><p>Hope you enjoyed this week’s newsletter.</p><p>Marcin Moskala</p><p>— kt.academy</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8a896584347e" width="1" height="1" alt=""><hr><p><a href="https://blog.kotlin-academy.com/powerful-tools-you-can-go-crazy-with-8a896584347e">Powerful tools, you can go crazy with</a> was originally published in <a href="https://blog.kotlin-academy.com">kt.academy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tricks that make amazing experience]]></title>
            <link>https://blog.kotlin-academy.com/tricks-that-make-amazing-experience-aabae12a08e2?source=rss----e57b304801ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/aabae12a08e2</guid>
            <category><![CDATA[intrinsicsize-in-compose]]></category>
            <category><![CDATA[flow-operators]]></category>
            <category><![CDATA[hashmap-internals]]></category>
            <dc:creator><![CDATA[Marcin Moskala]]></dc:creator>
            <pubDate>Tue, 12 May 2026 06:01:01 GMT</pubDate>
            <atom:updated>2026-05-12T06:01:01.094Z</atom:updated>
            <content:encoded><![CDATA[<p>Hello Kotliners,</p><p>sometimes a simple idea can make such a difference, it feels like magic. Hash Map was certainly such an idea, the way how Flows are implemented were such an idea, end even intrinsic size in Compose is such an idea. In this issue you will find:</p><p>💪 Intrinsic size in Compose</p><p>🔎 Map Hash Table</p><p>⚛️ Quick Coroutines take: Most Flow processing functions are very simple under the hood</p><p>📝 Article: New big challenger in UI development</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*oKfR-6ewotdeFvs8.jpg" /></figure><p>Your UI works. Make it polished.</p><p><strong>Polished Compose</strong> is an advanced course for Android developers who want to improve visual quality, consistency, motion, and UX details in Jetpack Compose.</p><p><strong>Starts 8 June 2026.</strong></p><p>Enroll now → <a href="http://polishedcompose.com/">http://polishedcompose.com/</a></p><p>Intrinsic size in Compose</p><p>One less known, but surprisingly useful modifier is intrinsic weight/height. They allow us to give children even size, that is max/min from each of them. 💪</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gch6FRCtnCNqBXy9.png" /></figure><p>Let’s start with an example problem. 🔧 Consider that you have a list of texts in a column. Texts are decorated with a card. Your designer requests all cards to have the same width, relative to the longest text.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/442/0*OMRV4GUP3TdtD5gH.png" /></figure><p>How can we achieve it? If you use a constant width, you will not react to text changes. If you use fillMaxWidth, you will make parent as large as the screen. The only way to achieve that is by using Modifier.width(IntrinsicSize.Max) on parent (Coumn) and Modifier.fillMaxWidth() on children. ⭐</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*zQg_MTzuR2yGSwJe.png" /></figure><p>Why Modifier.width(IntrinsicSize.Max)? That means taking as much space as the largest component needs. You can also take as little space as possible using Modifier.width(IntrinsicSize.Min). For text, that means width of the widest word. 👀</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*s2cVWk6j5EWt3bJf.png" /></figure><h3>Map Hash Table</h3><p>The complexity of finding a value by key in the default map on JVM is O(1), which means it is blazing fast, no matter how many elements there are in this map. Let me explain how it is possible</p><p>The default map in Kotlin is LinkedHashMap, but we also often use HashMap as a lighter alternative. Both keep elements using hash table. Hash table can be compared to a set of buckets, where elements can be stored.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2LA6FWFmq6NGVV1n.png" /></figure><p>When an element is added to a map, it first calculated its hash code using hashCode method. This method should be fast, should return the same int for equal values, and should possibly return different ints for different values.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*pUswkbtMvsWI57AI.png" /></figure><p>We calculate hash code modulo number of buckets, and this is where element is added. Hash table is an array, and bucket number is the index of an element in this array.</p><p>Finding elements in the map is simple: we calculate its hash code modulo number of buckets (fast), and we know in what bucket it should be. Now we need to compare only with elements in this bucket.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*eqZnWrrnhynX-jlR.png" /></figure><p>The trick is that on JVM the number of buckets is always significantly higher than the number of elements, so of hashCode is implemented well, once we know bucket index, this bucket should either have this element or be empty.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hpaEn9okKnEKphOr.png" /></figure><p>Set works the same way, under the hood it is quite often just a map with constant value.</p><h3>Quick Coroutines take: Most Flow processing functions are very simple under the hood</h3><p>This is what the map, filter, and onEach from Kotlin Coroutines look like under the hood. Yes, it’s that simple. Flow under the hood is as simple and efficient as it can be.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*aqHaP-n30fVp2a3b.png" /></figure><h3>Article: New big challenger in UI development</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*PgePkoAo5Kf966Qm.png" /></figure><p>Could a UI framework that started on Android become a serious challenger beyond mobile?</p><p>This article looks at Compose as a multiplatform declarative UI framework written in Kotlin, and why it is starting to appear not only on Android, but also on iOS, desktop, and the web.</p><p>It covers:</p><ul><li>how Compose differs from DOM/CSS-based frontend approaches</li><li>why previews, testing tools, and animation APIs matter in practice</li><li>how teams use Kotlin across backend and clients, and why that makes Compose attractive</li></ul><p>A useful perspective for developers watching the future of UI development across platforms.</p><p>👉 <a href="https://kt.academy/article/compose_new_challenger">https://kt.academy/article/compose_new_challenger</a></p><p>I hope this newsletter was a valuable part of your week.</p><p>Marcin<br>— kt.academy</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aabae12a08e2" width="1" height="1" alt=""><hr><p><a href="https://blog.kotlin-academy.com/tricks-that-make-amazing-experience-aabae12a08e2">Tricks that make amazing experience</a> was originally published in <a href="https://blog.kotlin-academy.com">kt.academy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Do you understand it?]]></title>
            <link>https://blog.kotlin-academy.com/do-you-understand-it-7c9abef959b1?source=rss----e57b304801ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/7c9abef959b1</guid>
            <category><![CDATA[kotlin-coroutines]]></category>
            <dc:creator><![CDATA[Marcin Moskala]]></dc:creator>
            <pubDate>Tue, 05 May 2026 06:01:01 GMT</pubDate>
            <atom:updated>2026-05-05T06:01:01.410Z</atom:updated>
            <content:encoded><![CDATA[<p>Hello Kotliners!</p><p>Do you understand data classes? MutableStateFlow.update? Text in Compose? Compose itself? Those are the questions we will ask in today issue 😃</p><p><strong>In this issue you will find:</strong></p><p>💡 MutableStateFlow hint: Use update function to avoid synchronization issues</p><p>🔎 Text vs BasicText in Compose</p><p>📝 Deep dive into data classes</p><p>⚛️ Quick Compose take: Modifiers are everything</p><p>📝 Article: Collection Literals in Kotlin</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*kb3EJDr7kSVZcWKe.jpg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*F3P2xmEjwaOwSqWe.jpg" /></figure><p><strong>Polished Compose</strong> is a 2-week advanced course for Android developers who want to improve theming, responsive layouts, animations, and visually polished UI in Jetpack Compose.</p><p>Learn how to structure interfaces that stay consistent, scalable, and production-ready as your app grows.</p><p><strong>Starts 8 June 2026</strong></p><p>Enroll now → <a href="https://www.polishedcompose.com/">https://www.polishedcompose.com/</a></p><h3>MutableStateFlow hint: Use update function to avoid synchronization issues</h3><p>Updates to MutableStateFlow should be made using the update function; otherwise, conflicts might cause some updates to be lost.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*nbXNw-4loBTZdgOW.png" /></figure><h3>Text vs BasicText in Compose</h3><p>What is the difference between Text and BasicText in Compose? They are both used to represent text, but…</p><p>👉 Text is a material component that includes many built-in features, making it easy to create text that looks good and is accessible. 𝐓𝐞𝐱𝐭, 𝐛𝐲 𝐝𝐞𝐟𝐚𝐮𝐥𝐭, 𝐝𝐞𝐩𝐞𝐧𝐝𝐬 𝐨𝐧 𝐋𝐨𝐜𝐚𝐥𝐓𝐞𝐱𝐭𝐒𝐭𝐲𝐥𝐞 𝐚𝐧𝐝 𝐋𝐨𝐜𝐚𝐥𝐂𝐨𝐧𝐭𝐞𝐧𝐭𝐂𝐨𝐥𝐨𝐫.</p><p>👉 BasicText displays an unstyled text, but is it highly configurable, so we can make it look any way we want. 𝐓𝐞𝐱𝐭 𝐮𝐧𝐝𝐞𝐫 𝐭𝐡𝐞 𝐡𝐨𝐨𝐝 𝐢𝐬 𝐣𝐮𝐬𝐭 𝐚 𝐰𝐫𝐚𝐩𝐩𝐞𝐫 𝐨𝐯𝐞𝐫 𝐁𝐚𝐬𝐢𝐜𝐓𝐞𝐱𝐭.</p><p>So when to use each? I typically start from Text, as it is often enough, and it looks good with minimal configuration, but I switch to BasicText if Text is limiting me.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*P_lLzdxRPcOUFQ6G.png" /></figure><h3>Deep dive into data classes</h3><p>Data classes are not just syntactic sugar; they have completely changed how we treat classes and objects, and by doing so, they have extremely simplified development. Let me explain 👇</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*g9KVKB70xZqKMcl-.png" /></figure><p>In Java (before 14) and many other languages, developers need to learn about functions like equals, hashCode, and toString to define classes to represent data models.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*UijSS78QXXjmeBS3.png" /></figure><p>There is no such need in Kotlin! In Kotlin, we have two types of classes: regular and data, and we nearly never need to define anything else!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/852/0*Cn7Jri0kfOLDBK9c.png" /></figure><p>Regular classes are perfect for representing components of our application, such as services, controllers, repositories, etc. They are unique and protect their state.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/806/0*kwxj1R3Ofzm06L91.png" /></figure><p>Data classes are perfect for representing a bundle of data. All the classes like Book, Product, or NewsFeet. Data modifier makes objects equal if their content is equal (primary constructor properties). It also provides consistent hashCode, and toString that prints the content.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*TRiy2XkI2s7inC2M.png" /></figure><p>Data modifier also provides the copy method, which is a game-changer for immutability. It allows us to make a new instance with some specific difference.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DGX7o_gGTDBlH2Ci.png" /></figure><p>Data modifier also allows object destructuring, but beware, because Kotlin currently only supports position-based destructuring.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/844/0*FlSql3pxCH-u5WvF.png" /></figure><p>Java 14 introduced a similar feature called record classes, but let’s be honest, record classes have much more restrictions and Java projects are still full of equals, hashCode and toString.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/526/0*Tt_X4OoxEXtryLLA.png" /></figure><p>Data modifier introduced a powerful abstraction, that made Kotlin much more beginner-friendly. Newcomers do not need to learn about correct overriding equals, hashCode, and toString, they can just use data classes, like we all do 😉</p><h3>Quick Compose take: Modifiers are everything</h3><p>If you strip composables from all the layers of abstraction, you will see that everything is just Layout and modifiers. Practically everything you see drawn on the screen is drawn by modifiers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Tn0vWQZJK4uhX7pu.png" /></figure><h3>Article: Collection Literals in Kotlin</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*dLqBF_wtzoQrULtN.png" /></figure><p>The new feature in Kotlin that allows for concise and expressive collection creation.</p><p>Read about it here: <a href="https://kt.academy/article/collection-literals">https://kt.academy/article/collection-literals</a></p><p>Hope you found this newsletter worthwhile.</p><p>Marcin Moskała</p><p>— kt.academy</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7c9abef959b1" width="1" height="1" alt=""><hr><p><a href="https://blog.kotlin-academy.com/do-you-understand-it-7c9abef959b1">Do you understand it?</a> was originally published in <a href="https://blog.kotlin-academy.com">kt.academy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Hints for productivity and performance]]></title>
            <link>https://blog.kotlin-academy.com/hints-for-productivity-and-performance-f89f8f3866a2?source=rss----e57b304801ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/f89f8f3866a2</guid>
            <category><![CDATA[kotlin-coroutines]]></category>
            <dc:creator><![CDATA[Marcin Moskala]]></dc:creator>
            <pubDate>Tue, 28 Apr 2026 08:50:46 GMT</pubDate>
            <atom:updated>2026-04-28T08:50:46.223Z</atom:updated>
            <content:encoded><![CDATA[<p>Hello Kotliner,</p><p>This issue is packed with knowledge, so we have no time to spare! In this issue you will find:</p><p>💡 Preview parameters in Compose</p><p>💪 Cancellation in Kotlin Coroutines</p><p>📈 Optimising into arrays</p><p>📝 Article: Why do developers count from zero?</p><p>⭐ Project: vmScope</p><p>Preview parameters in Compose</p><p>Preview in Jetpack Compose is highly configurable, which makes development incomparably better than in other technologies. Let me show you 👇</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*jHHHqRx6BotNBy31.png" /></figure><p>You can use Preview annotation before any composable with no parameters. Such a composable can be previewed at any moment, or started on a device in separation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_ieD8BWh8T3JjtPB.png" /></figure><p>You can also preview composables with optional parameters.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*FKO9a-tVCiCxkAec.png" /></figure><p>But you can also provide preview providers for parameters. Such providers can provide multiple variants of parameters, that leads to multiple previews. Is you start it on a screen, you can easily switch between different parameter configurations.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*LhVp_qMJXmrMD4qD.png" /></figure><p>All those options contribute to amazing programming experience, as practically any single component can be simply previewed and started in separation.</p><h3>Cancellation in Kotlin Coroutines</h3><p>Most people do not even think about cancellation, but it is extremely important, and both Android and backend developers benefit from Kotlin Coroutines cancellation mechanisms. Let me give you some examples.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*pHxqvI-5PNYmM9wT.png" /></figure><p>Thanks to structured concurrency, cancellation propagates from child to parent. That is a game-changer for Android developers, where each scope is associated with a view, so if the view gets destroyed, all its processes get cancelled.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/423/0*A9fOH7mlXv7caQTX.png" /></figure><p>But cancellation mechanisms are also a great benefit for backend developers. If you make async calls and one of them fails, others are cancelled by default.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/454/0*b_YSqF-nR4TJO0z4.png" /></figure><p>Also when you define a backend application in a framework like Ktor, that is coroutines-first, if an HTTP connection is lost, call gets immediately cancelled, the same if WebSocket or RSocket connection is lost.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/471/0*EpG6E0F6Vy-Ril_C.png" /></figure><p>Exceptions happen, and connections get lost. Now you can calculate how many resources that can help us save if cancellation lets us close a connection with another service earlier, or not make a request at all, or not calculate something, or release a thread previously blocked.</p><p>Kotlin Coroutines cancellation is not perfect, but still, it is the best solution I know, and I am very happy to have it in Kotlin.</p><h3>Optimising into arrays</h3><p>Transforming lists into arrays can sometimes help us have a crazy amount of memory❗ Of course, such optimizations make sense only in very specific cases.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*o09mscwDKQvlVzK1.png" /></figure><p>The size of IntArray is typically 5 times smaller than an array or a list of ints. The reason is simple: IntArray uses primitives that need only 4B. In other cases, we need a wrapped type that needs 16B for an object and 4B for reference.</p><p>Wrapped types also add a cost of accessing the underlying values and garbage collecting all the objects. That is why one of the first optimizations we do when we make performance tuning is optimizing for primitives.</p><p>Though this optimization only works for primitives. A typical list of object will need similar amount of memory as an array, because default list in Kotlin is just a wrapper over array. A wrapper that allows much simpler use.</p><p>That is why such an optimization should only be done on performance-critical parts of our code, where performance is more important than simplicity, as it often complicates our code and makes it less idiomatic.</p><h3>Article: Why do developers count from zero?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*j-Z3yL_F5Ued1IsI.png" /></figure><p>Why do developers count from zero?</p><p>This article explains the classic reasoning behind zero-based indexing and why the convention became so deeply rooted in programming.</p><p>It covers:</p><ul><li>why starting from 0 made sense when memory was more constrained</li><li>how array address calculations become simpler with zero-based indices</li><li>why some languages still chose differently, even though most kept the convention</li></ul><p>A short but useful read for developers who like understanding where common programming habits come from.</p><p><a href="https://kt.academy/article/index_zero">https://kt.academy/article/index_zero</a></p><h3>Project: vmScope</h3><p>Do you lack default error handler in viewModelScope? This library by Luka Velimirovic might be what you look for! It offers configurable multiplatform vmScope, as a replacement for viewModelScope. This library was made as a final project on Coroutines Mastery, great congratulations to Luka for creating it ❤</p><p>Code 👉 <a href="https://github.com/VelkosX/vmscope">https://github.com/VelkosX/vmscope</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*PHbkryMSyla3fbWp.png" /></figure><p>I hope you found something useful in this newsletter.</p><p>Marcin Moskała<br>kt.academy</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f89f8f3866a2" width="1" height="1" alt=""><hr><p><a href="https://blog.kotlin-academy.com/hints-for-productivity-and-performance-f89f8f3866a2">Hints for productivity and performance</a> was originally published in <a href="https://blog.kotlin-academy.com">kt.academy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Is it clean and predictable?]]></title>
            <link>https://blog.kotlin-academy.com/is-it-clean-and-predictable-b277b8123005?source=rss----e57b304801ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/b277b8123005</guid>
            <dc:creator><![CDATA[Marcin Moskala]]></dc:creator>
            <pubDate>Tue, 07 Apr 2026 15:01:03 GMT</pubDate>
            <atom:updated>2026-04-07T15:01:02.879Z</atom:updated>
            <content:encoded><![CDATA[<p>Hello Kotliners,</p><p>This issue is about API shape and recomposition scope — the stuff that makes your codebase feel either “clean and predictable” or “why is this so hard to maintain?”. We’ll revisit state hoisting decisions in Compose, peek into a Kotlin pattern you’ve been using for years without noticing (fake constructors), and look at a Compose trick that can noticeably reduce recomposition work. We’ll wrap with a question many teams debate: when to use MutableStateFlow directly vs stateIn.</p><p><strong>In this issue you will find:</strong></p><p>🎦 New YouTube videos</p><p>👉 Hoisting state</p><p>🔎 Fake interface constructor pattern</p><p>💪 Compose Best Practice: Prefer lambda modifiers for frequently changing state</p><p>🤔 MutableStateFlow vs stateIn</p><p>📝 Article: Is Kotlin a savior of Java?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*naCE8HEl0n7o8Tr5.jpg" /></figure><p>The Advanced Compose cohort starts in one week.</p><p>This program is about confidence when building and reviewing Compose code — understanding recomposition, modifiers, semantics, and testing at a deeper level.</p><p>This edition runs now. The next cohort will be next year at a higher price.</p><p>If this fits the problems you’re currently solving, registration remains open.</p><h3>New videos</h3><p>Another week, and we have new videos for you:</p><p><a href="https://youtu.be/yjIuJnHRP14"><strong>How many developers are there?</strong></a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Utpp9ULRoscAnfuXgldcAg.png" /></figure><p><a href="https://youtu.be/Q-ekvQ0vRCg"><strong>What are Explicit backing fields and where they can be dangerous?</strong></a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*d54awrgqAviSiX8R.png" /></figure><p><a href="https://www.youtube.com/watch?v=PDaOFle1CRA"><strong>Kotlin in 3 minutes by Marcin Moskała</strong></a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sEdMxvjsQ6UCFM3Q.png" /></figure><p>You hopefully seen some of our videos already, and you might have some opinions about you like and what you dislike. Please, share those in comments, we will read them all, and it is perfect time to influence this channel development.</p><h3>Hoisting state</h3><p>One of the most important decisions you need to make when defining custom composables is wrather you want to store mutable state locally or hoist it.</p><p>When state is local:</p><p>👉 Logic can be reused.</p><p>👉 Component has simpler API.</p><p>When we hoist it:</p><p>😨 There are more (or more complex) parameters.</p><p>👉 Logic can be more easily customised.</p><p>👉 Caller can decide how state is stored and how persistent is it.</p><p>👍 We can easily preview all different states.</p><p>I would say, that local state is more React-like, but hoisting this state it much more Compose-like. It is a popular approach hoist all state up to view models.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*aG4CL1r6Xuyh6Y-5.png" /></figure><h3>Fake interface constructor pattern</h3><p>A fake interface constructor returning a private class or object expression is a very common pattern in Kotlin team libraries. It allows us to expose only the interface, which is safe and offers maximum API flexibility.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/816/0*qNGmk-t-3i_gDxTV.png" /></figure><p>Examples include Job, CoroutineScope, CompletableDeferred, Channel, Mutex, Semaphore, MutableStateFlow, MutableSharedFlow, List, and much more!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vGF8dSiULar-I6uc.png" /></figure><p>Nearly every time it looks like we use a constructor from a kotlin or kotlinx library, it is actually a fake constructor.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*51hE_2XU4qY-EpGj.png" /></figure><p>Why is this pattern used? It gives library creators the freedom to change what actual class is returned.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*oNh2K_nl_JXZfC8-.png" /></figure><p>It also allows returning different classes depending on arguments (performance optimization).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*991NKhMvbcaF9zgZ.png" /></figure><p>Finally, such functions could create an object based on an argument using more complex algorithms, which would be inappropriate for constructors.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sH3u_RuX77tk202b.png" /></figure><p>There are more reasons to expose factory methods instead of constructors, I explained them in Effective Kotlin, Item 32: Consider factory functions instead of secondary constructors</p><p><a href="https://kt.academy/article/ek-factory-functions">https://kt.academy/article/ek-factory-functions</a></p><h3>Compose Best Practice: Prefer lambda modifiers for frequently changing state</h3><p>Prefer lambda modifiers for frequently changing state❗Why? Because this way we limit recomposition scope. If we read state when we build modifiers, this scope change triggers whole this composable recomposition, and requires building modifiers again. If we read state in a composable lambda, only this lambda will get recomposed with scope change 👌</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DLs-a1D7jTB9y-k5.png" /></figure><h3>MutableStateFlow vs stateIn</h3><p>We can often transform a coroutine into a StateFlow using stateIn, but should we do that? What do you think?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2eMPsS-ugK0vM938.png" /></figure><h3>Article: Is Kotlin a savior of Java?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*eJWFClojHSlZrGXA.png" /></figure><p>Can a competing language actually help save Java?</p><p>This article looks at a provocative idea: Kotlin may be one of the main reasons Java started evolving faster again.</p><p>It covers practical points developers care about:</p><ul><li>why Kotlin gained so much traction on Android and backend</li><li>which Java features arrived after Kotlin had already made similar ideas familiar on the JVM</li><li>why Kotlin may be pushing the whole JVM ecosystem forward, not just competing with Java</li></ul><p>A good read for developers interested in Kotlin, Java, and where JVM development is heading next.</p><p>👉 <a href="https://kt.academy/article/kotlin_vs_java">https://kt.academy/article/kotlin_vs_java</a></p><p>I hope this newsletter gave you something practical to take away.</p><p>Marcin Moskała<br>kt.academy</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b277b8123005" width="1" height="1" alt=""><hr><p><a href="https://blog.kotlin-academy.com/is-it-clean-and-predictable-b277b8123005">Is it clean and predictable?</a> was originally published in <a href="https://blog.kotlin-academy.com">kt.academy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building Reliable AI Agents with Koog]]></title>
            <link>https://blog.kotlin-academy.com/building-reliable-ai-agents-with-koog-86cd3f40c084?source=rss----e57b304801ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/86cd3f40c084</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[genai]]></category>
            <dc:creator><![CDATA[Ruben Quadros]]></dc:creator>
            <pubDate>Thu, 02 Apr 2026 10:36:44 GMT</pubDate>
            <atom:updated>2026-04-06T12:21:25.817Z</atom:updated>
            <content:encoded><![CDATA[<h4>Learn how to use Koog to build your own AI agents</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YFZgxpu3GoFea4F149hEtg.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@hypelights?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Brendan Sapp</a> on <a href="https://unsplash.com/photos/a-painting-of-a-tree-vZcULbAQTSo?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><blockquote>This article was featured in <a href="https://mailchi.mp/kotlinweekly/kotlin-weekly-505"><strong>Kotlin Weekly#505</strong></a></blockquote><h3>Introduction</h3><p>Like everyone else I was excited to try out different AI tools and later on to try and build my own AI agent. That got me thinking, <em>what would be a good use case?</em> I wanted to build something which I can use and then naturally, share it with others who might have a similar interest.</p><p><em>What did I build?</em> I built an AI agent to help me with my <a href="https://fantasy.premierleague.com/">FPL</a> team! I was tired of coming in last every week and I wanted a change! Since I’ve followed the game for a while, I can use my own footballing knowledge to double-check the accuracy of the agent’s recommendations.</p><p><em>How does Koog fit in?</em> Well, this is the core piece of the solution! Initially I tried building it the API way — call an endpoint directly every time you want an output from the LLM (<a href="https://github.com/rubenquadros/GameweekScout-Server/tree/8496cb1a27599e8357735d8ae3b48ce4d617dfc0">see this version of the repo</a>). While this is a good starting point, you also have to build a lot around it. Think manually managing tool calls, context window, iteration cycles and much more.</p><p>In this article, I will show you how <a href="https://docs.koog.ai/">Koog</a> helps solve most of the manual orchestration and let’s you focus on building the actual agent. As usual, the source code is linked at the end of the article!</p><h3>FPL agent</h3><p>First, let’s take a look at what we are building!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YtDpJ8jMcu4uGpZrdlN9uA.png" /><figcaption>FLP Agent Workflow</figcaption></figure><p>Does this look like some random boxes? Don’t worry! Let me break this down step by step. This will also give us clarity on how to build the agent and we can get into the juicy part of looking at the code after that!</p><h4>Detecting user intent</h4><p>The agent is responsible to only help with FPL related queries and therefore should not answer any random query. In this step the agent takes the user query as an input, reads the system instruction and then detects if the query is related to FPL. If it’s not then we simply fail at this step and don’t proceed further and the output will be something like: <strong><em>I’m built specifically for Fantasy Premier League advice. To help you with that, are you deciding on a transfer or a captain for this GW?</em></strong></p><h4>Providing FPL advice</h4><p>Now that the user query was deemed to be about FPL, the next step is to answer it. A key piece here is that instead of letting the LLM use it’s own FPL knowledge, we provide the FPL data as a tool to the agent. Now, if the LLM wants to know the price of a player or upcoming fixtures of a team, then it calls the methods we provided to get this information. Koog takes care of calling the relevant methods to fetch the data and then processes this to reach the final decision. The output of this step is what the user sees as the answer to their query.</p><h4>Compression</h4><p><em>Why do we need compression?</em> As the conversation keeps going, the agent context also keeps growing. In our case, we also have tool calls which return FPL data and this quickly leads to context bloat. Compressing the agent history helps improve performance, enhances accuracy and also reduces cost. If you are on a free tier, it also helps you avoid reaching your rate limits sooner!</p><h4>Persistence</h4><p><em>Why do we need persistence?</em> There are several advantages of having persistence. <strong><em>(i)</em></strong> In case of any failures (tool failure, server failure), Koog now has the previous checkpoint data and it can retrieve it to know where it was. <strong><em>(ii)</em></strong> Koog has context about previous conversations of the user and would not have to start fresh every time. <strong><em>(iii)</em></strong> It also acts as a audit log and would greatly help in debugging any potential issues.</p><h3>How do we finally build it?</h3><p>Now that you understand the different parts, how do we actually build this agent? Since the whole agent is nothing but a set of sequential flows, I decided to use the <strong><em>subgraphs feature</em></strong> of Koog to build this. More specifically, the <strong><em>custom strategy graphs</em></strong> feature. I would highly encourage you to read the official documentation to understand more about <a href="https://docs.koog.ai/subgraphs-overview/">subgraphs</a>, <a href="https://docs.koog.ai/custom-strategy-graphs/">custom strategy graphs</a>, <a href="https://docs.koog.ai/nodes-and-components/">pre-defined nodes and components</a>, <a href="https://docs.koog.ai/structured-output/">structured output</a>, <a href="https://docs.koog.ai/history-compression/">history compression</a> and <a href="https://docs.koog.ai/features/agent-persistence/">persistence</a>!</p><p>Every step from input to output is defined as a node. These nodes are connected by edges, ensuring that the output of one step seamlessly becomes the input for the next.</p><pre>// #1.1<br>edge(nodeStart forwardTo inputProcessingPrompt) <br>// #1.2<br>edge(inputProcessingPrompt forwardTo processInput) <br>// #1.3<br>edge(processInput forwardTo nodeFinish onCondition { !it.shouldProceed } transformed { it.response })<br>// #3.1<br>edge(processInput forwardTo evaluateCompression onCondition { it.shouldProceed })<br>// #3.2<br>edge(evaluateCompression forwardTo compressHistory onCondition { it.shouldCompress })<br>// #3.3<br>edge(evaluateCompression forwardTo scoutAdvicePrompt onCondition { !it.shouldCompress })<br>// #2.1<br>edge(compressHistory forwardTo scoutAdvicePrompt) <br>// #2.2<br>edge(scoutAdvicePrompt forwardTo provideSuggestion) <br>// #2.3<br>edge(provideSuggestion forwardTo nodeFinish) </pre><p>Let’s now look at each of the steps in more detail! I have purposely marked the compression step as #3 so we can first go through the natural flow of a user asking a question and the agent providing an answer. Let’s look at compression and persistence after we understand the core flow!</p><h4>Step 1: Intent detection</h4><pre>edge(nodeStart forwardTo inputProcessingPrompt) <br>edge(inputProcessingPrompt forwardTo processInput) <br>edge(processInput forwardTo nodeFinish onCondition { !it.shouldProceed } transformed { it.response })</pre><pre>val inputProcessingPrompt by nodeAppendPrompt&lt;String&gt;(&quot;input-process-prompt&quot;) {<br>  system(inputProcessInstruction)<br>}<br><br>val processInput by subgraphWithTask&lt;String, InputProcessResponse&gt;(<br>  name = &quot;input-processor&quot;,<br>  llmModel = geminiConfig.getLLMModel(geminiConfig.model.inputProcess),<br>  assistantResponseRepeatMax = 1<br>) { input -&gt;<br>    &quot;&quot;&quot;<br>      User query: &quot;$input&quot;<br>    &quot;&quot;&quot;.trimIndent()<br>}<br><br>@Serializable<br>@LLMDescription(&quot;The output format after processing the user query&quot;)<br>data class InputProcessResponse(<br>    @property:LLMDescription(<br>        &quot;&quot;&quot;<br>           Whether we should proceed to the next step. Will be &quot;true&quot;&quot; only if the query is related to Fantasy Premier League or English Premier League related. <br>        &quot;&quot;&quot;<br>    )<br>    val shouldProceed: Boolean,<br>    @property:LLMDescription(&quot;Response of the LLM in the current step&quot;)<br>    val response: String,<br>    @property:LLMDescription(&quot;The original user query which is needed in the next step&quot;)<br>    val originalInput: String,<br>    val shouldCompress: Boolean = false<br>)</pre><ul><li><a href="https://api.koog.ai/agents/agents-core/ai.koog.agents.core.dsl.builder/-a-i-agent-graph-strategy-builder/node-start.html">nodeStart</a> represents the starting node of the subgraph.</li><li><a href="https://api.koog.ai/agents/agents-core/ai.koog.agents.core.dsl.builder/-a-i-agent-graph-strategy-builder/node-finish.html">nodeFinish</a> represents the finish node of the subgraph. This is where the workflow stops.</li><li>inputProcessingPrompt is used to append the system prompt for detecting the user intent via <a href="https://api.koog.ai/agents/agents-core/ai.koog.agents.core.dsl.extension/node-append-prompt.html">nodeAppendPrompt</a>. You can find the system prompt <a href="https://github.com/rubenquadros/GameweekScout-Server/blob/main/ai/src/main/kotlin/io/github/rubenquadros/gameweekscout/server/ai/SystemInstruction.kt#L3">here</a>.</li><li>processInput is responsible for reading the user query and detecting if it’s related to FPL. <a href="https://api.koog.ai/agents/agents-core/ai.koog.agents.ext.agent/subgraph-with-task.html">subgraphWithTask</a> is a subgraph which performs a specific task using the provided tools and returns a structured output. In our case, it takes the user query as the input (<strong><em>String</em></strong>) and returns <strong><em>InputProcessResponse</em></strong> as the output.</li><li>If the user query is not related to FPL, then we stop here and return a static response to the user. This is handled via a check in <a href="https://api.koog.ai/agents/agents-core/ai.koog.agents.core.dsl.builder/-a-i-agent-edge-builder-intermediate-base/on-condition.html">onCondition</a>. Since the final output should also be a message (i.e. a string), we use <a href="https://api.koog.ai/agents/agents-core/ai.koog.agents.core.dsl.builder/-a-i-agent-edge-builder-intermediate-base/transformed.html">transformed</a> to pass the response back to the user.</li></ul><h4>Step 2: FPL advice</h4><pre>edge(compressHistory forwardTo scoutAdvicePrompt) <br>edge(scoutAdvicePrompt forwardTo provideSuggestion) <br>edge(provideSuggestion forwardTo nodeFinish)</pre><pre>val scoutAdvicePrompt by nodeAppendPrompt&lt;InputProcessResponse&gt;(&quot;scout-advice-prompt&quot;) {<br>  system(scoutAdviceInstruction)<br>}<br><br>val provideSuggestion by subgraphWithTask&lt;InputProcessResponse, String&gt;(<br>  name = &quot;scout-advisor&quot;,<br>  llmModel = geminiConfig.getLLMModel(geminiConfig.model.suggestion),<br>  tools = fplToolsRegistry.tools,<br>  assistantResponseRepeatMax = 5<br>) { context -&gt;<br>    &quot;&quot;&quot;<br>      User query: &quot;${context.originalInput}&quot;<br>    &quot;&quot;&quot;.trimIndent()<br>}<br><br>val fplToolsRegistry = ToolRegistry {<br>  tools(fplApi)<br>}</pre><ul><li>scoutAdvicePrompt is used to append the system prompt for providing FPL advice. You can find the system prompt <a href="https://github.com/rubenquadros/GameweekScout-Server/blob/main/ai/src/main/kotlin/io/github/rubenquadros/gameweekscout/server/ai/SystemInstruction.kt#L19">here</a>.</li><li>provideSuggestion is responsible for reading the user query and answering it.</li><li>To answer the user query accurately, the LLM requires access to the latest FPL data. This data retrieval is facilitated via tool calls. In our case, we make use of the <a href="https://docs.koog.ai/annotation-based-tools/"><strong><em>Annotation based tools feature</em></strong></a>. You can find all the available methods <a href="https://github.com/rubenquadros/GameweekScout-Server/blob/main/fpl/src/main/kotlin/io/github/rubenquadros/gameweekscout/server/fpl/FplApiImpl.kt">here</a>.</li><li>assistantResponseRepeatMax limits how many assistant responses are allowed before concluding the task cannot be completed. This is important to prevent the agent from getting stuck in an infinite loop.</li></ul><h4>Step 3: Compression</h4><pre>edge(processInput forwardTo evaluateCompression onCondition { it.shouldProceed })<br>edge(evaluateCompression forwardTo compressHistory onCondition { it.shouldCompress })<br>edge(evaluateCompression forwardTo scoutAdvicePrompt onCondition { !it.shouldCompress })</pre><pre>val evaluateCompression by node&lt;InputProcessResponse, InputProcessResponse&gt; { input -&gt;<br>  llm.readSession {<br>    val messages = prompt.messages<br>    val shouldCompress = messages.size &gt; 20 &amp;&amp; messages.sumOf { it.content.length } &gt; 200_000<br><br>    input.copy(shouldCompress = shouldCompress)<br>  }<br>}<br><br>val compressHistory by node &lt;InputProcessResponse, InputProcessResponse&gt;(&quot;compress-history&quot;) { input -&gt;<br>  llm.writeSession {<br>    model = geminiConfig.getLLMModel(geminiConfig.model.retrieval)<br>    replaceHistoryWithTLDR(<br>        strategy = RetrieveFactsFromHistory(concepts = fplConceptsToCompressHistory)<br>      )<br>    }<br><br>  input<br>}</pre><ul><li>evaluateCompression is responsible to determine if history compression is required. <a href="https://api.koog.ai/agents/agents-core/ai.koog.agents.core.agent.context/-a-i-agent-l-l-m-context/read-session.html">llm.readSession</a> provides the current state of the agent. In our case, we check if the the total back and forth messages in the session are over 20 and if the total characters of the messages is over 200,000.</li><li>compressHistory is responsible to perform history compression. <a href="https://api.koog.ai/agents/agents-core/ai.koog.agents.core.agent.context/-a-i-agent-l-l-m-context/write-session.html">llm.writeSession</a> provides the <a href="https://api.koog.ai/agents/agents-core/ai.koog.agents.core.dsl.extension/replace-history-with-t-l-d-r.html">replaceHistoryWithTLDR</a> method to modify the agent state.</li><li>We make use of the <a href="https://api.koog.ai/agents/agents-features/agents-features-memory/ai.koog.agents.memory.feature.history/-retrieve-facts-from-history/index.html">RetrieveFactsFromHistory</a> strategy to compress the history. This strategy searches for specific facts in the message history and compresses the whole history to just these facts for future LLM requests. You can find all the facts <a href="https://github.com/rubenquadros/GameweekScout-Server/blob/main/ai/src/main/kotlin/io/github/rubenquadros/gameweekscout/server/ai/Compression.kt">here</a>.</li><li><strong><em>Note:</em></strong> This is a approach needs to be observed and can be tweaked based on real world data.</li></ul><h4>Step 4: Persistence</h4><ul><li>For our particular use case, we enable continuous persistence. This ensures that a checkpoint is automatically created after each node is run.</li><li>We leverage the custom storage provider feature by using <a href="https://firebase.google.com/docs/firestore">Firestore database</a> as our storage source. To implement custom storage providers, we will have to define our own <a href="https://api.koog.ai/agents/agents-features/agents-features-snapshot/ai.koog.agents.snapshot.providers/-persistence-storage-provider/index.html">PersistenceStorageProvider</a>.</li></ul><pre>class FirestorePersistenceProvider : PersistenceStorageProvider&lt;Any&gt; {<br><br>    override suspend fun getCheckpoints(<br>        agentId: String,<br>        filter: Any?<br>    ): List&lt;AgentCheckpointData&gt; {<br>        // return all saved checkpoints from firestore<br>    }<br><br>    override suspend fun saveCheckpoint(<br>        agentId: String,<br>        agentCheckpointData: AgentCheckpointData<br>    ) {<br>        // insert data into firestore<br>    }<br><br>    override suspend fun getLatestCheckpoint(<br>        agentId: String,<br>        filter: Any?<br>    ): AgentCheckpointData? {<br>        // return last saved checkpoint<br>    }<br>}</pre><ul><li>You can find the custom implementation <a href="https://github.com/rubenquadros/GameweekScout-Server/blob/main/ai/src/main/kotlin/io/github/rubenquadros/gameweekscout/server/ai/persistence/FirestorePersisrenceProvider.kt">here</a>.</li></ul><h3>Putting it all together</h3><p>Now that we understood how to implement the different components, let’s stitch it all together to create our final agent!</p><pre>// Agent tools - FPL data retrieval <br>private val fplToolsRegistry = ToolRegistry {<br>  tools(fplApi)<br>}<br><br>// Main agent strategy<br>private val scoutStrategy = strategy&lt;String, String&gt;(&quot;fpl-scout&quot;) {<br>  <br>  // User intent detection<br>  val inputProcessingPrompt by nodeAppendPrompt&lt;String&gt;(&quot;input-process-prompt&quot;) {<br>    system(inputProcessInstruction)<br>  }<br><br>  val processInput by subgraphWithTask&lt;String, InputProcessResponse&gt;(<br>    name = &quot;input-processor&quot;,<br>    llmModel = geminiConfig.getLLMModel(geminiConfig.model.inputProcess),<br>    assistantResponseRepeatMax = 1<br>   ) { input -&gt;<br>      &quot;&quot;&quot;<br>        User query: &quot;$input&quot;<br>      &quot;&quot;&quot;.trimIndent()<br>  }<br><br>  // History compression<br>  val evaluateCompression by node&lt;InputProcessResponse, InputProcessResponse&gt; { input -&gt;<br>    llm.readSession {<br>      val messages = prompt.messages<br>      val shouldCompress = messages.size &gt; 20 &amp;&amp; messages.sumOf { it.content.length } &gt; 200_000<br><br>      input.copy(shouldCompress = shouldCompress)<br>    }<br>  }<br><br>  val compressHistory by node &lt;InputProcessResponse, InputProcessResponse&gt;(&quot;compress-history&quot;) { input -&gt;<br>    llm.writeSession {<br>      model = geminiConfig.getLLMModel(geminiConfig.model.retrieval)<br>      replaceHistoryWithTLDR(<br>        strategy = RetrieveFactsFromHistory(concepts = fplConceptsToCompressHistory)<br>        )<br>      }<br><br>    input<br>  }<br><br>  // FPL advice <br>  val scoutAdvicePrompt by nodeAppendPrompt&lt;InputProcessResponse&gt;(&quot;scout-advice-prompt&quot;) {<br>    system(scoutAdviceInstruction)<br>  }<br><br>  val provideSuggestion by subgraphWithTask&lt;InputProcessResponse, String&gt;(<br>    name = &quot;scout-advisor&quot;,<br>    llmModel = geminiConfig.getLLMModel(geminiConfig.model.suggestion),<br>    tools = fplToolsRegistry.tools,<br>    assistantResponseRepeatMax = 5<br>  ) { context -&gt;<br>      &quot;&quot;&quot;<br>        User query: &quot;${context.originalInput}&quot;<br>      &quot;&quot;&quot;.trimIndent()<br>  }<br><br>  // Agent workflow<br>  edge(nodeStart forwardTo inputProcessingPrompt)<br>  edge(inputProcessingPrompt forwardTo processInput)<br>  edge(processInput forwardTo nodeFinish onCondition { !it.shouldProceed } transformed { it.response })<br>  edge(processInput forwardTo evaluateCompression onCondition { it.shouldProceed })<br>  edge(evaluateCompression forwardTo compressHistory onCondition { it.shouldCompress })<br>  edge(evaluateCompression forwardTo scoutAdvicePrompt onCondition { !it.shouldCompress })<br>  edge(compressHistory forwardTo scoutAdvicePrompt)<br>  edge(scoutAdvicePrompt forwardTo provideSuggestion)<br>  edge(provideSuggestion forwardTo nodeFinish)<br>}<br><br>// Invoking the agent<br>val agent = AIAgent(<br>    id = userId,<br>    strategy = scoutStrategy,<br>    promptExecutor = simpleGoogleAIExecutor(apiKey = geminiConfig.apiKey),<br>    llmModel = geminiConfig.getLLMModel(geminiConfig.model.default),<br>    toolRegistry = fplToolsRegistry<br>) {<br>    // Custom persistence<br>    install(Persistence) {<br>      storage = FirestorePersistenceProvider() <br>      enableAutomaticPersistence = true<br>      rollbackStrategy = RollbackStrategy.MessageHistoryOnly<br>    }<br><br>  }<br><br>agent.run(input)</pre><p>You can find the entire agent <a href="https://github.com/rubenquadros/GameweekScout-Server/blob/main/ai/src/main/kotlin/io/github/rubenquadros/gameweekscout/server/ai/ScoutService.kt">here</a>.</p><p>That’s it! With these steps, you have a fully functioning AI agent which answers all your FPL queries and helps you build the perfect team for future game weeks!</p><p>You can find the source code of the agent in this repository.</p><p><a href="https://github.com/rubenquadros/GameweekScout-Server">GitHub - rubenquadros/GameweekScout-Server: AI agent which helps you with suggestions for your FPL team.</a></p><p>Contributions are most welcome! Please feel free to create issues or start a discussion if you are looking for more features or want to improve something!</p><h3>What’s next?</h3><p>I’m working on creating a web app (Compose web) so users can ask their queries and this agent will serve as a backend to answer them! This is something I’m working on as a hobby project but if you are interested, please free to reach out to me on slack via the <a href="https://kotlinlang.slack.com/team/U02GM18985A">kotlinlang slack workspace</a>!</p><h3>Additional resources</h3><ul><li><a href="https://docs.koog.ai/">Official Koog documentation</a> to know more about Koog and it’s features.</li><li><a href="https://api.koog.ai/index.html">Official Koog API reference</a> for a deep dive into the API.</li><li>Koog articles by <a href="https://medium.com/@vadim.briliantov">Vadim Briliantov</a> — creator of Koog.</li><li><a href="https://blog.jetbrains.com/ai/2025/11/building-ai-agents-in-kotlin-part-1-a-minimal-coding-agent/">Jetbrains blog series</a> on building AI agents in Kotlin. Please read all the parts!</li><li><a href="https://github.com/JetBrains/koog">Github repo</a> of Koog for more examples.</li></ul><p>Thanks for reading! If you liked the article please do leave a clap 👏 and don’t forget to subscribe and follow to get regular updates! :) You can also connect with me on <a href="https://www.linkedin.com/in/ruben-quadros-b87995173/">LinkedIn</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=86cd3f40c084" width="1" height="1" alt=""><hr><p><a href="https://blog.kotlin-academy.com/building-reliable-ai-agents-with-koog-86cd3f40c084">Building Reliable AI Agents with Koog</a> was originally published in <a href="https://blog.kotlin-academy.com">kt.academy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Small habits that prevent real user pain]]></title>
            <link>https://blog.kotlin-academy.com/small-habits-that-prevent-real-user-pain-0a90df9e712d?source=rss----e57b304801ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/0a90df9e712d</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[kotlin-coroutines]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <dc:creator><![CDATA[Marcin Moskala]]></dc:creator>
            <pubDate>Tue, 31 Mar 2026 15:01:04 GMT</pubDate>
            <atom:updated>2026-03-31T15:01:03.585Z</atom:updated>
            <content:encoded><![CDATA[<p>Hello Kotliners</p><p>Today’s issue mixes “small Compose habits that prevent real user pain” with a couple of Kotlin tools you can apply instantly. We’ll talk about where important state should live (and why remember is not enough), a neat way to make preconditions explicit with require/check, a Compose clickability gotcha, and then zoom out to the bigger picture: what Kotlin Coroutines quietly give you for free when you lean into their strengths.</p><p><strong>In this issue you will find:</strong></p><p>💪 Compose best practice: Keeping important state in view models or rememberSaveable</p><p>👉 Require and check</p><p>💡 Compose hint: clickable modifier has parameter enabled</p><p>📈 Kotlin Coroutine advantages</p><p>📝 Article: IntelliJ’s New Kotlin Coroutine Inspections, Explained</p><p>⭐ Project from Coroutines Mastery 2025: ReviewFlow</p><p>⏳ Price increases April 1 — this isn’t a joke.</p><p>Advanced Compose moves from €299 → €449 tomorrow.</p><p>The Bundle moves from €449 → €599.</p><p>If you’ve been considering joining, this is the final window at the Early Bird rate. The cohort starts April 13, and we won’t run it again this year.</p><p>This is the last reminder.</p><p>Details and enrollment are here → <a href="http://www.advancedcompose.com/">www.advancedcompose.com</a></p><p>Now, let’s move to today’s topic.</p><h3>New videos</h3><p>Our YouTube channel is growing! This week, you can learn the essence of how Compose works in 3 minutes, watch a funny video explaining why developers count from 0, and learn why extensions are not changing class behavior. Remember, you can help us with channel development by sharing those videos, leaving likes and comments, and, of course, subscribing and watching.</p><p><a href="https://www.youtube.com/watch?v=7Ura2g5piyo"><strong>Understand Compose in 3 minutes</strong></a></p><figure><a href="https://www.youtube.com/watch?v=7Ura2g5piyo"><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1nMTfLGKHV0VFVTz.png" /></a></figure><p><a href="https://www.youtube.com/watch?v=ddTkt4aEgEI&amp;pp=0gcJCdYKAYcqIYzv"><strong>In what order are Compose modifiers applied?</strong></a></p><figure><a href="https://www.youtube.com/watch?v=ddTkt4aEgEI&amp;pp=0gcJCdYKAYcqIYzv"><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Ij4K_rPrIFd_p5hk.png" /></a></figure><p><a href="https://www.youtube.com/watch?v=-l9drx2YBBY"><strong>The most common and hurtful misconception about Kotlin</strong></a></p><figure><a href="https://www.youtube.com/watch?v=-l9drx2YBBY"><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*suyRi4XM3ndZvNMq.png" /></a></figure><h3>Compose best practice: Keeping important state in view models or rememberSaveable</h3><p>Beware❗Do not use remember for important state, as it can easily be lost, for instance with screen rotation. Everything that is important should be kept in a view model on in rememberSavable 👍</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IrRlYSbKjyNX_qEE.png" /></figure><p>On the other hand, remember that rememberSavable is more demanding, so it should only be used for data that should persist configuration changes. Regarding form data, I don’t think users would be happy with having it lost.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/942/0*kkdmmAa8MWGwrJ-v.gif" /></figure><h3>Require and check</h3><p>In Kotlin you can use require and check to enforce preconditions in your code. require throws IllegalArgumentException, so it is used to check arguments, and check throws IllegalStateException, so it is used to check state.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*UnGkuTvxauqnWyZa.png" /></figure><h3>Compose hint: clickable modifier has parameter enabled</h3><p>Clickable modifier can be disabled by argument, do not use conditional modifier for that!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*KOn_3-K67GolaA3D.png" /></figure><h3>Kotlin Coroutine advantages</h3><p>Developers often grumble about all the complexity that Kotlin Coroutines hide under the carpet. But this library also hides its best advantages; we often benefit from them without even knowing about it. So let’s explore the key advantages of Kotlin Coroutines.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2TBmktz_5U9dpal4.jpg" /></figure><p>Let’s start with simplicity. With coroutines, we can write simple, imperative code. For backend developers, that is nothing new, but for Android it is a game-changer that we can have that while running on the main thread.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/470/0*eyG7rJrtWJVRupUd.png" /></figure><p>At the same time, coroutines allow a simple introduction of synchronicity with async/await. That is a well-recognized pattern, but in Kotlin Coroutines it offers structured concurrency with ease.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/442/0*V5aO5_UAUMX8EQgp.png" /></figure><p>Coroutines are also efficient. Coroutines are lighter than threads. Suspending functions are much lighter than Single, and Flow is much lighter than Observable. Coroutines can only be compared to virtual threads from Loom, but it is only because Loom uses its own coroutines under the hood.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/954/0*teVBeoFuegZsxbLq.png" /></figure><p>Programmers also underestimate the importance of cancellation. It is critical for Android developers where if a view is destroyed, we should also cancel all its processes. Coroutines offer powerful and effortless cancellation thanks to structured concurrency.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/423/0*wekINXvtfLoimusl.png" /></figure><p>But cancellation mechanisms are also a great benefit for backend developers. If you make async calls and one of them fails, others are cancelled by default.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/454/0*ZaM4s1HtsSHEL28h.png" /></figure><p>Also when you define a backend application in a framework like Ktor, that is coroutines-first, if an HTTP connection is lost, call gets immediately cancelled, the same if WebSocket or RSocket connection is lost.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/471/0*nM9umRWcrjzhQp_3.png" /></figure><p>Synchronization offers numerous powerful tools for synchronizing coroutines. You can easily make one coroutine wait for another coroutine to complete using join or suspend until another coroutine provides a value using CompletableDeferred or Channel.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/310/0*_8v9Vq5-aMdQfvk5.png" /></figure><p>Coroutines also offer the best support for testing I could see in any library. Kotlin Coroutines supports operating in virtual time, which lets us write precise, fast, and deterministic tests for cases that are hard to test with other libraries.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/533/0*GScqHCbLfUIyYXEx.png" /></figure><p>Virtual time also allows us to test timeouts, retries, and other time-related operations. It also allows us to verify what happens in different scenarios. What if X is faster than Y but slower than Z? What if X is faster than Z? We can easily simulate and test all these scenarios with virtual time!</p><p>Kotlin Coroutines provide a powerful abstraction for expressing and processing asynchronous streams of values. This abstraction is called Flow. Flow is much lighter than RxJava or Reactor streams, and its implementation is much easier. It also has a rich feature set with many useful operators.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/560/0*2v8eaejp9DPGe9Qj.png" /></figure><p>Coroutines are multiplatform, so you can use them in any Kotlin target and in common modules.</p><p>That is why I believe that Kotlin Coroutines is currently the best tool in the market for concurrency. Certainly the best for Android, but also the best for the backend, but only if we use its advantages instead of just treating it like “lightweight threads”.</p><h3>Article: IntelliJ’s New Kotlin Coroutine Inspections, Explained</h3><figure><a href="https://kt.academy/article/intellij-coroutines-new-warnings"><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*pPiBQCvJU4tYRzDp.png" /></a></figure><p>How many coroutine issues in code review come from patterns that “work”, but are still misleading, inefficient, or unsafe?</p><p>I break down IntelliJ’s new Kotlin coroutine inspections and explain what they actually mean in practice. This article is especially useful if you work with coroutines regularly and want sharper mental models, not just IDE hints.</p><p><a href="https://kt.academy/article/intellij-coroutines-new-warnings">https://kt.academy/article/intellij-coroutines-new-warnings</a></p><h3>Project from Coroutines Mastery 2025: ReviewFlow</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*USbMEGXiA82xVjre.png" /></figure><p>Dealing with Play Store In-App Review orchestration on Android can be hard, but here comes ReviewFlow library that makes it easy and coroutines-friendly. This great project was made as a final project on Coroutines Mastery, congratulations to Klemens Zleptnig for making such an ambitious project.</p><p>👉 Code: <a href="https://github.com/klemensz/android-review-flow">https://github.com/klemensz/android-review-flow</a></p><p>What I like most is when coroutine knowledge turns into a real solution to a real problem. That is exactly what we aim for in Coroutines Mastery. If you want to build this kind of practical understanding, join the next edition this November.</p><p>Hope you found this newsletter worthwhile.</p><p>Marcin Moskała<br> — kt.academy</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0a90df9e712d" width="1" height="1" alt=""><hr><p><a href="https://blog.kotlin-academy.com/small-habits-that-prevent-real-user-pain-0a90df9e712d">Small habits that prevent real user pain</a> was originally published in <a href="https://blog.kotlin-academy.com">kt.academy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fighting with bugs]]></title>
            <link>https://blog.kotlin-academy.com/fighting-with-bugs-caa3361a9629?source=rss----e57b304801ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/caa3361a9629</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[kotlin-coroutines]]></category>
            <dc:creator><![CDATA[Marcin Moskala]]></dc:creator>
            <pubDate>Tue, 24 Mar 2026 16:01:02 GMT</pubDate>
            <atom:updated>2026-03-24T16:01:01.169Z</atom:updated>
            <content:encoded><![CDATA[<p>Hello Kotliners,</p><p>Today is about small decisions that quietly shape correctness and performance. We’ll start with a Kotlin gotcha that looks harmless but isn’t, then jump into one of the most useful Compose tools for keeping recompositions under control, and we’ll close with a reminder that still saves people from wrong mental models: suspending functions are <em>not</em> coroutines.</p><p><strong>In this issue you will find:</strong></p><p>🎦 New YouTube videos</p><p>💡 Do not use object expressions for exceptions</p><p>💪 The power of derivedStateOf</p><p>🔎 Suspending functions are not coroutines!</p><p>📝 Article: Flow Guessing Game!</p><p>⭐ Project from Coroutines Mastery 2025: Comet</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-uhwrU--zksMl6e_x9pKmA.jpeg" /></figure><p>We’re currently enrolling for the <strong>Advanced and Polished Compose cohort course</strong>.</p><p>The content and schedule are already fixed.</p><p>What changes over time is only the entry price.</p><p>The regular price is <strong>€449</strong>.</p><p>Until March 31, you can join for <strong>€299</strong>.</p><p>If this course fits the work you’re doing now, the current pricing window is still open.</p><p>You’ll attend the first sessions and evaluate the depth and pace yourself.</p><p>If it turns out not to be the right fit, you can step back within the first 3 days.</p><p>Strong material welcomes early inspection.</p><p><strong>Join Advanced Compose</strong> — <a href="http://www.advancedcompose.com">www.advancedcompose.com</a></p><p><strong>Join Polished Compose</strong> — <a href="http://www.polishedcompose.com">www.polishedcompose.com</a></p><p>This is what a developer said about our previous cohort:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tZ2CJtKCCxvyUPMKcDyuig.jpeg" /></figure><h3>New YouTube videos by Marcin Moskała</h3><p>Our YouTube is growing! Last week we published three short videos:</p><p><strong>The bug that nearly destroyed Earth</strong> is a terrifying story that nearly no one talks about. Presented in visually entertaining form:</p><figure><a href="https://www.youtube.com/watch?v=xakNW96l3r4"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SnBTVQQDyqetJXm5YqObng.png" /></a></figure><p><a href="https://www.youtube.com/watch?v=xakNW96l3r4">https://www.youtube.com/watch?v=xakNW96l3r4</a></p><p><strong>In what order are Compose modifiers applied?</strong> There is a clear explanation of how Compose modifiers work. It is related to another video, which presents our Modifier Game and how to win it.</p><figure><a href="https://www.youtube.com/watch?v=ddTkt4aEgEI"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gxX_Lg-PaZCBLX4MgIKKYw.png" /></a></figure><p><a href="https://www.youtube.com/watch?v=ddTkt4aEgEI">https://www.youtube.com/watch?v=ddTkt4aEgEI</a></p><figure><a href="https://www.youtube.com/watch?v=1exvDo5wuqc"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1F7Q2e5Cm2U0BMg6pfxnDA.png" /></a></figure><p><a href="https://www.youtube.com/watch?v=1exvDo5wuqc">https://www.youtube.com/watch?v=1exvDo5wuqc</a></p><h3>Do not use object expressions for exceptions</h3><p>Exceptions should not be represented with object declarations❗ . Even though they look like having no mutable state, they do — each exception contains a stacktrace, so each exception must be unique.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WstX0GIuYNY0Ac-0Omaqaw.png" /></figure><h3>The power of derivedStateOf</h3><p>derivedStateOf is an important function in Compose that we use for limiting the number of recompositions. Let me show you how it works and when it should be used. 👇</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YKurn5H8JWr95OSwVXLZiw.png" /></figure><p>First the problem with a raw value calculation. Such a calculation is only appropriate if it is really cheap, as it is triggered by any recomposition, so by any value change. It also triggers whole composable recomposition for changes in state properties it uses. (2/4)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Bu1tNU9XG33dVH1nojx0Vg.png" /></figure><p>Using remember with a key is enough to limit the number of recalculations, but to make it work correctly, we must use state as a key. That requires reading snapshot state in component body, so recomposing it whenever this body changes. derivedStateOf provides narrow scope, so allows us to prevent parent recomposition.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JWo5wGdEOptQEL9COLVMAg.png" /></figure><p>Though you must remember, that derivedStateOf body can only observe state, it cannot observe parameters. Take a look at the above example: threshold must be a key of remember!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sLq5dWO3SIMaNztOLU5TWg.png" /></figure><h3>Suspending functions are not coroutines!</h3><p>Suspending functions are not coroutines! Those are functions that can suspend a coroutine, so they must be called from a coroutine, but they are not coroutines themselves. Compare them to JavaScript async functions that represent asynchronous coroutines.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UUwjNAB3brjDO53B5wamPg.png" /></figure><h3>Article: Flow Guessing Game!</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/466/1*AloIer8yN6dn2cSoSHPUHQ.gif" /></figure><p>One of the most useful questions you can ask yourself when reading Flow code is:</p><p>“Can I predict the behavior before I run it?”</p><p>If the answer is no, that usually means your understanding is still too close to syntax and not yet close enough to execution. And with Flow, that distinction matters a lot. Pipelines can stay readable on the surface while hiding assumptions that turn out to be wrong in practice.</p><p>That is why I made this game. More about the game can be found in this <a href="https://kt.academy/article/game-flow">article</a></p><p>It is a direct exercise in Flow reasoning: inspect the pipeline, think carefully, and decide what it does. Not approximately. Precisely.</p><p>A good challenge for developers who want to move from “I know the operators” to “I understand the behavior”.</p><p>Play here: <a href="https://game.kt.academy/?game=flow">https://game.kt.academy/?game=flow</a></p><h3>⭐ Project from Coroutines Mastery 2025: Comet</h3><p>Today I want to share with you a stunning library and top final project on Coroutines Mastery 2025. Comet by Pandu Baraja is a library for automatically tracking coroutines and their execution time. It allows us to track and analyze coroutine execution in real-time. It still has some limitations, but man, that seems like a very powerful idea!</p><p>Code 👉 <a href="https://github.com/pandubaraja/comet">https://github.com/pandubaraja/comet</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*hu52mDnsale5o_C50P9TBg.gif" /></figure><p>I hope you found something useful in this newsletter.</p><p>Marcin</p><p>— kt.academy</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=caa3361a9629" width="1" height="1" alt=""><hr><p><a href="https://blog.kotlin-academy.com/fighting-with-bugs-caa3361a9629">Fighting with bugs</a> was originally published in <a href="https://blog.kotlin-academy.com">kt.academy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Lambdas can trick you hard]]></title>
            <link>https://blog.kotlin-academy.com/lambdas-can-trick-you-hard-eac3aaee269b?source=rss----e57b304801ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/eac3aaee269b</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlin-coroutines]]></category>
            <dc:creator><![CDATA[Marcin Moskala]]></dc:creator>
            <pubDate>Tue, 17 Mar 2026 16:01:01 GMT</pubDate>
            <atom:updated>2026-03-17T16:01:01.132Z</atom:updated>
            <content:encoded><![CDATA[<p>Hello Kotliners,</p><p>This one is a “mental models” issue: UiState pattern in Compose, Kotlin lambda traps that bite even experienced devs, and a Compose trait that explains a whole class of “why did my component lost state” bugs. We’ll also zoom out to Flow itself — because a lot of confusion disappears once you internalize that Flow is synchronous by nature.</p><p><strong>In this issue you will find:</strong></p><p>🎦 New YouTube videos</p><p>👉 UiState pattern</p><p>🤕 Traps and mistakes when using lambda expressions</p><p>🤯 Composable identity</p><p>💡 Flow implementation</p><p>⭐ Project from Coroutines Mastery 2025: Where2fly</p><h3>Reminder: Start With Nav3 is on March 19</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vxrC1qqGq2_6S24JcEfLlA.jpeg" /></figure><p>Quick reminder — we’re going live on <strong>March 19 at 1pm (UTC+1)</strong> with <strong>Start With Nav3</strong>.</p><p>Bring your questions for the <strong>live Q&amp;A</strong> — or send them in advance.</p><p><strong>Can’t join live?</strong> Register anyway to get <strong>recording access</strong>.</p><p>Register (free) → <a href="https://webinar.kt.academy/start-with-nav3-05">https://webinar.kt.academy/start-with-nav3-05</a></p><h3>What does this require, week to week?</h3><p>One question engineers ask before joining the cohort is simple:</p><p><em>What does the weekly workload actually look like?</em></p><p>We recorded a short 1-minute walkthrough showing the structure — Busy Mode, Regular Mode, and how the schedule fits around a normal engineering week.</p><p>Watch the overview here → <a href="https://advancedcompose.com/#how-the-course-works">https://advancedcompose.com/#how-the-course-works</a></p><figure><a href="https://advancedcompose.com/#how-the-course-works"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y0iYV3CsHpgdsKAZw7-JaQ.jpeg" /></a><figcaption>What doeas this require week to week?</figcaption></figure><h3>New YouTube videos by Marcin Moskała</h3><p>Great news, everyone! We started posting content on YouTube. Those are short and entertaining videos on a variety of topics. We are ready to release two new videos every week, so if you liked this one, remember to subscribe!</p><p>The first video is about Kotlin, not an enemy, but as a savior of Java. Make sure to send it to all your Java-loving friends ;)</p><figure><a href="https://youtu.be/82XT--LmVDA"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GUquy_eeE6qDAzeoQNhQKQ.png" /></a></figure><p><a href="https://youtu.be/82XT--LmVDA">https://youtu.be/82XT--LmVDA</a></p><p>The second one presents a new player in UI development. Who could it be? I know you know, but let’s discover it together, and see why Compose is in many ways better than big frontend frameworks. Make sure to send this one to your friends who are frontend developers ;)</p><figure><a href="https://youtu.be/T-8yoCSnwnU"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OHClNmqgniSjX13cUy3T7w.png" /></a></figure><p><a href="https://youtu.be/T-8yoCSnwnU">https://youtu.be/T-8yoCSnwnU</a></p><p>I hope you had as much fun watching those videos as I had making them. This is just the beginning, so observe this channel, and remember that you can support it by sharing it with others, leaving comments, or likes. It is especially important now, since we just started, and a small kick would be really useful.</p><h3>UiState pattern</h3><p>One of the most heated discussion in Android is about using UiState pattern. UiState aggregates all possible states of a view into one hierarchy. In result:</p><p>👉 Our view is consistently represented in one property.</p><p>👉 We do not need to deal with properties that are currently not displayed (like news, when we are still loading).</p><p>👉 We update our views using copy, what eliminates dependencies between updated properties (like when we first change a, and then b, and we accidentally calculate new state of b based on the new state of a, instead of on the old one).</p><p>😨 In complex views, UiState can become really big and complex.</p><p>😨 Using UiState makes it harder (but not impossible) to optimise recomposition by moving reads directly to where they are needed (for that, the most efficient way is defining State in view model).</p><p>🤔 UiState often duplicates some classes from domain (what adds boilerplate, but some consider beneficial as it defines ui-specific representation of those classes).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PxQU48pqAOyfw3EA9Ta-xg.png" /></figure><h3>Traps and mistakes when using lambda expressions</h3><p>Lambda expressions are amazing, but they can also be very tricky. Here is my collection of the most common traps and mistakes developers make when they use lambda expressions.</p><p>Each lambda expression is defined with braces. Raw braces that are not part of any structure make lambda expressions. What is printed by the below code? The answer is nothing. This lambda expression is never called.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/448/1*MNHGBmYZkCM8SWi0VN3PRw.png" /></figure><p>A common mistake, especially by developers with a Scala background, is to create lambdas in single-expression functions. The below code does not print 42; it prints the lambda expression name.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/418/1*YwE4-5rjqjHzYqVmnQe0CA.png" /></figure><p>We can destructure in lambdas, what can be a bit confusing in some cases. This code prints John Doe and Jane Smith in the first case, but only John and Jane in the second case.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4neW15kCxGBNZJZefvnTEw.png" /></figure><p>Trailing lambdas (outside arguments bracket) always refer to the last parameter. Below code prints first CA and then AB. That is why when we have multiple functional parameters it is best practice to call them by name.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/962/1*lvsnEzeF8PMvaLm4RnnmXg.png" /></figure><p>Lambdas return the result of the last expression as a result. Here it means 30.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/346/1*gYaYaR8Gk45s78fzGbYx4A.png" /></figure><p>Using raw return means non-local return, so returning from the closes function. To return from lambda expression you need to use a label.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/618/1*q2sgr6pI5_31gXNQ49KcEQ.png" /></figure><h3>Composable identity</h3><p>When is a composable considered the same one and when is it considered a different one? Let’s discuss composables identity for a moment. 🤔</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Oh227uyR0bQH79vZUJ5mfQ.png" /></figure><p>When you define a composable, each composable it calls received unique ID. State is persisted for the same ID path, even if arguments are different. Unless this composable disappears, even for a moment, because in such case its state is lost. 🤯</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6TklR4sQsYvIALI3AZykfA.png" /></figure><p>When composables are created from enumerations, like loops, they are by default identified by position. That means, adding an element at the beginning or in the middle causes recomposition of later composables. We improve that by specifying a key for identifying composables.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qKuUcCUl_u1cW82QuPfqaA.png" /></figure><p>If you need to persist identity of a component that changes place, that can be achieved with movableContentOf. It allows using the same composable objects as children of different parents. That is especially useful when we define adaptive layouts. ✅</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G2eo1aHj71zpbbbTJ8bMwQ.png" /></figure><h3>Flow implementation</h3><p>Flow is synchronous by its nature. That is why the below code prints numbers with a delay of 1 second between each, and “Done” is printed after the last value. There is only one coroutine. Flow is like a suspending lambda expression. Of course, there are functions on Flow that start more coroutines, like flatMapMerge or buffer, but the nature of Flow is that it is synchronous, just like suspending functions</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HbBP-7vGhHYJ4PFmcgY1Ug.png" /></figure><h3>Project: Where2fly</h3><p>Time for an application for passionates of aviation. Where2fly by Krzysztof Cieślewicz is an unofficial Android version of <a href="http://airspace.pansa.pl">airspace.pansa.pl</a> web application. It allows downloading and viewing basic airspace and updated usage plans and related information for a Warsaw flight information region. It is well-written and looks good. It also presents how to deeply test view models and repositories using the power of kotlinx-coroutines-test.</p><p>Code 👉 <a href="https://gitlab.com/krzysztof.cieslewicz1/where-to-fly">https://gitlab.com/krzysztof.cieslewicz1/where-to-fly</a></p><p>It was written as a final project on the <a href="http://www.coroutinesmastery.com">Coroutines Mastery 2025 course</a>.</p><figure><a href="https://gitlab.com/krzysztof.cieslewicz1/where-to-fly"><img alt="" src="https://cdn-images-1.medium.com/max/800/1*8Q-aUWtUaVUgecfNYheYtQ.gif" /></a></figure><p>I hope you enjoyed this newsletter.</p><p>— Marcin<br>kt.academy</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eac3aaee269b" width="1" height="1" alt=""><hr><p><a href="https://blog.kotlin-academy.com/lambdas-can-trick-you-hard-eac3aaee269b">Lambdas can trick you hard</a> was originally published in <a href="https://blog.kotlin-academy.com">kt.academy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tools for Kotlin developers, and how to use them well]]></title>
            <link>https://blog.kotlin-academy.com/tools-for-kotlin-developers-and-how-to-use-them-well-25d0d5343764?source=rss----e57b304801ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/25d0d5343764</guid>
            <category><![CDATA[jetpack-compose]]></category>
            <dc:creator><![CDATA[Marcin Moskala]]></dc:creator>
            <pubDate>Tue, 10 Mar 2026 16:01:01 GMT</pubDate>
            <atom:updated>2026-03-10T16:01:01.029Z</atom:updated>
            <content:encoded><![CDATA[<p>Hello Kotliners,</p><p>This time we come to add some tools to your toolbox. We will explore interesting use of operators and context parameters, learn about the difference between StateFlow and SharedFlow, to finally explore why setting placeholder image is not only for user, but also for its device.</p><p>In this issue you will find:<br>💡 invoke method in UseCase classes<br>👉 stateInUi<br>📝 StateFlow vs SharedFlow<br>🎬 Placeholder image in a lazy grid<br>📝 Article: From Python to Kotlin: A transition worth making<br>⭐ Project from Coroutines Mastery 2025: VizCor</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m9N4h8L_86E9NXUfISE2WQ.jpeg" /></figure><p>Here is what developers said after our previous cohort.</p><p>Advanced Compose and Polish Compose have the same rich cohort learning format — lessons, hands-on exercises, interactive games, Q&amp;As, and an active community.</p><p>Join Advanced Compose — <a href="http://www.advancedcompose.com">www.advancedcompose.com</a></p><p>Join Polish Compose — <a href="http://www.polishedcompose.com">www.polishedcompose.com</a></p><h3>New webinar: Start With Nav3 (Navigation3) — build a solid mental model</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iV7YvoZR_IwnfqWTGQs-yg.jpeg" /></figure><p>Nav3 (Navigation3) looks promising, but the hard part is getting the <em>foundation</em> right: what the core building blocks are, how the backstack works, and how to structure navigation flows so they stay readable as the app grows.</p><p>That’s exactly what we’ll cover in <strong>Start With Nav3</strong> — hosted by <strong>Jov Mit and me.</strong></p><p><strong>When:</strong> 🗓️ <strong>March 19, 2026 (Thu)</strong> • 🕐 <strong>1pm (UTC+1)</strong></p><p><strong>Format:</strong> 30–60 min talk + 30 min live Q&amp;A • 🎥 recording included • 🆓 free upon registration</p><p>Reserve your seat → <a href="https://webinar.kt.academy/start-with-nav3-05">https://webinar.kt.academy/start-with-nav3-05</a></p><p><strong>Note:</strong> The join link is emailed after signup. You can submit questions before or during the webinar.</p><p>If you have missed it here It’s a concise, practical reference focused on how Compose actually behaves — Compose Advanced Cheat Sheet</p><p><a href="https://cheat-sheet.kt.academy/advanced-compose06">https://cheat-sheet.kt.academy/advanced-compose06</a></p><h3>invoke method in UseCase classes</h3><p>One practice (quite common in Android) is to make the only public function in use case classes invoke operator. Use cases should have only one public method anyway, and it makes it easier o call it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HDCptXMOxYePlNlLJR874w.png" /></figure><h3>stateInUi</h3><p>Happy to share with you stateInUi, a handy little helper function for idiomatically turning Flow into StateFlow in view models. It is also a nice example of using Context Parameters.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JP3RKZAy-xBky8S3Up8GEw.png" /></figure><h3>StateFlow vs SharedFlow</h3><p>StateFlow and SharedFlow seem similar, but they are designed for different use cases, and they should not be confused. Let’s discuss the key differences and usages.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gSDPjOo3UbpAL4KFuFYNvQ.png" /></figure><p>Let’s start with how they work. SharedFlow resembles a broadcast channel — emitted values are delivered to all observers. It allows setting a replay parameter specifying how many past values should be emitted to new observers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/970/1*md4ZT6a6BHr4EnHoJ-wuCQ.png" /></figure><p>StateFlow behaves a lot like SharedFlow with reply = 1, but StateFlow must always have a value, so an initial value must be specified when a StateFlow is created. This value can be accessed or changed using value property.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5whLg8qrwSWpp67xB1z1pA.png" /></figure><p>StateFlow was designed for a concrete use case: to represent an observable state. In Android, it is used to represent the state of our application, and views observe it and redraw whenever it changes. That is the key source of differences.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/733/1*ZvlW3OkMXKKPmWjJk1lwSQ.png" /></figure><p>Redrawing view can be expensive, and state changes can be frequent. That is why two optimizations were introduced. First, updates are only emitted when they are different from the previous state. This behavior can be achieved on SharedFlow using distinctUntilChanged.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/682/1*YCfP8NzSm33QL5pQxBzD2w.png" /></figure><p>Second, StateFlow is conflated, meaning if value changes faster than observer than handle it, it might lose some intermediate updates. That is appropriate for StateFlow, because we are not interested in drawing obsolete state.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/656/1*tIcK56zbuWqjZhYcZ4tjbA.png" /></figure><p>StateFlow also have some tools for state update, like update function, that lets us safely update state, by creating a new one based on the current one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*t0_dQ5BwfGukbC-F5RHpfQ.png" /></figure><p>That is why StateFlow should be used concretely to represent the observable state of our application. It should not be used as a “breadcast channel”. For that we use SharedFlow.</p><p>Let’s see an Android example: Things like a progress bar or data to display should be represented by StateFlow, but exceptions or messages to show to users as toasts should be represented as SharedFlow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/559/1*lS8vWsYqXOJ8bQeSjVbSLQ.png" /></figure><p>However, SharedFlow values are not completely safe to be used in view models, because updates made when there is no observer will not be observed (unless we use reply, but then the same values can be displayed again). So it is best to represent our state with StateFlow.</p><p>Now let’s see a backend example. On a backend service, updates received from some websocket should be represented using SharedFlow, but an observable counter of application users can be represented using StateFlow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S9oPVC8MBQ-ka2EMteY1Kg.png" /></figure><p>To sum up, StateFlow and SharedFlow are similar, but they are designed for different use cases. StateFlow is for representing observable state, and SharedFlow is for broadcasting events. Use them accordingly.</p><p>The content of this post, with executable code snippets, can be found here:</p><p><a href="https://kt.academy/article/sharedflow_vs_stateflow">https://kt.academy/article/sharedflow_vs_stateflow</a></p><h3>Placeholder image in a lazy grid</h3><p>One painful mistake made when using lazy lists or grids is having items with a very small size, like images before they get loaded 😨 To prevent that we should set size or use a placeholder.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*g0BG4n9mBAbLpNYvU2bN8g.gif" /></figure><h3>Article: From Python to Kotlin: A transition worth making</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eC6Ji0Kr-HrhbWhq1bYf5g.png" /></figure><p>If Python is your “get things done” language, what starts breaking once the codebase grows to years, not weeks?</p><p>This article compares Python vs Kotlin through real code (scripts → CRUD → backend), and shows why teams are increasingly treating Kotlin as a pragmatic “Python, but production-scalable” option.</p><p>Originally published on the JetBrains blog.</p><p>Read: <a href="https://kt.academy/article/python-to-kotlin">https://kt.academy/article/python-to-kotlin</a></p><h3>Project: VizCor</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*O87_RcAvZF1xvpvBhwzpfA.gif" /></figure><p>VizCor is an amazing project by Jiri Hermann. I believe it is the most ambitious Coroutines Mastery 2025 project, showing well-implemented backend in Ktor (supporting Swagger, AsyncAPI, OpenAPI, Micrometer and more) for visualizing how coroutines work in complex scenarios that can be setup with applications builder.</p><p>Backend code 👉 <a href="https://github.com/hermanngeorge15/vizcor">https://github.com/hermanngeorge15/vizcor</a></p><p>Marcin Moskała<br>kt.academy</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=25d0d5343764" width="1" height="1" alt=""><hr><p><a href="https://blog.kotlin-academy.com/tools-for-kotlin-developers-and-how-to-use-them-well-25d0d5343764">Tools for Kotlin developers, and how to use them well</a> was originally published in <a href="https://blog.kotlin-academy.com">kt.academy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>