<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Eevis</title>
    <description>The latest articles on Forem by Eevis (@eevajonnapanula).</description>
    <link>https://forem.com/eevajonnapanula</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F201004%2F0196665f-a91d-4692-a6d6-12e6d68737e2.png</url>
      <title>Forem: Eevis</title>
      <link>https://forem.com/eevajonnapanula</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/eevajonnapanula"/>
    <language>en</language>
    <item>
      <title>About Invisibility, Propaganda, and Assumptions of Incompetence</title>
      <dc:creator>Eevis</dc:creator>
      <pubDate>Sat, 07 Mar 2026 09:17:39 +0000</pubDate>
      <link>https://forem.com/eevajonnapanula/about-invisibility-propaganda-and-assumptions-of-incompetence-4ce0</link>
      <guid>https://forem.com/eevajonnapanula/about-invisibility-propaganda-and-assumptions-of-incompetence-4ce0</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/wecoded-2026"&gt;2026 WeCoded Challenge&lt;/a&gt;: Echoes of Experience&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It’s the time of the year when I feel most conflicted. Tomorrow’s International Women’s Day, and as I’ve witnessed in the past years, it’s usually the time when men start asking about when International Men’s Day is, and telling how equality has gone too far. Of course, not all men, but usually a man. &lt;/p&gt;

&lt;p&gt;In Finland, we also have Minna Canth’s day and the day of equality on the 19th of March, and in the days between these dates, all kinds of weird anti-equality stuff and trolls pop up. As someone who cares deeply about equality and human rights, this time is a bit stressful. &lt;/p&gt;

&lt;p&gt;By the way, Minna Canth was a Finnish writer, businesswoman, and social influencer, best known for her work on women’s rights. If you’re interested in knowing more about her, here’s a link to Wikipedia: &lt;a href="https://en.wikipedia.org/wiki/Minna_Canth" rel="noopener noreferrer"&gt;Minna Canth&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Anyway, I also enjoy this time of year a lot. Especially Dev’s We Coded has a special place in my heart - reading posts from other people who are from underrepresented genders in tech, and seeing how the community comes together to defend those who get some nasty or uncalled-for comments to their posts. &lt;/p&gt;

&lt;p&gt;This is my sixth time participating in We Coded, and over the years, I’ve shared my experiences as a nonbinary woman in tech. I’ve also shared some tips for allies, but last year, I decided to just share my experiences because so many people keep telling me there are no problems and that tech is equal for all, so I’m using my efforts to show otherwise. And I’m going to continue it this year as well, with a couple of tips included.&lt;/p&gt;

&lt;p&gt;Naturally, these examples are not everything I’ve witnessed and experienced since March 2025, but some selected examples. Let’s start with some invisibleness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Non-Binary Woman, an Invisible Creature
&lt;/h2&gt;

&lt;p&gt;This has been the first year I’ve fully embraced myself as a non-binary person. I’m still figuring out the details, so I’ve been introducing myself as a non-binary woman because I still feel a connection to womanhood. I’ve lived as a woman for most of my life, but at the same time, deep down, I’ve known that I’m not a woman. Or, just a woman. &lt;/p&gt;

&lt;p&gt;Anyway, one of the things I’ve started doing is referring to myself with “they” pronoun. Generally, I go by they/she, because I’ve decided not to use too much energy correcting people, so “she” is okay for me for now. But when I refer to myself, like in a bio or something that requires third person, I use “they”. &lt;/p&gt;

&lt;p&gt;I was filling out a CV for a freelancer agency last spring. The person from the agency asked me to fill out the CV details, and they would edit it to make it more selling. I was ok with that. I’m not the best at coming up with hype words to sell my experience.&lt;/p&gt;

&lt;p&gt;I wrote my bio using “they”. A bit later, I wanted to check something in the edited CV, so I opened the file and noticed the bio had been updated. Yes, there were some sentences that were better to sell my experience. But they had also changed the “they” pronouns to “she”. &lt;/p&gt;

&lt;p&gt;I felt so freaking invisible. &lt;/p&gt;

&lt;p&gt;I mean, it’s one thing when someone makes assumptions about me. I can understand that. But editing my own words, that hurts. And yes, they probably didn’t mean it. Heck, they probably didn’t even notice what they were doing. &lt;/p&gt;

&lt;p&gt;So, a tip: Respect the pronouns someone asks you to use, and especially the pronouns they use of themselves. If someone writes their bio with some pronouns, they know what they’re doing. There is a reason for that. &lt;/p&gt;

&lt;h2&gt;
  
  
  What if Your Existence is Propaganda
&lt;/h2&gt;

&lt;p&gt;Last year, I gave many talks about creative coding with Kotlin. The talks contained some live coding, and here’s a video of what I ended up building on stage:&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/6TCr5ABF-x8"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Isn’t the ghost cute? If you’re interested in hearing the whole story, I’ve given this talk, for example, in Droidcon London (it was two weeks after the talk I’m sharing about): &lt;a href="https://www.youtube.com/watch?v=stycBw0aa10" rel="noopener noreferrer"&gt;Creative Coding in Kotlin - talk&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Anyway, in the talk, the main idea behind the animation I created was that people are generally happier when they can be themselves. And that it’s partly based on my own story. I shared these things from the stage, as well. &lt;/p&gt;

&lt;p&gt;At least two people who sat through it all and decided to give feedback via an anonymous channel, to which there was a QR code at the very end of the talk. So, I repeat, they sat through the entire talk, listening, and then gave some feedback. One was just numeric, but the other also gave some written feedback:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ublefbbxf7c3dop9z2p.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ublefbbxf7c3dop9z2p.jpeg" alt="Title: Comments and suggestions. Text: LGBT and non-binary propaganda. Didnt find out anything about Creative Coding."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I saw this some time after the talk, still at the venue. I’m so glad I had other speakers around me who called the organizer in. They took it seriously as a Code of Conduct violation. Of course, this was an anonymous channel, so it wasn’t possible to reach out to the individual, but they addressed it in public. &lt;/p&gt;

&lt;p&gt;Oh, by the way, the London talk went well. I gave a content warning (shouldn’t have to), and one person left the room, so no one got traumatized from my non-binary propaganda (/s). &lt;/p&gt;

&lt;p&gt;Another tip: Anonymous feedback might sound like a great way to gather feedback for your speakers, but it really needs some moderation. After this incident, I would rather not take it, even if I risk missing some valuable feedback. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Bad Ol’ Arguments
&lt;/h2&gt;

&lt;p&gt;The year wouldn’t be complete without some ancient arguments. You know, those that the 1990´s want back. Like “I’ve never seen anyone discriminated”, coming from someone in a very privileged position. &lt;/p&gt;

&lt;p&gt;To my surprise, my last year’s We Coded post didn't get many negative comments. This time, no one told me that the sole reason for my being in tech is that I’m just looking for a romantic partner, or that I’d get a promotion only if someone had “other motives” towards me. Or that I was showing my sexuality down everyone’s throats (when I was telling about my experiences, not touching the topic of sexuality, not one time). &lt;/p&gt;

&lt;p&gt;However, there was one conversation in the comments where the commenter asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should we lower our standards in the name of equality? Hire incompetent people because we need to check boxes?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And I just pointed out that it’s interesting that there’s an assumption of other genders being incompetent. Of course, there was another commenter telling me that no, that was not the case, and explaining to me “many employers” have hired less competent people in the name of equality, and when I asked if it was the person’s gut feeling, or if there was actual evidence of this happening, they never responded. So, I’m guessing it was the gut feeling. &lt;/p&gt;

&lt;p&gt;I’m just so tired of this argument. Yes, before, even mediocre men got hired, but now that hiring standards are changing and competent people from other genders are being hired, it might feel ok to say they’re not competent because they threaten your place. And it might feel intimidating if you’re in the bracket that won’t get hired because there are other people who are more competent than you, who would not have been hired before because of the biases that exist. It might feel unfair, even. But you know, isn’t the idea that the most competent people get hired, regardless of their gender?&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Okay, time to wrap up this year’s post before it turns into a novel. These were some experiences from a year as a non-binary woman in tech, who is sick and tired of the inequality we still face. The current world situation doesn’t help, and we need to be even more visible now that there’s a joint effort by people with far-right views, anti-gender movement, and those who use Christianity as an excuse, who are trying to erase us. &lt;/p&gt;

&lt;p&gt;Of course, at the same time, it’s not always safe to be visible. And it’s important to take care of yourself and your loved ones. So, if you can, shine your light and be visible. I’m trying to do it for myself, and for those, who can’t. &lt;/p&gt;

&lt;p&gt;If you’re someone from a gender minority in tech: I see you, and I celebrate you. You are enough, and you are skilled. When you’re doubting yourself, remember: You’ve come this far, and you’ve got this. And that it’s ok to rest as well, you don’t have to always fight. &lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>wecoded</category>
      <category>dei</category>
      <category>career</category>
    </item>
    <item>
      <title>Adding Navigation support to Large Content Viewer with Compose</title>
      <dc:creator>Eevis</dc:creator>
      <pubDate>Sat, 28 Feb 2026 12:36:00 +0000</pubDate>
      <link>https://forem.com/eevajonnapanula/adding-navigation-support-to-large-content-viewer-with-compose-2m33</link>
      <guid>https://forem.com/eevajonnapanula/adding-navigation-support-to-large-content-viewer-with-compose-2m33</guid>
      <description>&lt;p&gt;In my previous blog post, &lt;a href="https://dev.to/eevajonnapanula/beyond-font-scaling-large-content-viewer-with-compose-25c1"&gt;Beyond Font Scaling: Large Content Viewer with Compose&lt;/a&gt;, I explained how to build a large content viewer from iOS using Jetpack Compose. The implementation did not include support for keyboard or other assistive tech navigation, so this blog post tackles those topics.&lt;/p&gt;

&lt;p&gt;A disclaimer: The way I'm presenting the support for assistive tech navigation in this blog post is one approach, and my goal is to provide examples and context, but as always, this is a demo project. In your production app, things might get a bit more complicated, and you might need to handle more variables. So always remember to test the solution, ideally with your users who use assistive technologies. And I hope it goes without saying: Before releasing to production. &lt;/p&gt;

&lt;p&gt;In this blog post, I’m presenting code for keyboard and screen reader navigation, then sharing some considerations for voice access support. The code relies heavily on the code presented in the previous blog post, so if you have questions about it, please check that post. A link for the full code is also provided at the end of the blog post. &lt;/p&gt;

&lt;h2&gt;
  
  
  Keyboard Navigation Support
&lt;/h2&gt;

&lt;p&gt;The pointer input implementation presented in the previous blog post relies on long-press, but keyboard navigation doesn’t support that kind of interaction, so we need another tactic to display the item preview. With a keyboard, focusing on an item is a natural choice, so we’re going to use that. &lt;/p&gt;

&lt;p&gt;The idea is that when a user who navigates with a keyboard or a keyboard-emulating device focuses on a bottom bar item for the same duration as a long press, we will show the item preview. We can do this with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="nc"&gt;NavigationBarItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onFocusChanged&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isFocused&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;viewConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longPressTimeoutMillis&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;previewedItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;previewedItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the &lt;code&gt;onFocusChanged&lt;/code&gt;-modifier. Its state indicates whether the current element is focused via &lt;code&gt;it.isFocused&lt;/code&gt;. If it is, we launch the coroutine scope, delay for the same amount of time as with long-press interaction, and then set &lt;code&gt;previewedItem&lt;/code&gt; to the currently focused item. If the element is not focused, meaning the focus leaves the navigation bar item, we set the &lt;code&gt;previewedItem&lt;/code&gt; to &lt;code&gt;null&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Now, when the user navigates with a keyboard and stays focused for the long-press time, we show the item preview:&lt;/p&gt;


    


&lt;p&gt;Alright, we now have keyboard navigation support. Let’s talk about screen reader navigation next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screen Reader Navigation Support
&lt;/h2&gt;

&lt;p&gt;You might ask, “Why are we implementing something like this for screen reader users, as they can’t see?” The thing is, not every screen reader user is blind, and even blindness is a spectrum. So, some screen reader users might still benefit from seeing the icon preview.&lt;/p&gt;

&lt;p&gt;Okay, back to the code. For screen reader navigation, we’re going to go with custom accessibility actions. The code could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="nc"&gt;NavigationBarItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
        &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;semantics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;customActions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;CustomAccessibilityAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Preview item"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;previewedItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="k"&gt;true&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add a custom accessibility action via &lt;code&gt;semantics&lt;/code&gt; and set its label to “Preview item”. In the action block, we set the previewed item as the current item. Then we return true from the action to indicate that it was successfully handled.&lt;/p&gt;

&lt;p&gt;You might ask, why don’t we use the long-press duration here? Well, if the user has already gone through all the trouble to trigger the accessibility action, they know what they’re doing and want to see the preview, so no point in making them wait. In touch and keyboard navigation, we don’t show it right away, as it might not be the desired behavior. &lt;/p&gt;

&lt;p&gt;After these changes, the navigation could look like the following. The video doesn’t have sound, but you can see the TalkBack input at the bottom of the screen. &lt;/p&gt;


    


&lt;h2&gt;
  
  
  Considerations for Voice Access
&lt;/h2&gt;

&lt;p&gt;One assistive technology available to users is Voice Access, which allows users to navigate with voice commands. For users who use it with the item preview, we actually don’t need anything new - everything’s already in place. &lt;/p&gt;

&lt;p&gt;One of the commands available for Voice Access is to long-press an interactive element, and as this implementation already supports it (check the previous blog post), we don’t need to do anything to support it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this blog post, we’ve continued with the icon preview, making it more accessible for assistive technology users. We first looked into improving keyboard navigation, then screen reader access, and finally Voice Access.&lt;/p&gt;

&lt;p&gt;There’s one consideration for the overall discoverability of the item previewer: it isn't a common pattern on Android, so you might want to consider how to let your users know about this cool new feature. &lt;/p&gt;

&lt;p&gt;The complete code is available as a &lt;a href="https://gist.github.com/eevajonnapanula/a6f26988aa81253c8917879347887202" rel="noopener noreferrer"&gt;Github Gist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links in the Blog Post
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/eevajonnapanula/beyond-font-scaling-large-content-viewer-with-compose-25c1"&gt;Beyond Font Scaling: Large Content Viewer with Compose&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://gist.github.com/eevajonnapanula/a6f26988aa81253c8917879347887202" rel="noopener noreferrer"&gt;Github Gist&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>a11y</category>
      <category>android</category>
      <category>mobile</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Beyond Font Scaling: Large Content Viewer with Compose</title>
      <dc:creator>Eevis</dc:creator>
      <pubDate>Sat, 17 Jan 2026 11:34:00 +0000</pubDate>
      <link>https://forem.com/eevajonnapanula/beyond-font-scaling-large-content-viewer-with-compose-25c1</link>
      <guid>https://forem.com/eevajonnapanula/beyond-font-scaling-large-content-viewer-with-compose-25c1</guid>
      <description>&lt;p&gt;If you’ve worked with accessibility issues and have a bottom bar with multiple items, you’ve probably come across a problem with larger font sizes, where it’s impossible to scale the bottom navigation bar texts properly with larger font sizes. You might have wondered whether there are any alternative ways to support larger font sizes beyond just, well, font scaling. &lt;/p&gt;

&lt;p&gt;That’s what happened to me. There was a bottom bar with five items with long texts. The developer who implemented the bottom bar had restricted font scaling because larger font sizes made the bottom bar’s items essentially unreadable. I started investigating options and came across one solution: a large content viewer from iOS. As Compose doesn’t support this out of the box, I had to build it. &lt;/p&gt;

&lt;p&gt;This blog post explains what a Large Content Viewer is and how to build similar functionality with Compose. I call it the item previewer in this blog post. But before diving into that, let’s discuss the large content viewer on iOS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Large Content Viewer on iOS
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2019/261/" rel="noopener noreferrer"&gt;Large content viewer&lt;/a&gt; is an accessibility tool for iOS that helps, for example, low-vision users display non-scaling elements, such as bottom-bar items, as previews with a larger font size. It’s enabled only when the Large text (an accessibility setting) is enabled. &lt;/p&gt;

&lt;p&gt;Here’s an example of what it looks like on the Files app: &lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/mpqufjsy02zr/25Fnsp4TplrDCI62hdKCxQ/04b170fa088feed2faccb2b399e9569e/Screenshot_2026-01-11_at_7.42.58.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/mpqufjsy02zr/25Fnsp4TplrDCI62hdKCxQ/04b170fa088feed2faccb2b399e9569e/Screenshot_2026-01-11_at_7.42.58.png" alt="iOS's Files app, Shared tab open, with Shared menu item displayed on top of the screen as a card with the folder icon and text ‘Shared’."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility Considerations
&lt;/h2&gt;

&lt;p&gt;There are accessibility considerations for this solution. First of all, yes, it can be WCAG-compliant. WCAG, which stands for Web Content Accessibility Guidelines, is the standard used, for example, in legislation. It applies to mobile apps as well. It states in success criteria 1.1.4 Resize text that &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Except for captions and images of text, text can be resized without assistive technology up to 200 percent without loss of content or functionality.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the mechanism is not the default font size setting, then another supported mechanism would suffice to pass the success criteria. With this solution, users can resize text without assistive technology up to 200 percent.&lt;/p&gt;

&lt;p&gt;However, this solution isn't very discoverable because it’s not a standard way to display fixed-size text. Also, the solution I’m presenting in this blog post is only for pointer-based interaction, as it relies on the long-press action. I will later write a blog post on how to support this for different assistive technologies.&lt;/p&gt;

&lt;p&gt;Now that these considerations have been discussed, let’s talk about the actual implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Navigation Bar Item Preview
&lt;/h2&gt;

&lt;p&gt;I’ve built a small example to demonstrate how to build the item previewer for Compose. It consists of a &lt;code&gt;Scaffold&lt;/code&gt; with a bottom bar, and fixed font size for the bottom bar’s text labels. You can find &lt;a href="https://gist.github.com/eevajonnapanula/94619c0862686af5b26c19a3bf0fa9fd" rel="noopener noreferrer"&gt;the full code on GitHub Gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The example looks like this with font size set to 200%:&lt;/p&gt;


    


&lt;p&gt;When the user long-presses a navigation item, it is displayed at the top of the screen. Once they release the long press, the tab changes. Also, if they just tap the item, nothing is shown.&lt;/p&gt;

&lt;p&gt;There are three things we need to implement to match the large content viewer behavior:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Detect when the user long-presses instead of tapping.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Show the preview of the selected item.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable the preview only when the font size is bigger than the default.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s start from the top. &lt;/p&gt;

&lt;h3&gt;
  
  
  Detect the Long Press
&lt;/h3&gt;

&lt;p&gt;To detect a long press, we’re going to use &lt;code&gt;InteractionSource&lt;/code&gt;. There are other options out there, but they don’t work in our case. As we’re using Material 3’s &lt;code&gt;NavigationBarItem&lt;/code&gt;, it doesn’t allow long clicks by default, and using &lt;code&gt;combinedClickable&lt;/code&gt; modifier doesn’t work, and neither does the &lt;code&gt;pointerInput&lt;/code&gt; with &lt;code&gt;detectLongPress&lt;/code&gt; because of the order of modifiers in the internal &lt;code&gt;NavigationBarItem&lt;/code&gt; component. With that, I mean that the modifier chain inside the &lt;code&gt;NavigationBarItem&lt;/code&gt; first includes the user-defined modifiers, and after them, the &lt;code&gt;selectable&lt;/code&gt; modifier, which overrides the behavior of those aforementioned modifiers. &lt;/p&gt;

&lt;p&gt;One thing to note is that the previewed item is stored as a state variable. &lt;code&gt;NavItem&lt;/code&gt; is a custom-defined data class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;previewedItem&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NavItem&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back to &lt;code&gt;InteractionSource&lt;/code&gt;. Luckily, we can use it with &lt;code&gt;NavigationBarItem&lt;/code&gt;, and utilize its methods to detect long presses. Let’s define an interaction source for each navbar item inside the map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;interactionSource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;MutableInteractionSource&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to know the duration of a long press. We can get it from &lt;code&gt;LocalViewConfiguration&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;viewConfiguration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalViewConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;

&lt;span class="c1"&gt;// Long press duration&lt;/span&gt;

&lt;span class="n"&gt;viewConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longPressTimeoutMillis&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to collect the interactions, specifically those of type &lt;code&gt;PressInteraction&lt;/code&gt;. We do it inside &lt;code&gt;LaunchedEffect&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="nc"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interactionSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;interactionSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;interactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectLatest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;interaction&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;PressInteraction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Press&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longPressTimeoutMillis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;previewedItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;PressInteraction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Cancel&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;previewedItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;PressInteraction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Release&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;selectedItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
                    &lt;span class="n"&gt;previewedItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, we listen to changes in &lt;code&gt;interactionSource&lt;/code&gt;, and collect the interactions. When the interaction is a press-interaction, we first delay the amount of &lt;code&gt;viewConfiguration.longPressTimeoutMillis&lt;/code&gt; to only set the previewed item when the user has held the press for a long enough time for it to count as a long press. &lt;/p&gt;

&lt;p&gt;If the interaction is cancelled, for example, if the user moves their pointer input out of the bottom bar item without actually releasing it (and it would otherwise count as a long press or click), we set the &lt;code&gt;previewedItem&lt;/code&gt; to null. And if the user releases the press (so, doesn’t cancel it but either lifts their finger or otherwise releases the pointer input), then it counts as either a press or a long press. We set the selected item to the current item, and &lt;code&gt;previewedItem&lt;/code&gt; to null. This way, the click works as it should, and if the interaction is counted as a long click, the tab changes and the preview disappears.&lt;/p&gt;

&lt;h3&gt;
  
  
  Display the Preview
&lt;/h3&gt;

&lt;p&gt;Next, we want to display the item the user is previewing. We first define the preview component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;PreviewItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NavItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modifier&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;RoundedCornerShape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NavigationBarDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;containerColor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;horizontalAlignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CenterHorizontally&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;verticalArrangement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Arrangement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Center&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;painter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;painterResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selectedIcon&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It displays the selected item’s icon and the text without preventing text scaling. &lt;/p&gt;

&lt;p&gt;Then, in the parent component, but outside of the bottom bar code, we display the &lt;code&gt;PreviewItem&lt;/code&gt; component, if the &lt;code&gt;previewItem&lt;/code&gt; is not null:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="n"&gt;previewedItem&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;PreviewItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;align&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;       
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Box&lt;/code&gt; is a wrapper that centers the preview the same way the large content viewer does with its preview item. We pass in a &lt;code&gt;modifier&lt;/code&gt; to align the &lt;code&gt;PreviewItem&lt;/code&gt; to the center of the &lt;code&gt;Box&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enabling the Preview
&lt;/h3&gt;

&lt;p&gt;The final step is to enable the item preview only when the font size is bigger than the fixed text size. One way is to utilize the font scale and enable the preview when the font size is bigger than 100%:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fontScale&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalDensity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fontScale&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;previewEnabled&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fontScale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fontScale&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, wrap the item preview with this boolean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previewEnabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;previewedItem&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s how we’ve created behaviour similar to iOS’s large content viewer with Jetpack Compose. You can find  &lt;a href="https://gist.github.com/eevajonnapanula/94619c0862686af5b26c19a3bf0fa9fd" rel="noopener noreferrer"&gt;The full code on GitHub Gist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links in the Blog Post
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2019/261/" rel="noopener noreferrer"&gt;Large content viewer&lt;/a&gt; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://gist.github.com/eevajonnapanula/94619c0862686af5b26c19a3bf0fa9fd" rel="noopener noreferrer"&gt;The full code on GitHub Gist&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>a11y</category>
      <category>programming</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Year in Review - 2025 Edition</title>
      <dc:creator>Eevis</dc:creator>
      <pubDate>Wed, 31 Dec 2025 11:34:00 +0000</pubDate>
      <link>https://forem.com/eevajonnapanula/year-in-review-2025-edition-4g7k</link>
      <guid>https://forem.com/eevajonnapanula/year-in-review-2025-edition-4g7k</guid>
      <description>&lt;p&gt;It’s that time of the year again! Looking back, I’m so glad I started writing these yearly reflections back in 2021. Now that this is the fifth yearly reflection, it’s great to read those previous ones, and see what has changed - and what has not. &lt;/p&gt;

&lt;p&gt;If you want to read about my previous yearly reflections, here they are: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eevis.codes/blog/2022-01-01/year-in-review-2021-edition/" rel="noopener noreferrer"&gt;Year in Review - 2021 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/eevajonnapanula/year-in-review-2022-edition-2fko"&gt;Year in Review - 2022 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/eevajonnapanula/year-in-review-2023-edition-581d"&gt;Year in Review - 2023 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/eevajonnapanula/year-in-review-2024-edition-1ij4"&gt;Year in Review - 2024 Edition&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s see what 2025 was about. &lt;/p&gt;

&lt;h2&gt;
  
  
  Accomplishments
&lt;/h2&gt;

&lt;p&gt;Looking back at 2025, the biggest accomplishment I want to share is that, in the spring, after being on burnout sickness leave for a month, I gave my two weeks' notice at my old job. At that point, I had no plan; I just knew I needed to get out. After that, things came together quickly, and two weeks after my last day at Oura, &lt;strong&gt;I started my own company, helping with mobile app accessibility&lt;/strong&gt;. If your organization needs workshops, coaching, coding, or other mobile app accessibility-related services, send me a message and let’s talk!&lt;/p&gt;

&lt;p&gt;Another thing I’m super proud and happy about is that in December, &lt;strong&gt;I sent the first issue of Inclusive Android Apps -newsletter&lt;/strong&gt;. I’ve been contemplating starting a newsletter for a while, and in November, I finally decided to give it a go. I already have some upcoming issues scheduled, and I can’t wait to send them out! You can subscribe to it behind the link: &lt;a href="https://eevis.codes/newsletter/" rel="noopener noreferrer"&gt;Inclusive Android Apps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On the coding front, I’ve learned a ton this year. One big theme has been Wear OS-apps, and I &lt;strong&gt;published my first Wear OS-application in August&lt;/strong&gt;.  It’s a simple stitch counter for knitting, and building it taught me a lot. I wish I had some time to continue developing it at some point, but so far my fall has been busier than I expected.&lt;/p&gt;

&lt;p&gt;The final accomplishment I want to share is that I was asked to serve as &lt;strong&gt;a judge for Grand One&lt;/strong&gt;, a Finnish award for digital agencies and marketing. The category I’m judging for is the most accessible digital service, and it was an honor to be asked. &lt;/p&gt;

&lt;h2&gt;
  
  
  Speaking
&lt;/h2&gt;

&lt;p&gt;In 2025, I gave talks at both meetups and conferences and participated in a couple of roundtables. Here’s the full list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Conference talks

&lt;ul&gt;
&lt;li&gt;Future Frontend&lt;/li&gt;
&lt;li&gt;Droidcon Berlin&lt;/li&gt;
&lt;li&gt;DevFest Czech &lt;/li&gt;
&lt;li&gt;Droidcon London&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Roundtables 

&lt;ul&gt;
&lt;li&gt;GDE Summit&lt;/li&gt;
&lt;li&gt;Droidcon Berlin&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Meetups

&lt;ul&gt;
&lt;li&gt;GDG Helsinki: Mobile Tech Spotlight&lt;/li&gt;
&lt;li&gt;Aurajoki Overflow&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;In addition, I’ve given a couple of internal talks at the companies I’ve worked at this year. This year's topics included accessibility, Android, and creative coding with Kotlin.&lt;/p&gt;

&lt;p&gt;From a numbers point of view, my 2025 speaker year was pretty close to 2024, just with roundtables in addition to talks. But what’s different is that last year at this time, I had one event confirmed in my calendar for the next year - right now, I already have three. &lt;/p&gt;

&lt;h2&gt;
  
  
  Writing
&lt;/h2&gt;

&lt;p&gt;On the writing front, I was not as productive as in 2024. I wrote a total of 20 posts (this being the 21st). I wrote one guest post in Selko Digital’s blog in Finnish (&lt;a href="https://selkodigital.fi/vieraskyna-verkkokauppojen-mobiilisovellusten-saavutettavuudessa-on-parantamisen-varaa/" rel="noopener noreferrer"&gt;Vieraskynä: Verkkokauppojen mobiilisovellusten saavutettavuudessa on parantamisen varaa&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;My blog posts were shared in multiple newsletters, and, according to analytics, the top three sharers were Android Weekly, JetC, and Accessible Mobile Apps. Thank you all for sharing my posts! &lt;/p&gt;

&lt;p&gt;As always, I’m sharing some of my favorite posts from the past year. &lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/eevajonnapanula/be-mine-and-add-interaction-with-compose-and-canvas-28la" class="crayons-story__hidden-navigation-link"&gt;Be Mine and Add Interaction with Compose and Canvas&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/eevajonnapanula" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F201004%2F0196665f-a91d-4692-a6d6-12e6d68737e2.png" alt="eevajonnapanula profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/eevajonnapanula" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Eevis
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Eevis
                
              
              &lt;div id="story-author-preview-content-2272600" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/eevajonnapanula" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F201004%2F0196665f-a91d-4692-a6d6-12e6d68737e2.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Eevis&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/eevajonnapanula/be-mine-and-add-interaction-with-compose-and-canvas-28la" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Feb 13 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/eevajonnapanula/be-mine-and-add-interaction-with-compose-and-canvas-28la" id="article-link-2272600"&gt;
          Be Mine and Add Interaction with Compose and Canvas
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/android"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;android&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/kotlin"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;kotlin&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/creativecoding"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;creativecoding&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/eevajonnapanula/be-mine-and-add-interaction-with-compose-and-canvas-28la" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;8&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/eevajonnapanula/be-mine-and-add-interaction-with-compose-and-canvas-28la#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;This blog post was so much fun to write! And when I say write, I mean code. In the post, I explain how to add interaction (in this case, touch exploration) for Compose Canvas. The example used is a collection of Valentine’s day hearts with a queer twist. &lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/eevajonnapanula/echoes-of-the-past-tech-is-still-not-equal-for-all-2c71" class="crayons-story__hidden-navigation-link"&gt;Echoes of the Past - Tech is Still Not Equal for All&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/eevajonnapanula" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F201004%2F0196665f-a91d-4692-a6d6-12e6d68737e2.png" alt="eevajonnapanula profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/eevajonnapanula" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Eevis
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Eevis
                
              
              &lt;div id="story-author-preview-content-2292310" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/eevajonnapanula" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F201004%2F0196665f-a91d-4692-a6d6-12e6d68737e2.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Eevis&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/eevajonnapanula/echoes-of-the-past-tech-is-still-not-equal-for-all-2c71" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 7 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/eevajonnapanula/echoes-of-the-past-tech-is-still-not-equal-for-all-2c71" id="article-link-2292310"&gt;
          Echoes of the Past - Tech is Still Not Equal for All
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/wecoded"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;wecoded&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dei"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dei&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/career"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;career&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/eevajonnapanula/echoes-of-the-past-tech-is-still-not-equal-for-all-2c71" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;42&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/eevajonnapanula/echoes-of-the-past-tech-is-still-not-equal-for-all-2c71#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              11&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;This blog post is part of Dev’s WeCoded-challenge, which started as a #SheCoded-campaing, turned into #WeCoded, and is now a writing challenge, happening around International Women’s Day. In the post, I share some comments and situations I’ve faced this year, mostly related to talking about diversity and inclusion. &lt;/p&gt;

&lt;p&gt;It’s a hard post to write, but the feedback has proven that it’s an important one. So, I will continue writing these posts every year. &lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/eevajonnapanula/wear-os-accessibility-considerations-32i1" class="crayons-story__hidden-navigation-link"&gt;Wear OS Accessibility Considerations&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/eevajonnapanula" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F201004%2F0196665f-a91d-4692-a6d6-12e6d68737e2.png" alt="eevajonnapanula profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/eevajonnapanula" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Eevis
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Eevis
                
              
              &lt;div id="story-author-preview-content-2821847" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/eevajonnapanula" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F201004%2F0196665f-a91d-4692-a6d6-12e6d68737e2.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Eevis&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/eevajonnapanula/wear-os-accessibility-considerations-32i1" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 6 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/eevajonnapanula/wear-os-accessibility-considerations-32i1" id="article-link-2821847"&gt;
          Wear OS Accessibility Considerations
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/android"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;android&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/wearos"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;wearos&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/a11y"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;a11y&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/eevajonnapanula/wear-os-accessibility-considerations-32i1" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;8&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/eevajonnapanula/wear-os-accessibility-considerations-32i1#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              2&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;The final post I’m sharing is about accessibility considerations for Wear OS. I got into Wear OS development this year, and as an accessibility specialist, I naturally dove into the platform's accessibility features as well. &lt;/p&gt;

&lt;h2&gt;
  
  
  Other Content Creation
&lt;/h2&gt;

&lt;p&gt;In the first quarter of 2025, I also started my own &lt;a href="https://www.youtube.com/@eeviscodes" rel="noopener noreferrer"&gt;YouTube channel&lt;/a&gt;. I started publishing these Shorts with silent coding of different things on the Compose Canvas. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Bingo
&lt;/h2&gt;

&lt;p&gt;At the beginning of 2025, Dev had a writing challenge about 2025 predictions, and in my submission, &lt;a href="https://dev.to/eevajonnapanula/2025-please-be-gentle-289e"&gt;2025, Please, Be Gentle&lt;/a&gt;, I created a bingo card or two for this year's predictions. Let’s see how it turned out.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Good Stuff
&lt;/h3&gt;

&lt;p&gt;First, as a picture:&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/mpqufjsy02zr/vlsviKx9DI2RFMLHKjxU4/6646ab08e59fb807758b48341f4ad832/2025_-_The_Good_Stuff_-_Whole_Year.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/mpqufjsy02zr/vlsviKx9DI2RFMLHKjxU4/6646ab08e59fb807758b48341f4ad832/2025_-_The_Good_Stuff_-_Whole_Year.png" alt="2025 - The Good Stuff - Whole Year"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then as a table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
 &lt;th&gt;&lt;b&gt;B&lt;/b&gt;&lt;/th&gt;
 &lt;th&gt;&lt;b&gt;I&lt;/b&gt;&lt;/th&gt;
 &lt;th&gt;&lt;b&gt;N&lt;/b&gt;&lt;/th&gt;
 &lt;th&gt;&lt;b&gt;G&lt;/b&gt;&lt;/th&gt;
 &lt;th&gt;&lt;b&gt;O&lt;/b&gt;&lt;/th&gt;
&lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
&lt;td&gt;Talk accepted to Kotlin Conf&lt;/td&gt;
&lt;td&gt;I get invited to be a guest in a podcast&lt;/td&gt;
&lt;td&gt;My research article gets published&lt;/td&gt;
&lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Learn a new skill&lt;/td&gt;
&lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; More non-binary representation in conferences&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt;Man speaks up against biased behaviour&lt;/td&gt;
 &lt;td&gt;New elections in Finland&lt;/td&gt;
 &lt;td&gt;Night outside, in a hammock&lt;/td&gt;
 &lt;td&gt;Get into a Doctorate program&lt;/td&gt;
 &lt;td&gt;Start mentoring again&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A project is launched without a last-minute crunch&lt;/td&gt;
 &lt;td&gt;More women and non-binary in staff+ positions&lt;/td&gt;
 &lt;td&gt;**Free space (Dream big!)**&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Blog post gets boosted or selected to Dev's top 7&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; #WeCoded without jerks commenting&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Conference swag socks in my size&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Someone says my work inspired them&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; A new tattoo&lt;/td&gt;
 &lt;td&gt;A genuine apology from someone after being a jerk&lt;/td&gt;
 &lt;td&gt;Get a book deal&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Get promoted to Tech Lead position&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; 100K views in Dev&lt;/td&gt;
 &lt;td&gt;Not burning out during the year&lt;/td&gt;
 &lt;td&gt;Build a new, useful feature for Neule.art-app&lt;/td&gt;
 &lt;td&gt;Finish building a keyboard&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Many of these things were things I hoped to accomplish, but I didn’t do much to achieve them. So it kind of makes sense that, with this card, I didn't get a bingo. &lt;/p&gt;

&lt;h3&gt;
  
  
  The More Realistic One
&lt;/h3&gt;

&lt;p&gt;First, as a picture:&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/mpqufjsy02zr/4MKtuRxpLQklltbnNUnqdQ/fbf2f0d7d9d1eae796c52dfaf118cd31/More_Realistic_2025_-_Whole_Year.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/mpqufjsy02zr/4MKtuRxpLQklltbnNUnqdQ/fbf2f0d7d9d1eae796c52dfaf118cd31/More_Realistic_2025_-_Whole_Year.png" alt="More Realistic 2025 - Whole Year"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then as a table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
 &lt;th&gt;&lt;b&gt;B&lt;/b&gt;&lt;/th&gt;
 &lt;th&gt;&lt;b&gt;I&lt;/b&gt;&lt;/th&gt;
 &lt;th&gt;&lt;b&gt;N&lt;/b&gt;&lt;/th&gt;
 &lt;th&gt;&lt;b&gt;G&lt;/b&gt;&lt;/th&gt;
 &lt;th&gt;&lt;b&gt;O&lt;/b&gt;&lt;/th&gt;
&lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Being addressed as a guy&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Someone gaslights me&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Biased AI-generated content&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Man speaks over me&lt;/td&gt;
 &lt;td&gt;Called "intimidating" for being assertive.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Someone interrupts when I'm presenting&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt;"Gender doesn't matter, just talent"&lt;/td&gt;
 &lt;td&gt;Man takes credit for what I said&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Being left out of an important meeting&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; "You'd be heard better, if you weren't so angry"&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Someone assumes I can't handle my tech setup&lt;/td&gt;
 &lt;td&gt;Meeting a new person who assumes I'm not a developer&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; "I've never seen anyone being discriminated"&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; "Intent over impact"&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Man needs to repeat what I've said&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Invalidation of lived experience&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; "Women just don't want to code"&lt;/td&gt;
 &lt;td&gt;Someone else credited for my work&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt;"But they clearly didn't mean it" &lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Sexist comment in a blog post or talk&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;"You were hired just for diversity quotas"&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Everyone's surprised after something I warned happens&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Man splaining my expertise to me&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; "Equality has gone too far"&lt;/td&gt;
 &lt;td&gt;
&lt;b&gt;Happened:&lt;/b&gt; Hypothetical developer is referred to as "he" or other gendered noun&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So, there it is - a realistic bingo. Unfortunately, I was right to predict these things, but hey, let’s hope next year is better in terms of the things I’ve listed in the bingo cards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Things
&lt;/h2&gt;

&lt;p&gt;Last year, I wrote a wish for 2025:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I wish that a year from now, I wouldn't have to think or write about burnout so much. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And this time, my wish came true. It wasn’t the easiest year, but today things are much, much better than they were this time last year. One major contributor was that I resigned from my old job and started pursuing things I’m truly interested in - and a better work culture. &lt;/p&gt;

&lt;h2&gt;
  
  
  What About 2026?
&lt;/h2&gt;

&lt;p&gt;From where I stand, 2026 looks pretty good, and I can’t wait to get there and see what it actually holds. I already have some speaking engagements confirmed for the first half of the year. I also have some cool ideas and projects I’m working on and can’t wait to share them. &lt;/p&gt;

&lt;p&gt;And hey, if your company needs expertise in mobile app accessibility, please do contact me! Let’s discuss how I can help you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links in the Blog Post
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eevis.codes/blog/2022-01-01/year-in-review-2021-edition/" rel="noopener noreferrer"&gt;Year in Review - 2021 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/eevajonnapanula/year-in-review-2022-edition-2fko"&gt;Year in Review - 2022 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/eevajonnapanula/year-in-review-2023-edition-581d"&gt;Year in Review - 2023 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/eevajonnapanula/year-in-review-2024-edition-1ij4"&gt;Year in Review - 2024 Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eevis.codes/newsletter/" rel="noopener noreferrer"&gt;Inclusive Android Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/eevajonnapanula/2025-please-be-gentle-289e"&gt;2025, Please, Be Gentle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>yearinreview</category>
      <category>android</category>
      <category>writing</category>
      <category>speaking</category>
    </item>
    <item>
      <title>The Problem of Rows Breaking with Large Text</title>
      <dc:creator>Eevis</dc:creator>
      <pubDate>Tue, 16 Dec 2025 10:45:00 +0000</pubDate>
      <link>https://forem.com/eevajonnapanula/the-problem-of-rows-breaking-with-large-text-4452</link>
      <guid>https://forem.com/eevajonnapanula/the-problem-of-rows-breaking-with-large-text-4452</guid>
      <description>&lt;p&gt;This was originally sent as Issue #1 of my newsletter. &lt;a href="https://eevis.codes/newsletter/" rel="noopener noreferrer"&gt;Subscribe here&lt;/a&gt; to get future issues in your inbox first.&lt;/p&gt;

&lt;p&gt;I was fixing an Android app and found buttons in a row getting cut off when I increased the font size. After diving into the code, the problem became clear: it was using &lt;code&gt;Row&lt;/code&gt; when &lt;code&gt;FlowRow&lt;/code&gt; was needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem of Rows Breaking with Large Text
&lt;/h2&gt;

&lt;p&gt;Let’s say we have this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxWidth&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;verticalAlignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CenterVertically&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;horizontalArrangement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Arrangement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spacedBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;space&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;alignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CenterHorizontally&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;painter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;painterResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ic_arrow_back&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
      &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Previous year"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Next year"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;painter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;painterResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ic_arrow&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
      &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which, in turn, looks like this with the default font size:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbck7m23kgu6kka8rlp86.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbck7m23kgu6kka8rlp86.png" alt="Two buttons, Previous year and Next year, next to each other. They both have arrow icons, first pointing to the left, and latter to the right." width="558" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, when we turn the font size up to 200%, things start breaking:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febjfw0rznxgscc25fdhc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febjfw0rznxgscc25fdhc.png" alt="Now the Previous year button fits as before, but the Next year button, which is still next to the other button, has much less space, and the text goes on four lines. The arrow is not visible." width="558" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Who This Hurts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;People with low vision who need larger text to read comfortably&lt;/li&gt;
&lt;li&gt;Elderly users (a huge and growing demographic)&lt;/li&gt;
&lt;li&gt;Anyone using large system font sizes—whether for accessibility or personal preference&lt;/li&gt;
&lt;li&gt;Users on smaller screens, where even default text can cause issues&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Developers Do This
&lt;/h3&gt;

&lt;p&gt;Developers often test on their own devices with default settings, and everything looks fine. They don't realize that &lt;code&gt;Row&lt;/code&gt; treats its children as a non-wrapping container, so if the content doesn't fit, it just overflows or gets cut off. There's no automatic wrapping behavior. It's not a bug in &lt;code&gt;Row&lt;/code&gt;. It's just not designed to handle dynamic content that might change size.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;The most straightforward solution would be to use &lt;code&gt;FlowRow&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;FlowRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c1"&gt;// Changed from Row&lt;/span&gt;
  &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxWidth&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;itemVerticalAlignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CenterVertically&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Changed from verticalAlignment&lt;/span&gt;
  &lt;span class="n"&gt;verticalArrangement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Arrangement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spacedBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// NEW: spacing between wrapped rows&lt;/span&gt;
  &lt;span class="n"&gt;horizontalArrangement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Arrangement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spacedBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;space&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;alignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CenterHorizontally&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;painter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;painterResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ic_arrow_back&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
      &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Previous year"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Next year"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;painter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;painterResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ic_arrow&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
      &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which fixes the issues:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqsijw6d2ztgwyeigpub.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqsijw6d2ztgwyeigpub.png" alt="Buttons are now aligned on top of each other, arrows and texts are both visible." width="453" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FlowRow&lt;/code&gt; allows content to wrap on multiple rows if it doesn’t fit the horizontal space. Just remember not to set a fixed or maximum height for the &lt;code&gt;FlowRow&lt;/code&gt; component!&lt;/p&gt;

&lt;p&gt;When you use &lt;code&gt;FlowRow&lt;/code&gt;, there are a couple of things to keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;itemVerticalAlignment&lt;/code&gt; instead of &lt;code&gt;verticalAlignment&lt;/code&gt; (different API)&lt;/li&gt;
&lt;li&gt;Don't set &lt;code&gt;maxHeight&lt;/code&gt; or a fixed height, because if you do so, you'll defeat the wrapping&lt;/li&gt;
&lt;li&gt;If you need certain buttons to stay together, you can nest them in their own Row within the FlowRow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;FlowRow&lt;/code&gt; handles wrapping, but there’s one thing you’ll need to remember: each button still needs enough space to be readable. Test with large text and small screens to ensure everything works as expected for every user.&lt;/p&gt;

&lt;p&gt;Note: &lt;code&gt;FlowRow&lt;/code&gt; was added in Compose 1.4.0. If you're on an older version, consider updating or using a custom wrapping layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read More
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://developer.android.com/develop/ui/compose/layouts/flow" rel="noopener noreferrer"&gt;Flow layouts in Compose&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Android Developers documentation has a whole page dedicated to flow layouts and how to use them. The page explains the features of flow layouts (&lt;code&gt;FlowRow&lt;/code&gt; and &lt;code&gt;FlowColumn&lt;/code&gt;), as well as how items can be arranged on the main and cross axes, aligned, and more. I recommend checking it out to learn more about flow layouts and how to use them.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor" rel="noopener noreferrer"&gt;Accessibility Scanner&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Accessibility Scanner is a great tool for testing the accessibility of your app. It tests for four categories: content labeling, implementation, touch target size, and low contrast. While it does not reveal all possible accessibility problems, it can help with catching many low-hanging fruit.&lt;/p&gt;

&lt;p&gt;Google has also provided a video about using the Accessibility scanner: &lt;a href="https://www.youtube.com/watch?v=i1gMzQv0hWU" rel="noopener noreferrer"&gt;Accessibility scanner - Accessibility on Android by Android Developers&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;This was the first issue of the Inclusive Android Apps newsletter. What topics should I cover in the next issues?&lt;/p&gt;

&lt;p&gt;Want to get future issues of Inclusive Android Apps delivered to your inbox? &lt;a href="https://eevis.codes/newsletter/" rel="noopener noreferrer"&gt;Subscribe here&lt;/a&gt;. Next issue covers building inclusive gender forms.&lt;/p&gt;

</description>
      <category>android</category>
      <category>a11y</category>
      <category>mobile</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why I'm Starting a Newsletter About Inclusive Android Apps</title>
      <dc:creator>Eevis</dc:creator>
      <pubDate>Tue, 02 Dec 2025 16:03:00 +0000</pubDate>
      <link>https://forem.com/eevajonnapanula/why-im-starting-a-newsletter-about-android-inclusion-1078</link>
      <guid>https://forem.com/eevajonnapanula/why-im-starting-a-newsletter-about-android-inclusion-1078</guid>
      <description>&lt;p&gt;One thing I'm super excited about at the end of this year is a newsletter I'm launching: Inclusive Android Apps. It's a monthly newsletter about making Android apps more inclusive, and it covers accessibility, LGBTQ+ inclusion, support for different cultures and languages, privacy and safety for marginalized users, and more.&lt;/p&gt;

&lt;p&gt;In this post, I'm explaining why I'm starting it and what you can expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Inclusive Android Apps?
&lt;/h2&gt;

&lt;p&gt;The short answer is that I'm starting the newsletter because I'd love to read it, and there's nothing like it around. &lt;/p&gt;

&lt;p&gt;The longer answer is that these topics matter to me. Accessibility is a big part of my life, as you might know if you've read my blog posts, seen me speak at events, or followed me on social media. I'm also queer, and many of the issues I'm discussing are from what I've either experienced or witnessed. However, I'm not an expert on everything, and this is also me learning in public.&lt;/p&gt;

&lt;p&gt;And I think that now, more than ever, it's important to raise these issues and talk about them. In this time when marginalized communities are under increasing political, social, and digital attack, building inclusive technology is an act of resistance. And you know, every accessible button, every inclusive form, every thoughtful design choice says: you belong here. This newsletter is about making that concrete.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Expect from Inclusive Android Apps?
&lt;/h2&gt;

&lt;p&gt;Inclusive Android Apps will cover one inclusion problem each month. The structure of each issue is the following:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;The first section explains the problem the newsletter issue covers, giving examples of what happens and describing why it's a problem. Depending on the problem, it might be more about code or about a design pattern. &lt;/p&gt;

&lt;h3&gt;
  
  
  Who the Problem Hurts
&lt;/h3&gt;

&lt;p&gt;Next, each issue has a list of user groups affected by the problem. For example, when buttons break at large text sizes, it affects people with low vision, elderly users, and anyone who has customized their text settings.&lt;/p&gt;

&lt;p&gt;Some of these might be obvious, but not always - when I've been writing these issues and doing research, even I've been surprised by the number of different user groups affected by some of the problems. &lt;/p&gt;

&lt;h3&gt;
  
  
  Why Developers Do It
&lt;/h3&gt;

&lt;p&gt;One of the most interesting parts of writing the issues has been exploring and documenting the reasons why the problem described in the newsletter occurs. Sometimes it's about not knowing specific solutions because there isn't much material out there, and sometimes it's about the biases we humans have. &lt;/p&gt;

&lt;p&gt;I wanted to spell these things out, not to blame anyone, but to illustrate why something happens. The first step to combating biases and knowledge gaps is to recognize them; only then can they be addressed. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Each issue provides actionable solutions: code examples, design patterns, or practical changes you can implement immediately. &lt;/p&gt;

&lt;p&gt;Of course, because of the format (newsletter), it won't cover all possible solutions, just provide one (or a couple) suggestions to solve the problem. But as the newsletter explains the problem in detail, it will also give you some ideas to consider when solving the issue if the provided solution doesn't work for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Learn More
&lt;/h3&gt;

&lt;p&gt;Finally, the newsletter will include links to learn more about the topic or about something recent related to the newsletter's subject. Each link is curated, and I will write an explanation of why I selected it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subscribe to Inclusive Android Apps
&lt;/h2&gt;

&lt;p&gt;If you want to subscribe to the newsletter, here's the link:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://eevis.codes/newsletter/" rel="noopener noreferrer"&gt;Subscribe to Inclusive Android Apps&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first issue will appear in your inbox on December 9th, 2025! It will cover &lt;code&gt;Row&lt;/code&gt;s breaking with larger texts, and a solution for how to fix that problem. The newsletter issues after that will discuss problems with forms that ask for gender and how using color alone to communicate information can be a serious accessibility issue.&lt;/p&gt;

</description>
      <category>android</category>
      <category>a11y</category>
      <category>programming</category>
      <category>inclusion</category>
    </item>
    <item>
      <title>Does AI Generate Accessible Android Apps?</title>
      <dc:creator>Eevis</dc:creator>
      <pubDate>Tue, 25 Nov 2025 11:27:00 +0000</pubDate>
      <link>https://forem.com/eevajonnapanula/does-ai-generate-accessible-android-apps-2gb5</link>
      <guid>https://forem.com/eevajonnapanula/does-ai-generate-accessible-android-apps-2gb5</guid>
      <description>&lt;p&gt;Over the past six months or so, I've been writing a series of blog posts in which I've generated an app using the same prompt with different AI tools, and then tested the outcome with various assistive technologies and accessibility settings. &lt;/p&gt;

&lt;p&gt;The tools I've tested are: Gemini, Junie, Cursor, and Claude. You can find the list of the blog posts above.&lt;/p&gt;

&lt;p&gt;Now it's time to wrap up and write a summary of my learnings. Something worth noting is that I started these tests in spring, which is like decades ago in the current pace of technical advancements. Unfortunately, when it comes to accessibility, not everything moves forward. So, even if the first findings are from the end of spring, they're still relevant for learning purposes. &lt;/p&gt;

&lt;h2&gt;
  
  
  Redundant Content Descriptions
&lt;/h2&gt;

&lt;p&gt;Every tool I tested added redundant content descriptions. And with redundant content descriptions, I mean, for example, buttons that already had a text "Add yarn", and the content description was "Add new yarn", which adds zero new value. And in some cases, the implementation was such that the screen reader read both the text ("Add yarn") and the content description ("Add new yarn"), which added redundant listening for the user.&lt;/p&gt;

&lt;p&gt;Claude took this even further, as it added sometimes actions to the content descriptions, meaning that after the redundant content description, there was a "Tap for details" text appended. The card where this was added already had a role of button set, so it led to the screen reader user getting something like: "Bla bla bla. Tap for details. Tap to activate". And if you're using your screen by listening, you probably want to skip redundant information. &lt;/p&gt;

&lt;p&gt;From a technical point of view, it's understandable why it happens - accessibility documentation and blog posts often focus on screen reader accessibility, and content descriptions are probably the easiest way to add something and assume it makes the UI accessible. And AI has been trained with the documentation (among other things), so it's repeating these patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Non-Scrollable Screen(s)
&lt;/h2&gt;

&lt;p&gt;Another major problem these tests revealed was that, except for Claude, none of the tested tools added scrollability to the screens. The screens didn't contain that much information, so they didn't need scrollability at default text sizes. But the problems started when the font size was bigger. &lt;/p&gt;

&lt;p&gt;If the screen doesn't support scrollability, and the content takes more vertical space than available on the phone screen, the content that goes beyond the visible screen is unusable. &lt;/p&gt;

&lt;p&gt;As developers often test with the default font sizes, not all available apps support scrollability, so, again, from a technical point of view, it's understandable why this happens - the material the tools are trained with contains these issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redundant Focusable Modifier
&lt;/h2&gt;

&lt;p&gt;The first app I created with Gemini had this one unique problem from others - it added &lt;code&gt;focusable&lt;/code&gt; modifier to a component with &lt;code&gt;clickable&lt;/code&gt; modifier. What it means is that when a user who uses e.g. a keyboard or D-pad for navigation encounters this component, they would: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Focus on a button&lt;/li&gt;
&lt;li&gt;Focus disappears&lt;/li&gt;
&lt;li&gt;Focus on the next focusable item&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I've seen this out in the wild - developers adding a &lt;code&gt;focusable&lt;/code&gt; modifier because they think, with good intentions, that it improves the accessibility of the app. So, no wonder it was added. &lt;/p&gt;

&lt;h2&gt;
  
  
  Extra Tab Stops
&lt;/h2&gt;

&lt;p&gt;Junie, on the other hand, created an interesting problem on the second run. When testing with a keyboard, an invisible component was added to the tab order. After some investigation, it turned out to be an invisible floating action button. &lt;/p&gt;

&lt;h2&gt;
  
  
  Incorrect Semantics
&lt;/h2&gt;

&lt;p&gt;Junie and Claude both added some incorrect semantics to the components. On the second test run with Junie, as I asked it to improve accessibility, it added incorrect roles to some components and redundant state descriptions. &lt;/p&gt;

&lt;p&gt;Claude went a bit further - it started hallucinating semantics. For a custom modifier called &lt;code&gt;accessibleTextField&lt;/code&gt;, it added a role of &lt;code&gt;Role.TextField&lt;/code&gt;, which doesn't actually exist. &lt;/p&gt;

&lt;h2&gt;
  
  
  Button Navigation not Supported
&lt;/h2&gt;

&lt;p&gt;Finally, the last issue found on the tests was that Claude did not support button navigation. In practice, it means there isn't enough padding at the bottom of the screen for the button navigation not to hide the last content on the screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/mpqufjsy02zr/51JbkuTErKVSBruOBAetK2/c35689cd931e09fd21b5a77ee180d62e/claude-test-app-navigation-bars.jpg" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/mpqufjsy02zr/51JbkuTErKVSBruOBAetK2/c35689cd931e09fd21b5a77ee180d62e/claude-test-app-navigation-bars.jpg" alt="Bottom of the app screen, showing Quick actions card with Add yarn and Add needles buttons. Semi-transparent navigation bar covers half of the Add needles button."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a button navigation user myself, I see this happening way too often. I suspect most developers use gesture navigation, so they don't test with button navigation, which is why this pattern is widespread. &lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Here you can also find the findings in a table format. &lt;/p&gt;

&lt;p&gt;Legend: In the table below, "YES" means that it's a problem.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;Gemini&lt;/th&gt;
&lt;th&gt;Junie&lt;/th&gt;
&lt;th&gt;Cursor&lt;/th&gt;
&lt;th&gt;Claude&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redundant content descriptions, which override the text&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redundant actions in content descriptions&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;❌ YES &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screen(s) not scrollable&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redundant focusable modifier&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large font sizes not supported&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra tab stops&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incorrect semantics&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Doesn’t support button navigation&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;❌ YES&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;This blog post concludes my journey into testing AI-generated Android apps. The testing results were pretty much what I expected - the apps contained similar issues that I see out there in the wild, with apps that humans have developed. So I don't believe code generation with AI will solve issues with the inaccessibility of Android apps anytime soon. &lt;/p&gt;

&lt;p&gt;You might argue that the issues I've discussed are not that big. But it's worth noting that the app built from the prompt itself isn't that complicated, so a more complex app would probably have resulted in more accessibility issues. &lt;/p&gt;

&lt;p&gt;So, all in all, I'm not convinced just yet. Different AI-tools can be helpful, but they're not yet ready to replace developers, as many non-developers would like to believe.&lt;/p&gt;

</description>
      <category>android</category>
      <category>a11y</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Writing Tests for Larger Font Sizes with Compose: Scrolling and Text Truncation</title>
      <dc:creator>Eevis</dc:creator>
      <pubDate>Fri, 31 Oct 2025 08:14:00 +0000</pubDate>
      <link>https://forem.com/eevajonnapanula/writing-tests-for-larger-font-sizes-with-compose-scrolling-and-text-truncation-1l0</link>
      <guid>https://forem.com/eevajonnapanula/writing-tests-for-larger-font-sizes-with-compose-scrolling-and-text-truncation-1l0</guid>
      <description>&lt;p&gt;While testing Android apps for accessibility, one of the biggest problems I’ve encountered are with larger font sizes. They’re usually not properly supported: content overlaps when font sizes increase, scrolling isn't enabled to accommodate growing text, or text is truncated without a way for the user to expand it. &lt;/p&gt;

&lt;p&gt;I’m often asked tips on writing accessibility tests for Android, and I decided to write this post to demonstrate how to test some of those larger font size issues with UI tests. In this blog post, we’re looking into writing tests to verify that scrolling is enabled when needed and that truncated text can be expanded.&lt;/p&gt;

&lt;p&gt;Let’s first discuss the screen I wrote to test these issues. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Screen We’re Testing
&lt;/h2&gt;

&lt;p&gt;I created an example screen with Compose to demonstrate tests in this blog post. It’s a &lt;code&gt;Scaffold&lt;/code&gt; that wraps a LGBTQ+ Glossary of different gender identities and sexual and romantic orientations. If this were an actual app, there would be a separate screen for each of these categories, listing all the data. On this screen, only the first four are displayed, except for romantic orientation, which shows only the first two. &lt;/p&gt;

&lt;p&gt;This is what the screen looks like with 100% font size (so, the default one):&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/mpqufjsy02zr/2OsX5t1x6gS6jkp3dQHzdw/df8643e2a57fe98af59bd2b5c903a4f6/Screenshot_20251031_070425.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/mpqufjsy02zr/2OsX5t1x6gS6jkp3dQHzdw/df8643e2a57fe98af59bd2b5c903a4f6/Screenshot_20251031_070425.png" alt="A screen with the title LGBTQ+ Glossary, and sections Gender Identity, Sexual Orientation, and Romantic Orientation. Each of them contains cards with text explaining different terms within the category."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that with the default font size, all content fits the screen, and no scrolling is needed. However, when the font size increases and text takes up more space, the content should be scrollable to accommodate that.&lt;/p&gt;

&lt;p&gt;The content of the screen, explaining Non-binary, agender, genderqueer, transgender, asexual, bisexual, queer, lesbian, aromantic, and biromantic terms, is copied from the &lt;a href="https://lgbtqia.fandom.com/wiki/LGBTQIA%2B_Wiki" rel="noopener noreferrer"&gt;LGBTQIA+ Wiki 🏳️‍🌈🏳️‍⚧️&lt;/a&gt;. If you’re interested in learning more, you’ll find much more information on the link. &lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Setup
&lt;/h2&gt;

&lt;p&gt;Before we can start writing the tests, we need to do a bit of setup. &lt;/p&gt;

&lt;p&gt;As the default UI tests are run against the default font size (100%), we need a way to specify the font size as 200% for the tests. There are several ways to do it. In this blog post, we’re going to use something rather straightforward: Composition local. &lt;/p&gt;

&lt;p&gt;In our test class, let’s first get the Compose test rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LargeFontSizeTests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Rule&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;composeTestRule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createComposeRule&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then create a function we can call in both of our tests to set content for testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;setupScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;composeTestRule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;AccessibilityTestsTheme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;LargeFontScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;LocalDensity&lt;/code&gt; is a composition local which provides information about the screen’s density and font scale. Using it to change the tests’ font scale is a good strategy because it keeps the tests isolated and doesn’t require additional dependencies. &lt;/p&gt;

&lt;p&gt;Let’s wrap our screen with &lt;code&gt;CompositionLocalProvider&lt;/code&gt;, providing a new &lt;code&gt;fontScale&lt;/code&gt;value for &lt;code&gt;LocalDensity&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="nc"&gt;CompositionLocalProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;LocalDensity&lt;/span&gt; &lt;span class="n"&gt;provides&lt;/span&gt; &lt;span class="nc"&gt;Density&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;fontScale&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;density&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalDensity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;density&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;LGBTQGlossaryScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have the tests set up for larger font sizes, let’s move on to the actual tests and start by testing if the content is scrolling. &lt;/p&gt;

&lt;h2&gt;
  
  
  Scrolling Content
&lt;/h2&gt;

&lt;p&gt;When a screen doesn’t have much content, it might be built in a way that it can’t be scrolled. Developers often develop with font size set to default (or even smaller), with phones that have big screens, so it might feel natural not to add scrolling to the screen. &lt;/p&gt;

&lt;p&gt;However, when users increase the font size and/or screen size in accessibility settings, the content takes up more vertical space. So, for users, the ability to scroll is often needed to see all the content on the screen. &lt;/p&gt;

&lt;p&gt;To write a UI test for this scenario, we’ll need to do three things on the screen: first, check that the last item is not visible to assert that scrolling is needed; then, swipe up; and finally, check that the last item is visible. In code, it would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;contentIsScrolling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setupScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;composeTestRule&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNodeWithText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Biromantic"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIsNotDisplayed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;composeTestRule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNodeWithTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"screen-content"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIsDisplayed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;performTouchInput&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;swipeUp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;composeTestRule&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNodeWithText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Biromantic"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIsDisplayed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the test, we first call the &lt;code&gt;setupScreen&lt;/code&gt; helper we defined in the previous section, and then assert that the last item (the card with the title “Biromantic”) is not visible. Then, we get the screen content node, check that it’s visible, and perform a swipe-up action. The final block checks that the card with the “Biromantic” title is now displayed. &lt;/p&gt;

&lt;p&gt;The other scenario we’re going to test in this blog post is about text truncation. Let’s discuss it next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Truncated Text
&lt;/h2&gt;

&lt;p&gt;The other problem I see a lot with larger font sizes is that when the text doesn’t fit its container, it's truncated with an ellipsis, and there is no way to see the rest of the text. If this truncation strategy is used, there should always be a way to expand the text. I’ve written a blog post on the topic, so if you’re interested in reading more about the problems text truncation can create, here’s a link: &lt;a href="https://dev.to/eevajonnapanula/the-problem-of-trun-56mm"&gt;The Problem of Trun..&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the example I created for this blog post, card texts are truncated with an ellipsis, and when the user clicks the card, the full text is displayed. So, for example, with genderqueer, the truncated version looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/mpqufjsy02zr/1cTspWklPszt5jCTh0faNN/b60652d12ad8fa1c0f97e7f6184f0cf0/Screenshot_2025-10-28_at_6.47.44.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/mpqufjsy02zr/1cTspWklPszt5jCTh0faNN/b60652d12ad8fa1c0f97e7f6184f0cf0/Screenshot_2025-10-28_at_6.47.44.png" alt="A card with title Genderqueer and truncated text Non-binary generally is used as the..."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and the full version looks like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/mpqufjsy02zr/6lxd7JoQGhxro11NRgT62A/f752fb7755e6350da856de5d859d96d3/Screenshot_2025-10-28_at_6.47.51.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/mpqufjsy02zr/6lxd7JoQGhxro11NRgT62A/f752fb7755e6350da856de5d859d96d3/Screenshot_2025-10-28_at_6.47.51.png" alt="A card with title Genderqueer and text Non-binary generally is used as the catchall term for those who do not identify with the gender binary, whereas genderqueer often refers more to a particular experience under that umbrella, referring to non-normative or queer gender."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Helper Methods
&lt;/h3&gt;

&lt;p&gt;Alright, and then the tests. We’ll want to write a couple of helper methods to make the code more readable. &lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;SemanticsNode.isTruncatedWithEllipsis()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Let’s start with a check for a semantics node for if it is truncated with ellipsis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;SemanticsNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isTruncatedWithEllipsis&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;textLayoutResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableListOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextLayoutResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SemanticsActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetTextLayoutResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;
        &lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;textLayoutResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;textLayoutResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;firstOrNull&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;until&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lineCount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isLineEllipsized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code snippet above, we first define a mutable list of &lt;code&gt;TextLayoutResult&lt;/code&gt;s, and then get the node’s &lt;code&gt;TextLayoutResult&lt;/code&gt; semantics action, which returns a function that’s called for a text node when its text layout is measured. If the node has this action, we call its action-property, so the action that’s called when the function is invoked. And if it exists, we invoke that action on the mutable list we created, and then have the text layout result(s) of the node available for us. &lt;/p&gt;

&lt;p&gt;After that, we map over the first text layout result’s lines (if available), and check if any of these lines are ellipsized. If any of them is, then we know that the text is truncated with ellipses. And if at any point of the check we get &lt;code&gt;null&lt;/code&gt;, we can assume the node is not ellipsized, so the final &lt;code&gt;!= null&lt;/code&gt; check completes the function and returns a boolean.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;isEllipsized()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Another helper function we need is a &lt;code&gt;SemanticsMatcher&lt;/code&gt; for filtering out ellipsized nodes. Let’s define it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isEllipsized&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;SemanticsMatcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SemanticsMatcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"Truncated with Ellipsis"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt;
  &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isTruncatedWithEllipsis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function creates a &lt;code&gt;SemanticsMatcher&lt;/code&gt; which checks if the &lt;code&gt;SemanticsNode&lt;/code&gt; is truncated with ellipsis, so it calls the method we defined in the previous step. &lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;SemanticsNodeInteractionCollection.performClick()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The final helper method we need to create is one to click all nodes in a collection. Compose testing libraries don’t provide this method out of the box, so let’s create it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;SemanticsNodeInteractionCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;performClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchSemanticsNodes&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;forEachIndexed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;performClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We define the extension function for &lt;code&gt;SemanticsNodeInteractionCollection&lt;/code&gt;. This collection doesn’t provide methods for iteration, so we need to get a little creative: We fetch the list of semantic nodes, which is the same size as the collection, and then call &lt;code&gt;forEachIndexed&lt;/code&gt; on it. This way, we get the index and can retrieve the &lt;code&gt;SemanticsNodeInteraction&lt;/code&gt; from that index, then call its &lt;code&gt;performClick()&lt;/code&gt; function.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Actual Test
&lt;/h3&gt;

&lt;p&gt;Now we have everything for writing the actual test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;textIsNotTruncatedWithEllipsis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setupScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;composeTestRule&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onAllNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isEllipsized&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;performClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;composeTestRule&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onAllNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isEllipsized&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertCountEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the test, we first find all nodes that are truncated with an ellipsis and click all of them. After that, using the same matcher, we get all nodes that are ellipsised, and then assert that their count is 0 —meaning that all texts that were truncated are now expanded. &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this blog post, we’ve looked into how to write tests for two cases that often cause problems with larger font sizes: the screen is scrollable, and if there is text that is truncated, there’s a method to expand the text so that it’s readable. &lt;/p&gt;

&lt;p&gt;These tests, especially the text truncation one, are simple ones and serve as an example. Your UI might be more complex and require a different kind of testing - for example, if your strategy is that you have cards with truncated text on your UI, and when the user clicks the card, a modal opens, the tests would require a bit more to assert that the whole text can be viewed. &lt;/p&gt;

&lt;h2&gt;
  
  
  Links in the Blog Post
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://lgbtqia.fandom.com/wiki/LGBTQIA%2B_Wiki" rel="noopener noreferrer"&gt;LGBTQIA+ Wiki 🏳️‍🌈🏳️‍⚧️&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/eevajonnapanula/the-problem-of-trun-56mm"&gt;The Problem of Trun..&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>a11y</category>
      <category>testing</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Wear OS Accessibility Considerations</title>
      <dc:creator>Eevis</dc:creator>
      <pubDate>Sat, 06 Sep 2025 10:39:00 +0000</pubDate>
      <link>https://forem.com/eevajonnapanula/wear-os-accessibility-considerations-32i1</link>
      <guid>https://forem.com/eevajonnapanula/wear-os-accessibility-considerations-32i1</guid>
      <description>&lt;p&gt;Lately, I've been learning about Wear OS development. As I'm also an accessibility specialist, I've naturally also been looking into the accessibility aspects of Wear OS. The small form factor impacts many things typically associated with accessibility. Not everything can work, or is working, the same as with, for example, phones and tablets.&lt;/p&gt;

&lt;p&gt;It's been fascinating to dive into the topic and learn more about it. In this blog post, I'll give pointers on Wear OS accessibility and share some of the learnings I've had. For transparency, I must note that my perspective is very Pixel-y, as I have a Pixel Watch 3. &lt;/p&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Screen Reader Seems to Be on the Main Stage
&lt;/h2&gt;

&lt;p&gt;Most of the available documentation about Wear OS accessibility focuses on making apps accessible for screen reader users. The &lt;a href="https://developer.android.com/training/wearables/accessibility" rel="noopener noreferrer"&gt;Accessibility on Wear OS&lt;/a&gt;-page first mentions font size and rotary input (which we'll discuss later in this blog post), and then proceeds to optimizing apps for TalkBack (the built-in screen reader), which takes up most of the page. &lt;/p&gt;

&lt;p&gt;And I'm not surprised - that's the case with most of the accessibility documentation for any platform. And don't get me wrong, screen reader accessibility is important, but there's often this misconception that that's all accessibility is. However, it's not just screen readers - there are many other sides to it. &lt;/p&gt;

&lt;p&gt;In Wear OS's case, however, I think that it might even be warranted to keep the screen reader accessibility on the main stage of the accessibility documentation. The reason for that is that many other things work out of the box. I mean, of course, there are ways to make them not work, but unless you're misusing components, most of the things work.&lt;/p&gt;

&lt;p&gt;The "Optimize your app for Talkback" section in the forementioned documentation comes down to basics: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use built-in components, as they already have the semantics, accessibility actions, focus handling, etc. included.&lt;/li&gt;
&lt;li&gt;Add content descriptions to tiles and complications. 

&lt;ul&gt;
&lt;li&gt;Tip! If you're wondering what to write in a content description field, or if the element even needs one, I've written a guide: &lt;a href="https://dev.to/eevajonnapanula/how-to-add-content-descriptions-in-compose-a-guide-for-android-devs-d22"&gt;How to Add Content Descriptions in Compose - A Guide for Android Devs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Understand list behaviours, so that you can build the app in a way that supports Talkback.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;I recommend checking out the further details from the &lt;a href="https://developer.android.com/training/wearables/accessibility" rel="noopener noreferrer"&gt;Accessibility on Wear OS&lt;/a&gt;-page!&lt;/p&gt;

&lt;h2&gt;
  
  
  Small Screen Size
&lt;/h2&gt;

&lt;p&gt;As mentioned, small screen size brings its own accessibility (and usability) concerns. There's not much space for elements on the screen, so many of the usual design patterns for, e.g., phone apps don't work. &lt;/p&gt;

&lt;p&gt;One cognitive accessibility-related concern is the use of icon buttons. And more specifically, icon buttons without labels. When the screen estate gets smaller, the number of labelless icon buttons usually increases. &lt;/p&gt;

&lt;p&gt;Why is it a problem? Well, icons are not universal. It's not always easy to understand what each icon communicates and what action the button performs if there is no label attached to explain it. And if you have, for example, some cognitive disability, or are just stressed, it might even make things worse. &lt;/p&gt;

&lt;p&gt;I personally hate icon buttons, because I rarely know what each of them does. I don't learn them, and I also hate the idea that I need to test what they do to know what they do. What if I accidentally delete some important data? Or call someone? The latter is probably my worst nightmare. &lt;/p&gt;

&lt;p&gt;And yet, at the same time, I do understand that the small screen size requires some compromises on this front. &lt;/p&gt;

&lt;h2&gt;
  
  
  Font Size Considerations
&lt;/h2&gt;

&lt;p&gt;Then there are the font size considerations - your app needs to support user's preferred font size. This support may require you to adjust the design of your app to accommodate the larger font sizes better.&lt;/p&gt;

&lt;p&gt;The Wear OS accessibility page also mentions the following:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use an ellipsis to show that text overflows its container. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I disagree with this to some extent. Truncating texts can be a huge accessibility (and usability) barrier. If you're interested in why, I've written a blog post about it: &lt;a href="https://dev.to/eevajonnapanula/the-problem-of-trun-56mm"&gt;The Problem of Trun...&lt;/a&gt; So, my suggestion would be to think about whether the design could be changed to allow text to reflow if it doesn't fit the container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minimum Touch Target Size
&lt;/h2&gt;

&lt;p&gt;Continuing from the previous section, as the watch screen is small, the importance of conforming to minimum touch target sizes with clickable items is even more important. And at the same time, it's tempting to reduce the clickable size to save screen space.&lt;/p&gt;

&lt;p&gt;The minimum touch target size requirement, as per Android Material Design, is 48 x 48 dp. However, for Wear OS and small screen sizes, some situations allow a 40 x 40 dp size. The accessibility documentation does not specify these situations. &lt;/p&gt;

&lt;p&gt;If you're using the built-in Material components, then you should be fine on this requirement. They have enough padding for clickable items to fill the touch target size needs. &lt;/p&gt;

&lt;h2&gt;
  
  
  Different Input types
&lt;/h2&gt;

&lt;p&gt;Of course, as a medium, and due to its small screen size, a watch differs in input compared to, for example, a phone. I'll discuss two input types next: The text and rotary input.&lt;/p&gt;

&lt;h3&gt;
  
  
  Text Input
&lt;/h3&gt;

&lt;p&gt;Inputting text on a small device presents some challenges. Luckily, Wear OS supports multiple ways of inputting text (and other data) to your app. Remote Input is the technical solution for this, and as &lt;a href="https://developer.android.com/training/wearables/user-input/wear-ime" rel="noopener noreferrer"&gt;Create input method editors on Wear&lt;/a&gt; lists, these options include: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dictation&lt;/li&gt;
&lt;li&gt;Emoji&lt;/li&gt;
&lt;li&gt;Canned responses&lt;/li&gt;
&lt;li&gt;Smart Reply&lt;/li&gt;
&lt;li&gt;Default IME&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default keyboard has tiny characters (because of, you guessed it, the space constraints), so I personally like to use dictation way more. It's surprisingly good with Finnish, but with English, I apparently pronounce words in a way that it takes several attempts to get them right. &lt;/p&gt;

&lt;p&gt;From an accessibility perspective, it's important to support different ways of inputting text. Someone might not be able to hit the keys on the keyboard, and someone might not be able to dictate, so supporting both makes the app more inclusive for users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rotary Input
&lt;/h3&gt;

&lt;p&gt;The Wear OS Accessibility documentation explains the rotary input:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Most Wear OS devices contain a physical rotating side button (RSB), rotating bezel or touch bezel. This is called a rotary input. You can use the rotary input to adjust the volume of media apps, scroll content up or  down, and more.&lt;/p&gt;
&lt;p&gt;Wear OS devices are smaller than mobile devices, which presents additional challenges. Users with dexterity challenges may find accuracy on a small screen difficult. Screen reader users may also find it difficult to use two-finger interactions for scrolling. Using rotary input assists users with these challenges by providing a more convenient way to scroll rather than using the two-finger interaction.&lt;/p&gt;

&lt;/blockquote&gt;

&lt;p&gt;In short, from the accessibility perspective, supporting Rotary input is essential. The &lt;a href="https://developer.android.com/training/wearables/compose/rotary-input" rel="noopener noreferrer"&gt;Rotary input with Compose&lt;/a&gt; page explains how you can do that, so I'm not covering it here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reduce Animations
&lt;/h2&gt;

&lt;p&gt;You know, the first thing I do when I get a new device is check for the reduce/remove animations or reduce motion setting, as I severely need it. I was happy to find it on my watch and turned it on. And after a while, I was surprised - I don't really need this setting on a watch as the animations that size don't trigger my symptoms. &lt;/p&gt;

&lt;p&gt;But others benefit from this setting, so you want to support it. Luckily, if you're using the built-in components and animation APIs, then they take care of supporting the Reduce animations setting as well.&lt;/p&gt;

&lt;p&gt;However, if you're building something customized, or get a bug ticket from someone saying your app doesn't respect the setting, here's how you can read the value of the setting and then adjust your code according to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isReduceMotionOn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalReduceMotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it. I wish it were as simple on phones, and this Composition Local would also be available for larger Android devices (than watches). &lt;/p&gt;

&lt;p&gt;Curious why the reduce motion (or, remove animations) setting is important for someone? A couple of years back, I wrote a blog post about motion sensitivity and how to support the remove animations setting on Android: &lt;a href="https://dev.to/eevajonnapanula/android-animations-and-reduced-motion-2em2"&gt;Android, Animations and Reduced Motion&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Magnification
&lt;/h2&gt;

&lt;p&gt;Wear OS also provides a pretty neat magnification tool. While it's not something you'd probably need to support in your app code, it's useful to know it exists. You can turn it on from the accessibility settings of the watch. &lt;/p&gt;

&lt;p&gt;With it, the same considerations apply as with the magnification on larger devices. These concerns mean, for example, having good enough color contrasts, not relying on touch-input only, keeping text field and other interactive element labels close to each other, and using graphics that don't pixelate too much when zoomed in. &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;So, in this blog post I've discussed different accessibility considerations for Wear OS apps. I've discussed screen reader support, small screen sizes, font size considerations, minimum touch target size, supporting different input types, the reduce animations setting, and magnification.&lt;/p&gt;

&lt;p&gt;Were these all familiar to you, or did you learn something new?&lt;/p&gt;

&lt;h2&gt;
  
  
  Links in the Blog Post
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/training/wearables/accessibility" rel="noopener noreferrer"&gt;Accessibility on Wear OS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/eevajonnapanula/how-to-add-content-descriptions-in-compose-a-guide-for-android-devs-d22"&gt;How to Add Content Descriptions in Compose - A Guide for Android Devs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/eevajonnapanula/the-problem-of-trun-56mm"&gt;The Problem of Trun...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/training/wearables/compose/rotary-input" rel="noopener noreferrer"&gt;Rotary input with Compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/training/wearables/user-input/wear-ime" rel="noopener noreferrer"&gt;Create input method editors on Wear&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/eevajonnapanula/android-animations-and-reduced-motion-2em2"&gt;Android, Animations and Reduced Motion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>wearos</category>
      <category>a11y</category>
      <category>programming</category>
    </item>
    <item>
      <title>Does Claude Generate Accessible Apps</title>
      <dc:creator>Eevis</dc:creator>
      <pubDate>Fri, 22 Aug 2025 12:13:00 +0000</pubDate>
      <link>https://forem.com/eevajonnapanula/does-claude-generate-accessible-apps-5759</link>
      <guid>https://forem.com/eevajonnapanula/does-claude-generate-accessible-apps-5759</guid>
      <description>&lt;p&gt;During the spring and summer, I've been testing different AI tools for their ability to generate accessible Android user interfaces. This post is the final testing post in the series. I will then write a recap post to summarize all my findings.&lt;/p&gt;

&lt;p&gt;The drill is the same for this test as well - I generated an Android app with Claude and then tested it with various accessibility tools, settings, and assistive technologies. Let's first talk about the app I generated. &lt;/p&gt;

&lt;h2&gt;
  
  
  The App
&lt;/h2&gt;

&lt;p&gt;This time, too, I did only one round of tests. The reason is similar to that of the previous tests with Cursor - the setup. I didn't want to pay $20 for this one blog post, so I'm using the free version, which means I'll need to operate within Claude's app and use the chat feature. Not optimal, but it gets the job done within the limits of my tests. &lt;/p&gt;

&lt;p&gt;So, in short, I generated the code in the chat, then created an Android project in Android Studio, added the necessary files, and copy-pasted the code into them. &lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt
&lt;/h3&gt;

&lt;p&gt;The prompt I gave to Claude was: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can you generate UI screens for an Android app with Jetpack Compose, in which user can keep track of their yarn stash and knitting needles per size and type? It should contain the following screens: Home screen, where user can add both yarn and needles, a list screen for yarn, a details screen for individual yarn, a list screen for knitting needles, a details screen for knitting needles.&lt;/p&gt;
&lt;p&gt;The information to store about yarn consists of following: Brand name, yarn name, colorway, yarage in meters, weight of skein, amount of skeins, picture. Information for knitting needles consists of the following: Brand name, knitting needles size in mm, type (single pointed, double pointed, circular). The type should be a dropdown.&lt;/p&gt;
&lt;p&gt;The app screens should be accessible for users with alternative navigation (switch device, keyboard, voice navigation), as well as screen readers, and it should work well in both vertical and landscape orientations, as well as with larger font sizes.&lt;/p&gt;
&lt;p&gt; It should follow all Material3 best practices.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It listed the accessibility aspects considered. Here's that list:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Semantic descriptions for all interactive elements &lt;/li&gt;
&lt;li&gt;Proper role assignments (Button, Switch, etc.) &lt;/li&gt;
&lt;li&gt;Content descriptions for screen readers &lt;/li&gt;
&lt;li&gt;Focus management with keyboard navigation support &lt;/li&gt;
&lt;li&gt;Support for large text scaling &lt;/li&gt;
&lt;li&gt;High contrast mode option &lt;/li&gt;
&lt;li&gt;Voice announcement controls &lt;/li&gt;
&lt;li&gt;Logical tab order in forms &lt;/li&gt;
&lt;li&gt;Accessible dropdown menus &lt;/li&gt;
&lt;li&gt;Proper heading hierarchy &lt;/li&gt;
&lt;li&gt;Alternative text for images/icons &lt;/li&gt;
&lt;li&gt;Screen orientation support &lt;/li&gt;
&lt;li&gt;Touch target size compliance (48dp minimum) &lt;/li&gt;
&lt;li&gt;Color contrast compliance with Material 3 &lt;/li&gt;
&lt;li&gt;Support for switch navigation devices&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;A funny thing is that even though Claude generated a &lt;a href="https://github.com/eevajonnapanula/ai-app-generation-tests/blob/main/ClaudeTestApp/app/src/main/java/com/eevajonna/claudetestapp/utils/AccessibilityExtensions.kt" rel="noopener noreferrer"&gt;AccessibilityExtensions.kt&lt;/a&gt;-file, it's not used anywhere. And oh my, how many redundancies does it contain. I could write a separate blog post about it. &lt;/p&gt;

&lt;p&gt;So, while this list has many good points, in the context of this app, it's mostly just a list without the actual implementation of the relevant points.&lt;/p&gt;

&lt;h3&gt;
  
  
  The UI
&lt;/h3&gt;

&lt;p&gt;Here's a short video of how the app turned out:&lt;/p&gt;


   


&lt;p&gt;I must say I like it more than the other generated apps, which looked like copies of each other. &lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Process
&lt;/h2&gt;

&lt;p&gt;After building the app, I ran a limited set of manual accessibility tests on the app. I used my Pixel Fold, as I have everything for testing set up on it. The tools, assistive technologies, and accessibility settings I tested the app with were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accessibility Scanner  &lt;/li&gt;
&lt;li&gt;TalkBack &lt;/li&gt;
&lt;li&gt;Switch Access &lt;/li&gt;
&lt;li&gt;Physical keyboard &lt;/li&gt;
&lt;li&gt;Voice Access &lt;/li&gt;
&lt;li&gt;Large font sizes &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problems I Found
&lt;/h2&gt;

&lt;p&gt;As I expected, the app wasn't without any accessibility problems. Most notably, the familiar problem with redundant content descriptions was present, and it also hallucinated some semantic roles. &lt;/p&gt;

&lt;p&gt;Let's look at the problems more closely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redundant Content Descriptions Are Here to Stay, Unfortunately
&lt;/h3&gt;

&lt;p&gt;As with all the other apps, Claude's app also had redundant content descriptions. Here's one example (I've omitted the irrelevant parts)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;FloatingActionButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
        &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;semantics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Add new yarn to collection"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Add New Yarn"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the "to collection" part doesn't add any relevant information. When the &lt;code&gt;FloatingActionButton&lt;/code&gt; has the &lt;code&gt;contentDescription&lt;/code&gt; attribute and the text within, the accessibility text for this component is "Add new yarn to collection. Add new yarn." The solution would be to omit the &lt;code&gt;semantics&lt;/code&gt; modifier with &lt;code&gt;contentDescription&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Claude's code takes the redundancy even further: for some &lt;code&gt;contentDescription&lt;/code&gt;s, it adds redundant action text as well. Here's one example, a card for displaying needle list items with texts within the &lt;code&gt;Card&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;semantics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;
            &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
                &lt;span class="s"&gt;"${needle.brandName} ${needle.type.displayName} "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;"needles, ${needle.sizeMillimeters} millimeters. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; 
                &lt;span class="s"&gt;"Tap for details."&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The content description now contains all the same information as the contents of this card. It's already exposed to accessibility services, so it's redundant to add it here. However, it also adds the "Tap for details" text, which is redundant.&lt;/p&gt;

&lt;p&gt;The card, being a clickable element, already contains the semantics to convey that it is clickable, so there's no need to indicate it in the content description. In addition, as it's an item on a list, so together with list semantics and clickability, there's no need to indicate that tapping it would open the details. That's a pattern in many apps, so it's likely familiar to users. &lt;/p&gt;

&lt;p&gt;And now that we're on the topic of &lt;code&gt;contentDescription&lt;/code&gt;s, it's interesting how Claude adds &lt;code&gt;contentDescription&lt;/code&gt; for &lt;code&gt;IconButton&lt;/code&gt;s with &lt;code&gt;semantics&lt;/code&gt;, and not with, well, the &lt;code&gt;contentDescription&lt;/code&gt;-attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;IconButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* TODO: Edit yarn */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;semantics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Edit yarn details"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;painter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Icons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Edit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the &lt;code&gt;contentDescription&lt;/code&gt; is not redundant in this case, the more straightforward solution to set it would be the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;IconButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* TODO: Edit yarn */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;painter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Icons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Edit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Edit yarn details"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Missing Grouping in Details Screen
&lt;/h3&gt;

&lt;p&gt;The details, both yarn and needle, would need to be grouped with the labels and values so that they can be read together. Now, for example, reading the row with the label "Brand" and its brand name requires the user to navigate through both to obtain the information. With &lt;code&gt;mergeDecendants = true&lt;/code&gt; for the &lt;code&gt;semantics&lt;/code&gt; modifier, the user would hear both of them together. &lt;/p&gt;

&lt;p&gt;In code, it would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// DetailRow.kt&lt;/span&gt;

&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;DetailRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;semantics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mergeDescendants&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="nc"&gt;Adding&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;
        &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Hallucinated Semantics
&lt;/h3&gt;

&lt;p&gt;Out of all the AI tools I've tested this way, Claude has been the first to hallucinate something that prevented me from building the app. As I mentioned, it generated a whole &lt;code&gt;AccessibilityExtensions.kt&lt;/code&gt; file, the contents of which are not in use. It had this one &lt;code&gt;accessibleTextField&lt;/code&gt; modifier with &lt;code&gt;Role.TextField&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accessibleTextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;isRequired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;semantics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isRequired&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="s"&gt;"$label, required field"&lt;/span&gt; 
    &lt;span class="k"&gt;else&lt;/span&gt; 
        &lt;span class="n"&gt;label&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AnnotatedString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="nc"&gt;This&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="n"&gt;doesn&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;exist&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When looking at the &lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/Role" rel="noopener noreferrer"&gt;documentation of the &lt;code&gt;Role&lt;/code&gt;&lt;/a&gt;, it doesn't have the &lt;code&gt;TextField&lt;/code&gt; role. Additionally, all the semantics here are somewhat redundant, as the &lt;code&gt;TextField&lt;/code&gt; composable already provides them. &lt;/p&gt;

&lt;h3&gt;
  
  
  No Proper Support for Button Navigation
&lt;/h3&gt;

&lt;p&gt;The final problem I'm going to discuss is that the app doesn't support button navigation well. You know, the navigation mode, where there are those buttons at the bottom of the screen - see the video above in the the UI-section.&lt;/p&gt;

&lt;p&gt;When increasing the font and display sizes, the app doesn't leave space for the navigation bar, and some of the content is hidden behind the system navigation bar, as seen in the picture below: &lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/mpqufjsy02zr/51JbkuTErKVSBruOBAetK2/c35689cd931e09fd21b5a77ee180d62e/claude-test-app-navigation-bars.jpg" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/mpqufjsy02zr/51JbkuTErKVSBruOBAetK2/c35689cd931e09fd21b5a77ee180d62e/claude-test-app-navigation-bars.jpg" alt="Bottom of the app screen, showing Quick actions card with Add yarn and Add needles buttons. Semitransparent navigation bar covers half of the Add needles button."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I personally use the button navigation (so, not the gesture navigation), and surprisingly many apps have this same problem. &lt;/p&gt;

&lt;h2&gt;
  
  
  In Summary
&lt;/h2&gt;

&lt;p&gt;As mentioned before, I liked Claude's app the most from the UI perspective. From an accessibility perspective, the generated app wasn't too bad - it did have accessibility issues, but not as many as, for example, Gemini's first attempt. &lt;/p&gt;

&lt;p&gt;However, this was the first time in my tests when I got some hallucinations. The hallucinated code wasn't actually used by the app, but it could have been.&lt;/p&gt;

&lt;p&gt;The results didn't surprise me. I've reached the point of saturation, so it's time to write the summary post for all the tests I've been running over the last few months. So, stay tuned, that's coming next. &lt;/p&gt;

&lt;h2&gt;
  
  
  Links in Blog Post
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/eevajonnapanula/ai-app-generation-tests/tree/main/ClaudeTestApp" rel="noopener noreferrer"&gt;Claude Test App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/eevajonnapanula/ai-app-generation-tests/blob/main/ClaudeTestApp/app/src/main/java/com/eevajonna/claudetestapp/utils/AccessibilityExtensions.kt" rel="noopener noreferrer"&gt;AccessibilityExtensions.kt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/Role" rel="noopener noreferrer"&gt;documentation of the &lt;code&gt;Role&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>a11y</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>10 Tips to Make Your Blog Posts More Accessible</title>
      <dc:creator>Eevis</dc:creator>
      <pubDate>Fri, 18 Jul 2025 14:47:00 +0000</pubDate>
      <link>https://forem.com/eevajonnapanula/10-tips-to-make-your-blog-posts-more-accessible-1o7m</link>
      <guid>https://forem.com/eevajonnapanula/10-tips-to-make-your-blog-posts-more-accessible-1o7m</guid>
      <description>&lt;p&gt;Sometimes I encounter this weird idea that developers don't need accessibility at all. Some people seem to believe that our development tools and resources don't need to be accessible, because accessibility is just something to do with the end users of our services.&lt;/p&gt;

&lt;p&gt;If you're one of those people, you know, disabled software developers exist. You're reading text from one right now, and I know many developers with disabilities. Yes, even blind developers. &lt;/p&gt;

&lt;p&gt;I've been writing a blog since 2019, and as an accessibility specialist, I've tried to make my blog posts as accessible as possible. In this blog post, I decided to share some of the things I've learned over the years. So, without further ado, let's get started. &lt;/p&gt;

&lt;h2&gt;
  
  
  Share Code as Text, not as Images
&lt;/h2&gt;

&lt;p&gt;If you're writing a technical blog post and sharing some code, always write it as text, never share it as an image. Why? There are multiple reasons. &lt;/p&gt;

&lt;p&gt;First of all, it's super annoying to copy the code from a blog post if it's an image. The reader would need to type it out, instead of just copying and pasting. At least for me, when  I'm sharing code in a blog post, it's meant to be used, and the fastest way is to use copy and paste.&lt;/p&gt;

&lt;p&gt;Images of code are also an accessibility problem. With an image, there is no way to make the text bigger, change colors for better contrast, or modify the text row length. All of these are things people might need to make the web content more accessible for them. &lt;/p&gt;

&lt;p&gt;And then there's the text alternative part - if you're sharing an image of text, you should always include the text as text alternative (alt text) for the image. However, even if you include code, the way screen readers interact with alt texts makes it more challenging to use. E.g., navigating within the code is harder because screen readers read the text from the beginning, and it's not really possible to jump from one row to another. &lt;/p&gt;

&lt;p&gt;So, always put code into a code block as text. Most blogging platforms allow this, and if you have your own website, semantic HTML also has these tags (hint: &lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt;) for including code without running it. &lt;/p&gt;

&lt;p&gt;I mentioned alt text or text alternatives - if you're unfamiliar with the concept, I'll explain it in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write Text Alternatives for Graphics
&lt;/h2&gt;

&lt;p&gt;The next tip I'm going to give is to write text alternatives for images. Usually, blogging platforms allow this, and if you're using markdown, you can write it in the first brackets of the image. So,&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;![Alt text goes here](image url)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;And if you're writing in pure HTML,  the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element has the &lt;code&gt;alt&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;img src="..." alt="Text alternative goes here" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? Text alternatives are there for people who can't see the picture. And that might include you, even if you're a sighted person. Most importantly, it's for assistive technology, such as screen readers, users. They need the same content as a sighted person can see as text. &lt;/p&gt;

&lt;p&gt;It's also useful in cases where the image doesn't load. Slow internet? Yup, been there. If the image has a text alternative, I can still read the article. And all those crawlers that crawl websites can use everything that's in text format, so it's good for SEO and AI search.&lt;/p&gt;

&lt;p&gt;And what should you write as the text alternative? &lt;a href="https://webaim.org/techniques/alttext/" rel="noopener noreferrer"&gt;WebAIM's Alt text page&lt;/a&gt; has excellent resources, explaining how, e.g., context affects the decision, and what decorative images are. &lt;/p&gt;

&lt;h2&gt;
  
  
  Use Readability Score to Guide Your Writing
&lt;/h2&gt;

&lt;p&gt;When you're writing a blog post, I wholeheartedly recommend using tools like &lt;a href="https://hemingwayapp.com/" rel="noopener noreferrer"&gt;Hemingway&lt;/a&gt; or &lt;a href="https://www.grammarly.com/" rel="noopener noreferrer"&gt;Grammarly&lt;/a&gt; for grammar. Additionally, they're great for getting a readability score for your text. I'll share about it in a bit. &lt;/p&gt;

&lt;p&gt;I'm not a native English speaker, and I've relied on Grammarly for writing and checking my grammar for the whole time I've been writing blog posts. Nowadays, it complains less (I guess I've learned something, yay!), and it's been extremely helpful in producing better text. And no, I'm not using the generative AI features. I want to write my own text. &lt;/p&gt;

&lt;p&gt;The readability score is a fantastic feature. It gives a score on how easy the text is to read, which helps to tailor the content for better, you guessed it, readability. &lt;/p&gt;

&lt;p&gt;There are different scoring methods. The idea for all of them is to give a score indicating the education level the reader should have to understand the text easily. If you want to know more about the different methods, Flesch-Kincaid Grade Level and Flesch Reading Ease score are good search terms to start your explorations.&lt;/p&gt;

&lt;p&gt;And okay, now you might think that for technical content, the education level should be university-level, as everyone in this field has at least a bachelor's degree. Okay, I really do hope you don't think that, because not everyone does. &lt;/p&gt;

&lt;p&gt;We're reading texts in different situations. If the text is written in a way that it's readable for an eighth-grader, it also helps anyone who's, e.g., under stress to understand the text better. And I don't know about any of you, but for me, being stressed has been a large part of being a software developer. &lt;/p&gt;

&lt;p&gt;Oh, and if you're wondering, the readability score of this text is 69 on Grammarly. It translates roughly to 8th grade, as Grammarly uses Flesch reading ease scoring. &lt;/p&gt;

&lt;h2&gt;
  
  
  List Links at The End
&lt;/h2&gt;

&lt;p&gt;My next tip is to list all the links included within the text at the end of either a section or the blog post. For some people, it might be hard to see links inside a paragraph. This is especially true if the link styles don't differ that much from the text styles, e.g. the link is annotated only with a (slight) color change and lacks underlining.&lt;/p&gt;

&lt;p&gt;And when you do list the links, use the same link text as in the original link (so, inside the text). Links leading to the same destination should always have the same text. &lt;/p&gt;

&lt;p&gt;As an example, you can find links in this blog post in the Links in the Blog Post section. &lt;/p&gt;

&lt;h2&gt;
  
  
  Add Table of Contents
&lt;/h2&gt;

&lt;p&gt;Adding a table of contents at the beginning of a blog post is also a way to make the post more accessible. This way, the reader can get a better picture of what the blog post contains. If the table of contents also includes links to each section, they can also navigate to relevant sections more easily. &lt;/p&gt;

&lt;p&gt;I have a table of contents in each blog post in the original post that I publish on my website. It's automatically generated from the top-level headings. I've been thinking about copying it to the articles I'm cross-posting, but haven't done so yet. &lt;/p&gt;

&lt;h2&gt;
  
  
  Write Clearly and Simply
&lt;/h2&gt;

&lt;p&gt;Writing clearly makes the blog post's content more understandable. It's also connected to the readability score, but it's more than just the length of the sentences and the words used - the factors used in the reading algorithms. &lt;/p&gt;

&lt;p&gt;WebAIM provides a good resource on the topic, and I used the title of the page as the title of this section. The resource is available in &lt;a href="https://webaim.org/techniques/writing/" rel="noopener noreferrer"&gt;WebAIM: Writing Clearly and Simply&lt;/a&gt;. I'll list the points from WebAIM below; you can read more about each step from the link to WebAIM's page:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Organize your ideas into a logical outline—before and during the writing process.&lt;/li&gt;
&lt;li&gt;Introduce, explain, summarize.&lt;/li&gt;
&lt;li&gt;Stay on point.&lt;/li&gt;
&lt;li&gt;Make it interesting.&lt;/li&gt;
&lt;li&gt;Write for your audience.&lt;/li&gt;
&lt;li&gt;Assume that your readers are intelligent, but do not assume that they know the subject matter as well as you.&lt;/li&gt;
&lt;li&gt;Write cohesive paragraphs constructed around a single major idea.&lt;/li&gt;
&lt;li&gt;Avoid slang and jargon.&lt;/li&gt;
&lt;li&gt;Use familiar words and combinations of words.&lt;/li&gt;
&lt;li&gt;Use active voice…
10a. …but not necessarily always.&lt;/li&gt;
&lt;li&gt;Use forceful verbs.&lt;/li&gt;
&lt;li&gt;Use parallel sentence construction.&lt;/li&gt;
&lt;li&gt;Use positive terms.&lt;/li&gt;
&lt;li&gt;Give direct instructions.&lt;/li&gt;
&lt;li&gt;Use positive or single-negative phrasing.&lt;/li&gt;
&lt;li&gt;Explain acronyms and abbreviations; avoid them if possible.&lt;/li&gt;
&lt;li&gt;Check spelling.&lt;/li&gt;
&lt;li&gt;Write short sentences.&lt;/li&gt;
&lt;li&gt;Ensure that every word and paragraph is necessary.&lt;/li&gt;
&lt;li&gt;When you're finished, stop.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have to admit that this is something I'm still learning myself. Using tools like Grammarly or Hemingway for writing helps, as they have checks for many of these points. However, I still struggle with some of them regularly. If you feel overwhelmed by the number of things to consider, I recommend using one of the writing tools mentioned and learning one thing at a time. &lt;/p&gt;

&lt;h2&gt;
  
  
  Explain Code Examples
&lt;/h2&gt;

&lt;p&gt;If you share examples of code, in addition to giving them in text format, explain them as well. Even if you know what the code does just by looking at it, not everyone knows. Unless you know every single reader of your post and can be sure that they're never, for example, stressed or tired, explaining the code improves cognitive accessibility for readers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Semantic HTML
&lt;/h2&gt;

&lt;p&gt;This tip is relevant if you have access to the source code, such as when publishing articles on your website. &lt;/p&gt;

&lt;p&gt;First of all, use semantic HTML on your website. Seriously, using &lt;code&gt;div&lt;/code&gt;s for everything is not accessible. Semantic elements provide a lot of functionality and information for accessibility services and different navigation APIs by default, so you don't need to add that information or functionality yourself. &lt;/p&gt;

&lt;p&gt;If you want to learn more about semantic HTML, I've written a blog post waaaay back: &lt;a href="https://dev.to/eevajonnapanula/ode-to-semantic-html-38c3"&gt;Ode to Semantic HTML&lt;/a&gt;. MDN's Curriculum has also &lt;a href="https://developer.mozilla.org/en-US/curriculum/core/semantic-html/" rel="noopener noreferrer"&gt;a section about Semantic HTML&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Display Estimated Reading Time
&lt;/h2&gt;

&lt;p&gt;Another tip for sites where you can customise the presentation of the article beyond just the text is to consider adding estimated reading time for the article. Having it at the beginning of the article can help users decide if now is the time to read the article, or if they should return to it later. It also helps to manage expectations - roughly, how long should I expect to spend reading it?&lt;/p&gt;

&lt;p&gt;Depending on the technology you're using for building the site, there are tools or plugins for that. For example, my site is built with Eleventy, and I use &lt;br&gt;
&lt;a href="https://www.npmjs.com/package/eleventy-plugin-time-to-read?activeTab=readme" rel="noopener noreferrer"&gt;&lt;code&gt;eleventy-plugin-time-to-read&lt;/code&gt;&lt;/a&gt; for calculating the time to read. It's not perfect, but it gives a good enough number for the purposes. &lt;/p&gt;

&lt;h2&gt;
  
  
  Consider Adding a Summary
&lt;/h2&gt;

&lt;p&gt;The last tip I'm going to share is to consider adding a short "Too Long, Didn't Read" (TL;DR) type of summary at the beginning of the blog post. This summary provides the reader with an idea of what's in the blog post, whether it's relevant for them, and helps to understand the content better.&lt;/p&gt;

&lt;p&gt;I have been planning to add this for a long time, but haven't gotten to it yet, as it has felt hard to summarize the essential points. Like, what's the most important thing in the post? But I intend to explore how well different AI tools would do this, meaning I'll just need to find the right prompt and then check the result to see if it's correct.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this blog post, I've shared some advice on how to make blog posts more accessible for all. The tips have ranged from the text and content itself to modifying the site itself to make it accessible, if you have access to the website's source code. &lt;/p&gt;

&lt;p&gt;Do you have any additional advice? Did you learn something new?&lt;/p&gt;

&lt;h2&gt;
  
  
  Links in the Blog Post
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://webaim.org/techniques/alttext/" rel="noopener noreferrer"&gt;WebAIM's Alt text page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hemingwayapp.com/" rel="noopener noreferrer"&gt;Hemingway&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.grammarly.com/" rel="noopener noreferrer"&gt;Grammarly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/eevajonnapanula/ode-to-semantic-html-38c3"&gt;Ode to Semantic HTML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webaim.org/techniques/writing/" rel="noopener noreferrer"&gt;WebAIM: Writing Clearly and Simply&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/curriculum/core/semantic-html/" rel="noopener noreferrer"&gt;a section about Semantic HTML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/eleventy-plugin-time-to-read?activeTab=readme" rel="noopener noreferrer"&gt;&lt;code&gt;eleventy-plugin-time-to-read&lt;/code&gt;&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>writing</category>
      <category>a11y</category>
      <category>html</category>
      <category>inclusion</category>
    </item>
    <item>
      <title>It's All About (Accessibility) Focus And Compose</title>
      <dc:creator>Eevis</dc:creator>
      <pubDate>Mon, 07 Jul 2025 12:41:00 +0000</pubDate>
      <link>https://forem.com/eevajonnapanula/its-all-about-accessibility-focus-and-compose-48ki</link>
      <guid>https://forem.com/eevajonnapanula/its-all-about-accessibility-focus-and-compose-48ki</guid>
      <description>&lt;p&gt;I've seen multiple questions in various Slack communities, Stack Overflow, and other places related to focus on Android and how it doesn't behave as expected. The question typically concerns the use of &lt;code&gt;focusRequester&lt;/code&gt; and then inquires why Talkback or other assistive technologies fail to set focus correctly. &lt;/p&gt;

&lt;p&gt;The simple answer is that these APIs are different, and focus behaves differently with accessibility focus and keyboard focus. &lt;/p&gt;

&lt;p&gt;In this blog post, I'll discuss the differences and what you can do in different cases. Let's first look at what I mean when I talk about accessibility focus and keyboard focus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility Focus
&lt;/h2&gt;

&lt;p&gt;In this blog post, accessibility focus refers to the focus related to screen readers, such as TalkBack. It could include focus for switch access as well, but I'm leaving it out of the scope of this blog post. &lt;/p&gt;

&lt;p&gt;When using a screen reader, accessibility focus can be set to any element relevant to the user, such as interactive elements, text, images with text alternatives (content descriptions), and other meaningful elements. The focus indicator can look different on different phones, but here's an example from my phone:&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/mpqufjsy02zr/1Wrg2nNHZVSAYixKVTI5FF/5e032ff39e7928a9052398922833240b/TalkBack.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/mpqufjsy02zr/1Wrg2nNHZVSAYixKVTI5FF/5e032ff39e7928a9052398922833240b/TalkBack.png" alt="In the picture, there are two instances of the same UI element side by side: first, a button with the text 'Button', and below it, a switch with the label 'Switch'. Under the first one, there's a text Button focused, and there is a green rectangle around the button. Under the second one, the text is Switch focused, and the green rectangle is around the Switch and its label."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's set by the system, so the app developer can't edit it, and there shouldn't be any need to do that either. &lt;/p&gt;

&lt;h2&gt;
  
  
  Keyboard Focus
&lt;/h2&gt;

&lt;p&gt;Keyboard focus, on the other hand, is the focus that interactive elements receive when a user uses a keyboard, D-pad, or other keyboard-emulating device for navigation.&lt;/p&gt;

&lt;p&gt;Only interactive elements should be focusable; never, for example, headings or other text. The default focus indicator is the ripple, so it's not that visible: &lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/mpqufjsy02zr/1G8xSRBFEy1pfNH6pp4uof/7b5ba4f4453774e273b1a24c88520577/Keyboard.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/mpqufjsy02zr/1G8xSRBFEy1pfNH6pp4uof/7b5ba4f4453774e273b1a24c88520577/Keyboard.png" alt="In the picture, there are two instances of the same UI element side by side: first, a button with the text 'Button', and below it, a switch with the label 'Switch'. Under the first one, there's a text Button focused, and the button is almost in a non-noticeably lighter color. Under the second one, the text is Switch focused, and the background of the Switch and the label is almost in a non-noticeably lighter color."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Later, I will write a blog post about customising and making the focus indicator pass the accessibility legal requirements with color contrast and other requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making a Component Focusable
&lt;/h2&gt;

&lt;p&gt;So you're creating a custom component, and wondering if it's the kind that should be focusable. The short and simple answer is that every component that the user can interact with via touch input should most likely be focusable. The actual interaction should be handled with accessibility actions and key events, depending on the component. &lt;/p&gt;

&lt;p&gt;To put it even more clearly: every button, checkbox, editable text, swipeable element, and graph that has touch input for either/both scrolling or/and data point exploration, should usually be focusable. &lt;/p&gt;

&lt;p&gt;However, note that when using Material components, such as buttons and other interactive elements, they are already focusable, so you don't need to add it manually. Adding, for example, a &lt;code&gt;focusable&lt;/code&gt; modifier makes the element appear twice in the focus order. &lt;/p&gt;

&lt;p&gt;There are several ways to make a custom interactive element focusable for both screen readers and keyboard navigation. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;clickable&lt;/code&gt;, &lt;code&gt;toggleable&lt;/code&gt;, or &lt;code&gt;selectable&lt;/code&gt; modifiers&lt;/strong&gt;. If you're creating a component that the user should be able to either click, toggle, or select, use the respective modifiers. They make the component focusable as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;focusable&lt;/code&gt;-modifier&lt;/strong&gt;. Sometimes a component needs to be focusable, but it's not clickable, toggleable, or selectable. It might, for example, require custom keyboard shortcuts, such as a chart with touch input that reveals more data with touch gestures. In these cases, using the focusable modifier is the right choice for making the component focusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving Focus Programmatically to a Component
&lt;/h2&gt;

&lt;p&gt;The next thing we need to discuss is moving the focus programmatically based on the user's actions. For keyboard and keyboard-emulating navigation methods, this is relatively straightforward. For screen readers, it requires a little bit more. Let's first discuss the keyboard focus and then the accessibility focus. &lt;/p&gt;

&lt;h3&gt;
  
  
  Keyboard Focus
&lt;/h3&gt;

&lt;p&gt;If you want to move keyboard focus from an element when a new screen appears, or on, let's say, a button click, &lt;code&gt;FocusRequester&lt;/code&gt; is your friend.&lt;/p&gt;

&lt;p&gt;Here's what using it looks like in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;focusRequester&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;FocusRequester&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestFocus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Move focus"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;CustomComponentWereFocusing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggleable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, when the user clicks the &lt;code&gt;Button&lt;/code&gt;, focus moves to the &lt;code&gt;CustomComponentWereFocusing&lt;/code&gt;. We first define the &lt;code&gt;focusRequester&lt;/code&gt; by remembering &lt;code&gt;FocusRequester()&lt;/code&gt;, then set it to the &lt;code&gt;focusRequester&lt;/code&gt; modifier for the &lt;code&gt;CustomComponentWereFocusing&lt;/code&gt;. Finally, we call &lt;code&gt;FocusRequester&lt;/code&gt;'s &lt;code&gt;requestFocus()&lt;/code&gt; method on button click.&lt;/p&gt;

&lt;p&gt;One important thing to remember here, as with other modifiers, is that the order of modifiers matters. So, when setting the focusRequester modifier, it must be placed before the modifier that adds focusability to the component. Otherwise, it doesn't work at all. &lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility Focus
&lt;/h3&gt;

&lt;p&gt;Moving the accessibility focus for screen readers needs a little bit more work. The &lt;code&gt;focusRequester&lt;/code&gt; doesn't work here, so we need to resort to workarounds to accomplish it. I'll share some ideas; the final implementation, naturally, depends on the actual use case. &lt;/p&gt;

&lt;p&gt;The first suggestion is that, in some cases, changing the traversal order may be the solution. The following section discusses this topic, so keep reading for tips on how to do that.&lt;/p&gt;

&lt;p&gt;Then there's a solution for changing the &lt;code&gt;focused&lt;/code&gt; property on the &lt;code&gt;semantics&lt;/code&gt; modifier. I found this solution from &lt;a href="https://slack-chats.kotlinlang.org/t/10509998/is-there-a-way-to-request-talkback-focus-on-a-particular-ele" rel="noopener noreferrer"&gt;Kotlinlang Slack's Compose channel&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The example from the keyboard navigation section with the ability to focus with a screen reader would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isFocused&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isFocused&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isFocused&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;isFocused&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;isFocused&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Move focus"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;CustomComponentWereFocusing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;semantics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;focused&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isFocused&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggleable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, here we first define a boolean variable &lt;code&gt;isFocused&lt;/code&gt;, which we remember. Then, we set the &lt;code&gt;focused&lt;/code&gt; property of the &lt;code&gt;semantics&lt;/code&gt; modifier to &lt;code&gt;isFocused&lt;/code&gt; for &lt;code&gt;CustomComponentWereFocusing&lt;/code&gt;. On the button click, we set the &lt;code&gt;isFocused&lt;/code&gt; to true to move the focus to &lt;code&gt;CustomComponentWereFocusing&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;And finally, we have a &lt;code&gt;LaunchedEffect&lt;/code&gt; listening for changes to &lt;code&gt;isFocused&lt;/code&gt;, and if it's true, we set it back to false. It doesn't clear the focus from &lt;code&gt;CustomComponentWereFocusing&lt;/code&gt;, but lets us refocus with a new button click if needed. &lt;/p&gt;

&lt;p&gt;Here, again, the order of the modifiers matters - &lt;code&gt;semantics&lt;/code&gt; needs to be before the focusability-applying modifier, which is &lt;code&gt;toggleable&lt;/code&gt; in the example. &lt;/p&gt;

&lt;h2&gt;
  
  
  Changing Traversal Order
&lt;/h2&gt;

&lt;p&gt;The final topic for this blog post is changing traversal order. Let's say we have a layout with two rows and two columns. The layout looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/mpqufjsy02zr/7FOzTyUmCVN09lxV6XR6NN/03d80ea352ca6eb47819f08ae3c9a410/Buttons.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/mpqufjsy02zr/7FOzTyUmCVN09lxV6XR6NN/03d80ea352ca6eb47819f08ae3c9a410/Buttons.png" alt="Four buttons on two rows, two on each row. The texts on the first row are First and Third, and on the second row Second and Fourth."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The default focus order would be as follows: first top row, then bottom row - meaning First, Third, Second, Fourth. And we want it to be in numerical order. &lt;/p&gt;

&lt;p&gt;The solutions for accessibility and keyboard focus are different, so let's discuss them separately, starting with accessibility focus. &lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility Focus
&lt;/h3&gt;

&lt;p&gt;For accessibility focus, using the &lt;code&gt;semantics&lt;/code&gt; modifier, &lt;code&gt;isTraversalGroup&lt;/code&gt;, and &lt;code&gt;traversalIndex&lt;/code&gt; is the way to customise the focus order.&lt;/p&gt;

&lt;p&gt;First, let's set the &lt;code&gt;isTraversalGroup&lt;/code&gt; property to &lt;code&gt;true&lt;/code&gt; for the parent component wrapping the two rows. Why? It's to create a border for our changes in traversal order. You see, if we don't set it, and set the &lt;code&gt;traversalIndex&lt;/code&gt; to the buttons, they'd be last in the screen's focus order, because their traversal index is greater than 0. Zero is the default traversal index for elements that don't have the traversal index explicitly set. However, if we set the border with &lt;code&gt;isTraversalGroup&lt;/code&gt;, then the changes will only affect the focus order within the traversal group, not the screen's focus order. &lt;/p&gt;

&lt;p&gt;So, in code it would look like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;semantics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;isTraversalGroup&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then we set the &lt;code&gt;traversalIndex&lt;/code&gt; for each of the buttons to match the focus order we want to create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;semantics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="n"&gt;traversalIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1f&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; 
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"First"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;semantics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="n"&gt;traversalIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3f&lt;/span&gt;
 &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;   &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; 
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Third"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've omitted the other row, which contains the "Second" and "Fourth" buttons, from the example, but the idea remains the same. &lt;/p&gt;

&lt;p&gt;And that's it. With these changes, the focus order for accessibility focus changes. Let's talk about keyboard focus-related changes next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keyboard Focus
&lt;/h3&gt;

&lt;p&gt;For keyboard focus, we first need to create &lt;code&gt;FocusRequester&lt;/code&gt;s for each button with &lt;code&gt;FocusRequester.createRefs()&lt;/code&gt;, and then attach them with &lt;code&gt;focusRequester&lt;/code&gt; modifier, and finally, set the focus order with &lt;code&gt;focusProperties&lt;/code&gt; modifier.&lt;/p&gt;

&lt;p&gt;Creating the &lt;code&gt;FocusRequester&lt;/code&gt;s looks like the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="py"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;third&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;fourth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nc"&gt;FocusRequester&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRefs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we set the &lt;code&gt;FocusRequester&lt;/code&gt;s with &lt;code&gt;focusRequester&lt;/code&gt; modifier and the next element to focus on with &lt;code&gt;focusProperties&lt;/code&gt; modifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focusProperties&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;second&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; 
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"First"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focusRequester&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;third&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focusProperties&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fourth&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; 
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Third"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've omitted the second row from the example, but it would be similar.&lt;/p&gt;

&lt;p&gt;This way, keyboard and keyboard-emulating device navigating users would first get to the "First" button, then "Second", then "Third", and finally, "Fourth". &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this blog post, I've discussed the differences between focusing with screen readers (e.g., TalkBack) and navigating with the keyboard. I've also demonstrated the differences between the two in changing the currently focused element and altering the traversal/focus order. &lt;/p&gt;

&lt;p&gt;If you want to read the docs, &lt;a href="https://developer.android.com/develop/ui/compose/accessibility/traversal" rel="noopener noreferrer"&gt;Modify traversal order&lt;/a&gt; is the page to check for accessibility focus, and &lt;a href="https://developer.android.com/develop/ui/compose/touch-input/focus" rel="noopener noreferrer"&gt;Focus in Compose&lt;/a&gt; summarizes the information about keyboard focus. &lt;/p&gt;

&lt;h2&gt;
  
  
  Links in the Blog Post
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://slack-chats.kotlinlang.org/t/10509998/is-there-a-way-to-request-talkback-focus-on-a-particular-ele" rel="noopener noreferrer"&gt;Kotlinlang Slack's Compose channel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/ui/compose/accessibility/traversal" rel="noopener noreferrer"&gt;Modify traversal order&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/ui/compose/touch-input/focus" rel="noopener noreferrer"&gt;Focus in Compose&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>a11y</category>
      <category>android</category>
      <category>programming</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
